From ac46df8daea09899ce30dc8fd70986e258c746bf Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 9 Feb 2018 06:46:43 -0500 Subject: Move Add-on SDK source to toolkit/jetpack --- addon-sdk/Makefile.in | 29 - addon-sdk/mach_commands.py | 107 - addon-sdk/moz.build | 542 -- addon-sdk/mozbuild.template | 20 - addon-sdk/source/.gitattributes | 5 - addon-sdk/source/.gitignore | 36 - addon-sdk/source/.jpmignore | 18 - addon-sdk/source/.travis.yml | 26 - addon-sdk/source/CONTRIBUTING.md | 54 - addon-sdk/source/LICENSE | 30 - addon-sdk/source/README.md | 34 - addon-sdk/source/app-extension/application.ini | 11 - addon-sdk/source/app-extension/bootstrap.js | 362 -- addon-sdk/source/app-extension/install.rdf | 33 - addon-sdk/source/bin/activate | 84 - addon-sdk/source/bin/activate.bat | 134 - addon-sdk/source/bin/activate.fish | 66 - addon-sdk/source/bin/activate.ps1 | 99 - addon-sdk/source/bin/cfx | 33 - addon-sdk/source/bin/cfx.bat | 6 - addon-sdk/source/bin/deactivate.bat | 23 - addon-sdk/source/bin/fx-download.sh | 7 - .../integration-scripts/buildbot-run-cfx-helper | 14 - .../bin/integration-scripts/integration-check | 364 -- addon-sdk/source/bin/jpm-test.js | 34 - addon-sdk/source/bin/node-scripts/apply-patch.js | 64 - addon-sdk/source/bin/node-scripts/test.addons.js | 57 - addon-sdk/source/bin/node-scripts/test.docs.js | 145 - addon-sdk/source/bin/node-scripts/test.examples.js | 45 - .../source/bin/node-scripts/test.firefox-bin.js | 37 - addon-sdk/source/bin/node-scripts/test.ini.js | 68 - addon-sdk/source/bin/node-scripts/test.modules.js | 28 - addon-sdk/source/bin/node-scripts/update-ini.js | 141 - addon-sdk/source/bin/node-scripts/utils.js | 104 - addon-sdk/source/bin/node-scripts/words.txt | 11 - addon-sdk/source/examples/actor-repl/README.md | 3 - .../actor-repl/data/codemirror-compressed.js | 5 - .../source/examples/actor-repl/data/codemirror.css | 264 - .../source/examples/actor-repl/data/index.html | 147 - addon-sdk/source/examples/actor-repl/data/main.css | 117 - .../source/examples/actor-repl/data/robot.png | Bin 4184 -> 0 bytes addon-sdk/source/examples/actor-repl/index.js | 37 - addon-sdk/source/examples/actor-repl/package.json | 10 - .../source/examples/actor-repl/test/test-main.js | 10 - .../source/examples/debug-client/data/client.js | 816 --- .../source/examples/debug-client/data/index.html | 50 - .../source/examples/debug-client/data/plugin.png | Bin 3819 -> 0 bytes .../source/examples/debug-client/data/task.js | 28 - addon-sdk/source/examples/debug-client/index.js | 33 - .../source/examples/debug-client/package.json | 10 - .../source/examples/debug-client/test/test-main.js | 10 - .../source/examples/reading-data/data/mom.png | Bin 4778 -> 0 bytes .../source/examples/reading-data/data/sample.html | 7 - addon-sdk/source/examples/reading-data/lib/main.js | 53 - .../source/examples/reading-data/package.json | 9 - .../examples/reading-data/tests/test-main.js | 25 - addon-sdk/source/examples/theme/data/icon-16.png | Bin 1657 -> 0 bytes addon-sdk/source/examples/theme/data/index.html | 9 - addon-sdk/source/examples/theme/data/theme.css | 7 - addon-sdk/source/examples/theme/lib/main.js | 37 - addon-sdk/source/examples/theme/package.json | 10 - addon-sdk/source/examples/theme/test/test-main.js | 10 - .../source/examples/toolbar-api/data/favicon.ico | Bin 15086 -> 0 bytes .../source/examples/toolbar-api/data/index.html | 21 - addon-sdk/source/examples/toolbar-api/lib/main.js | 48 - addon-sdk/source/examples/toolbar-api/package.json | 12 - .../source/examples/toolbar-api/test/test-main.js | 10 - .../source/examples/ui-button-apis/lib/main.js | 39 - .../source/examples/ui-button-apis/package.json | 10 - .../examples/ui-button-apis/tests/test-main.js | 29 - addon-sdk/source/gulpfile.js | 44 - addon-sdk/source/lib/dev/debuggee.js | 95 - addon-sdk/source/lib/dev/frame-script.js | 120 - addon-sdk/source/lib/dev/panel.js | 259 - addon-sdk/source/lib/dev/panel/view.js | 14 - addon-sdk/source/lib/dev/ports.js | 64 - addon-sdk/source/lib/dev/theme.js | 135 - addon-sdk/source/lib/dev/theme/hooks.js | 17 - addon-sdk/source/lib/dev/toolbox.js | 107 - addon-sdk/source/lib/dev/utils.js | 40 - addon-sdk/source/lib/dev/volcan.js | 3848 --------------- addon-sdk/source/lib/diffpatcher/.travis.yml | 5 - addon-sdk/source/lib/diffpatcher/History.md | 14 - addon-sdk/source/lib/diffpatcher/License.md | 18 - addon-sdk/source/lib/diffpatcher/Readme.md | 70 - addon-sdk/source/lib/diffpatcher/diff.js | 45 - addon-sdk/source/lib/diffpatcher/index.js | 5 - addon-sdk/source/lib/diffpatcher/package.json | 54 - addon-sdk/source/lib/diffpatcher/patch.js | 21 - addon-sdk/source/lib/diffpatcher/rebase.js | 36 - addon-sdk/source/lib/diffpatcher/test/common.js | 3 - addon-sdk/source/lib/diffpatcher/test/diff.js | 59 - addon-sdk/source/lib/diffpatcher/test/index.js | 14 - addon-sdk/source/lib/diffpatcher/test/patch.js | 83 - addon-sdk/source/lib/diffpatcher/test/tap.js | 3 - .../source/lib/framescript/FrameScriptManager.jsm | 27 - addon-sdk/source/lib/framescript/content.jsm | 94 - addon-sdk/source/lib/framescript/context-menu.js | 215 - addon-sdk/source/lib/framescript/manager.js | 26 - addon-sdk/source/lib/framescript/util.js | 25 - addon-sdk/source/lib/index.js | 3 - addon-sdk/source/lib/jetpack-id/index.js | 53 - addon-sdk/source/lib/jetpack-id/package.json | 28 - addon-sdk/source/lib/method/.travis.yml | 5 - addon-sdk/source/lib/method/History.md | 55 - addon-sdk/source/lib/method/License.md | 18 - addon-sdk/source/lib/method/Readme.md | 117 - addon-sdk/source/lib/method/core.js | 225 - addon-sdk/source/lib/method/package.json | 41 - addon-sdk/source/lib/method/test/browser.js | 20 - addon-sdk/source/lib/method/test/common.js | 272 - .../source/lib/mozilla-toolkit-versioning/index.js | 112 - .../lib/mozilla-toolkit-versioning/lib/utils.js | 15 - .../lib/mozilla-toolkit-versioning/package.json | 21 - addon-sdk/source/lib/node/os.js | 90 - addon-sdk/source/lib/sdk/addon/bootstrap.js | 182 - addon-sdk/source/lib/sdk/addon/events.js | 56 - addon-sdk/source/lib/sdk/addon/host.js | 12 - addon-sdk/source/lib/sdk/addon/installer.js | 121 - addon-sdk/source/lib/sdk/addon/manager.js | 18 - addon-sdk/source/lib/sdk/addon/runner.js | 180 - addon-sdk/source/lib/sdk/addon/window.js | 66 - addon-sdk/source/lib/sdk/base64.js | 47 - addon-sdk/source/lib/sdk/browser/events.js | 20 - addon-sdk/source/lib/sdk/clipboard.js | 337 -- addon-sdk/source/lib/sdk/console/plain-text.js | 78 - addon-sdk/source/lib/sdk/console/traceback.js | 86 - addon-sdk/source/lib/sdk/content/content-worker.js | 305 -- addon-sdk/source/lib/sdk/content/content.js | 17 - addon-sdk/source/lib/sdk/content/context-menu.js | 408 -- addon-sdk/source/lib/sdk/content/events.js | 57 - addon-sdk/source/lib/sdk/content/l10n-html.js | 133 - addon-sdk/source/lib/sdk/content/loader.js | 74 - addon-sdk/source/lib/sdk/content/mod.js | 68 - addon-sdk/source/lib/sdk/content/page-mod.js | 236 - addon-sdk/source/lib/sdk/content/page-worker.js | 154 - addon-sdk/source/lib/sdk/content/sandbox.js | 426 -- addon-sdk/source/lib/sdk/content/sandbox/events.js | 12 - addon-sdk/source/lib/sdk/content/tab-events.js | 58 - addon-sdk/source/lib/sdk/content/thumbnail.js | 51 - addon-sdk/source/lib/sdk/content/utils.js | 105 - addon-sdk/source/lib/sdk/content/worker-child.js | 158 - addon-sdk/source/lib/sdk/content/worker.js | 180 - addon-sdk/source/lib/sdk/context-menu.js | 1188 ----- addon-sdk/source/lib/sdk/context-menu/context.js | 147 - addon-sdk/source/lib/sdk/context-menu/core.js | 384 -- addon-sdk/source/lib/sdk/context-menu/readers.js | 112 - addon-sdk/source/lib/sdk/context-menu@2.js | 32 - addon-sdk/source/lib/sdk/core/disposable.js | 186 - addon-sdk/source/lib/sdk/core/heritage.js | 184 - addon-sdk/source/lib/sdk/core/namespace.js | 43 - addon-sdk/source/lib/sdk/core/observer.js | 89 - addon-sdk/source/lib/sdk/core/promise.js | 118 - addon-sdk/source/lib/sdk/core/reference.js | 29 - addon-sdk/source/lib/sdk/deprecated/api-utils.js | 197 - .../source/lib/sdk/deprecated/events/assembler.js | 54 - addon-sdk/source/lib/sdk/deprecated/sync-worker.js | 288 -- .../source/lib/sdk/deprecated/unit-test-finder.js | 199 - addon-sdk/source/lib/sdk/deprecated/unit-test.js | 584 --- .../source/lib/sdk/deprecated/window-utils.js | 193 - addon-sdk/source/lib/sdk/dom/events-shimmed.js | 18 - addon-sdk/source/lib/sdk/dom/events.js | 192 - addon-sdk/source/lib/sdk/dom/events/keys.js | 63 - addon-sdk/source/lib/sdk/event/chrome.js | 65 - addon-sdk/source/lib/sdk/event/core.js | 193 - addon-sdk/source/lib/sdk/event/dom.js | 78 - addon-sdk/source/lib/sdk/event/target.js | 74 - addon-sdk/source/lib/sdk/event/utils.js | 328 -- addon-sdk/source/lib/sdk/frame/hidden-frame.js | 115 - addon-sdk/source/lib/sdk/frame/utils.js | 94 - addon-sdk/source/lib/sdk/fs/path.js | 500 -- addon-sdk/source/lib/sdk/hotkeys.js | 40 - addon-sdk/source/lib/sdk/indexed-db.js | 79 - addon-sdk/source/lib/sdk/input/browser.js | 73 - addon-sdk/source/lib/sdk/input/customizable-ui.js | 28 - addon-sdk/source/lib/sdk/input/frame.js | 85 - addon-sdk/source/lib/sdk/input/system.js | 113 - addon-sdk/source/lib/sdk/io/buffer.js | 351 -- addon-sdk/source/lib/sdk/io/byte-streams.js | 104 - addon-sdk/source/lib/sdk/io/file.js | 196 - addon-sdk/source/lib/sdk/io/fs.js | 984 ---- addon-sdk/source/lib/sdk/io/stream.js | 440 -- addon-sdk/source/lib/sdk/io/text-streams.js | 235 - addon-sdk/source/lib/sdk/keyboard/hotkeys.js | 110 - addon-sdk/source/lib/sdk/keyboard/observer.js | 58 - addon-sdk/source/lib/sdk/keyboard/utils.js | 189 - addon-sdk/source/lib/sdk/l10n.js | 91 - addon-sdk/source/lib/sdk/l10n/core.js | 9 - addon-sdk/source/lib/sdk/l10n/html.js | 32 - addon-sdk/source/lib/sdk/l10n/json/core.js | 36 - addon-sdk/source/lib/sdk/l10n/loader.js | 70 - addon-sdk/source/lib/sdk/l10n/locale.js | 127 - addon-sdk/source/lib/sdk/l10n/plural-rules.js | 407 -- addon-sdk/source/lib/sdk/l10n/prefs.js | 51 - addon-sdk/source/lib/sdk/l10n/properties/core.js | 87 - addon-sdk/source/lib/sdk/lang/functional.js | 47 - .../source/lib/sdk/lang/functional/concurrent.js | 110 - addon-sdk/source/lib/sdk/lang/functional/core.js | 290 -- .../source/lib/sdk/lang/functional/helpers.js | 29 - addon-sdk/source/lib/sdk/lang/type.js | 388 -- addon-sdk/source/lib/sdk/lang/weak-set.js | 75 - addon-sdk/source/lib/sdk/loader/cuddlefish.js | 102 - addon-sdk/source/lib/sdk/loader/sandbox.js | 74 - addon-sdk/source/lib/sdk/messaging.js | 12 - addon-sdk/source/lib/sdk/model/core.js | 23 - addon-sdk/source/lib/sdk/net/url.js | 94 - addon-sdk/source/lib/sdk/net/xhr.js | 36 - addon-sdk/source/lib/sdk/notifications.js | 112 - addon-sdk/source/lib/sdk/output/system.js | 71 - addon-sdk/source/lib/sdk/page-mod.js | 190 - addon-sdk/source/lib/sdk/page-mod/match-pattern.js | 10 - addon-sdk/source/lib/sdk/page-worker.js | 194 - addon-sdk/source/lib/sdk/panel.js | 427 -- addon-sdk/source/lib/sdk/panel/events.js | 27 - addon-sdk/source/lib/sdk/panel/utils.js | 451 -- addon-sdk/source/lib/sdk/passwords.js | 61 - addon-sdk/source/lib/sdk/passwords/utils.js | 107 - addon-sdk/source/lib/sdk/places/bookmarks.js | 395 -- addon-sdk/source/lib/sdk/places/contract.js | 73 - addon-sdk/source/lib/sdk/places/events.js | 128 - addon-sdk/source/lib/sdk/places/favicon.js | 49 - addon-sdk/source/lib/sdk/places/history.js | 65 - .../source/lib/sdk/places/host/host-bookmarks.js | 238 - addon-sdk/source/lib/sdk/places/host/host-query.js | 179 - addon-sdk/source/lib/sdk/places/host/host-tags.js | 92 - addon-sdk/source/lib/sdk/places/utils.js | 268 - addon-sdk/source/lib/sdk/platform/xpcom.js | 241 - .../source/lib/sdk/preferences/event-target.js | 61 - .../source/lib/sdk/preferences/native-options.js | 193 - addon-sdk/source/lib/sdk/preferences/service.js | 137 - addon-sdk/source/lib/sdk/preferences/utils.js | 42 - addon-sdk/source/lib/sdk/private-browsing.js | 12 - addon-sdk/source/lib/sdk/private-browsing/utils.js | 54 - addon-sdk/source/lib/sdk/querystring.js | 121 - addon-sdk/source/lib/sdk/remote/child.js | 284 -- addon-sdk/source/lib/sdk/remote/core.js | 8 - addon-sdk/source/lib/sdk/remote/parent.js | 338 -- addon-sdk/source/lib/sdk/remote/utils.js | 39 - addon-sdk/source/lib/sdk/request.js | 248 - addon-sdk/source/lib/sdk/selection.js | 470 -- addon-sdk/source/lib/sdk/self.js | 61 - addon-sdk/source/lib/sdk/simple-prefs.js | 26 - addon-sdk/source/lib/sdk/simple-storage.js | 235 - addon-sdk/source/lib/sdk/stylesheet/style.js | 71 - addon-sdk/source/lib/sdk/stylesheet/utils.js | 75 - addon-sdk/source/lib/sdk/system.js | 172 - addon-sdk/source/lib/sdk/system/child_process.js | 332 -- .../lib/sdk/system/child_process/subprocess.js | 186 - addon-sdk/source/lib/sdk/system/environment.js | 33 - addon-sdk/source/lib/sdk/system/events-shimmed.js | 16 - addon-sdk/source/lib/sdk/system/events.js | 181 - addon-sdk/source/lib/sdk/system/globals.js | 46 - addon-sdk/source/lib/sdk/system/process.js | 62 - addon-sdk/source/lib/sdk/system/runtime.js | 28 - addon-sdk/source/lib/sdk/system/unload.js | 104 - addon-sdk/source/lib/sdk/system/xul-app.js | 12 - addon-sdk/source/lib/sdk/system/xul-app.jsm | 242 - addon-sdk/source/lib/sdk/tab/events.js | 74 - addon-sdk/source/lib/sdk/tabs.js | 17 - addon-sdk/source/lib/sdk/tabs/common.js | 34 - addon-sdk/source/lib/sdk/tabs/events.js | 39 - addon-sdk/source/lib/sdk/tabs/helpers.js | 22 - addon-sdk/source/lib/sdk/tabs/namespace.js | 10 - addon-sdk/source/lib/sdk/tabs/observer.js | 113 - addon-sdk/source/lib/sdk/tabs/tab-fennec.js | 249 - addon-sdk/source/lib/sdk/tabs/tab-firefox.js | 353 -- addon-sdk/source/lib/sdk/tabs/tab.js | 24 - addon-sdk/source/lib/sdk/tabs/tabs-firefox.js | 135 - addon-sdk/source/lib/sdk/tabs/utils.js | 370 -- addon-sdk/source/lib/sdk/tabs/worker.js | 17 - addon-sdk/source/lib/sdk/test.js | 114 - addon-sdk/source/lib/sdk/test/assert.js | 366 -- addon-sdk/source/lib/sdk/test/harness.js | 645 --- addon-sdk/source/lib/sdk/test/httpd.js | 6 - addon-sdk/source/lib/sdk/test/loader.js | 123 - addon-sdk/source/lib/sdk/test/memory.js | 11 - addon-sdk/source/lib/sdk/test/options.js | 23 - addon-sdk/source/lib/sdk/test/runner.js | 131 - addon-sdk/source/lib/sdk/test/utils.js | 199 - addon-sdk/source/lib/sdk/timers.js | 105 - addon-sdk/source/lib/sdk/ui.js | 17 - addon-sdk/source/lib/sdk/ui/button/action.js | 114 - addon-sdk/source/lib/sdk/ui/button/contract.js | 73 - addon-sdk/source/lib/sdk/ui/button/toggle.js | 127 - addon-sdk/source/lib/sdk/ui/button/view.js | 243 - addon-sdk/source/lib/sdk/ui/button/view/events.js | 18 - addon-sdk/source/lib/sdk/ui/component.js | 182 - addon-sdk/source/lib/sdk/ui/frame.js | 16 - addon-sdk/source/lib/sdk/ui/frame/model.js | 154 - addon-sdk/source/lib/sdk/ui/frame/view.html | 18 - addon-sdk/source/lib/sdk/ui/frame/view.js | 150 - addon-sdk/source/lib/sdk/ui/id.js | 27 - addon-sdk/source/lib/sdk/ui/sidebar.js | 311 -- addon-sdk/source/lib/sdk/ui/sidebar/actions.js | 10 - addon-sdk/source/lib/sdk/ui/sidebar/contract.js | 27 - addon-sdk/source/lib/sdk/ui/sidebar/namespace.js | 15 - addon-sdk/source/lib/sdk/ui/sidebar/utils.js | 8 - addon-sdk/source/lib/sdk/ui/sidebar/view.js | 214 - addon-sdk/source/lib/sdk/ui/state.js | 239 - addon-sdk/source/lib/sdk/ui/state/events.js | 18 - addon-sdk/source/lib/sdk/ui/toolbar.js | 16 - addon-sdk/source/lib/sdk/ui/toolbar/model.js | 151 - addon-sdk/source/lib/sdk/ui/toolbar/view.js | 248 - addon-sdk/source/lib/sdk/uri/resource.js | 37 - addon-sdk/source/lib/sdk/url.js | 349 -- addon-sdk/source/lib/sdk/url/utils.js | 29 - addon-sdk/source/lib/sdk/util/array.js | 123 - addon-sdk/source/lib/sdk/util/collection.js | 115 - addon-sdk/source/lib/sdk/util/contract.js | 55 - addon-sdk/source/lib/sdk/util/deprecate.js | 40 - addon-sdk/source/lib/sdk/util/dispatcher.js | 54 - addon-sdk/source/lib/sdk/util/list.js | 90 - addon-sdk/source/lib/sdk/util/match-pattern.js | 113 - addon-sdk/source/lib/sdk/util/object.js | 104 - addon-sdk/source/lib/sdk/util/rules.js | 53 - addon-sdk/source/lib/sdk/util/sequence.js | 593 --- addon-sdk/source/lib/sdk/util/uuid.js | 19 - addon-sdk/source/lib/sdk/view/core.js | 26 - addon-sdk/source/lib/sdk/webextension.js | 43 - addon-sdk/source/lib/sdk/window/browser.js | 54 - addon-sdk/source/lib/sdk/window/events.js | 68 - addon-sdk/source/lib/sdk/window/helpers.js | 81 - addon-sdk/source/lib/sdk/window/namespace.js | 6 - addon-sdk/source/lib/sdk/window/utils.js | 460 -- addon-sdk/source/lib/sdk/windows.js | 32 - addon-sdk/source/lib/sdk/windows/fennec.js | 83 - addon-sdk/source/lib/sdk/windows/firefox.js | 224 - addon-sdk/source/lib/sdk/windows/observer.js | 53 - addon-sdk/source/lib/sdk/windows/tabs-fennec.js | 172 - addon-sdk/source/lib/sdk/worker/utils.js | 19 - addon-sdk/source/lib/sdk/zip/utils.js | 16 - addon-sdk/source/lib/test.js | 11 - addon-sdk/source/lib/toolkit/loader.js | 1147 ----- addon-sdk/source/lib/toolkit/require.js | 91 - addon-sdk/source/mapping.json | 71 - addon-sdk/source/modules/system/Startup.js | 57 - addon-sdk/source/modules/system/moz.build | 9 - addon-sdk/source/package.json | 39 - addon-sdk/source/python-lib/cuddlefish/__init__.py | 959 ---- addon-sdk/source/python-lib/cuddlefish/_version.py | 174 - addon-sdk/source/python-lib/cuddlefish/bunch.py | 34 - addon-sdk/source/python-lib/cuddlefish/manifest.py | 807 --- .../cuddlefish/mobile-utils/bootstrap.js | 48 - .../python-lib/cuddlefish/mobile-utils/install.rdf | 39 - .../source/python-lib/cuddlefish/packaging.py | 463 -- .../source/python-lib/cuddlefish/preflight.py | 77 - addon-sdk/source/python-lib/cuddlefish/prefs.py | 239 - .../python-lib/cuddlefish/property_parser.py | 111 - addon-sdk/source/python-lib/cuddlefish/rdf.py | 214 - addon-sdk/source/python-lib/cuddlefish/runner.py | 767 --- .../source/python-lib/cuddlefish/templates.py | 32 - .../source/python-lib/cuddlefish/tests/__init__.py | 52 - .../packages/explicit-icon/explicit-icon.png | 0 .../packages/explicit-icon/explicit-icon64.png | 0 .../packages/explicit-icon/lib/main.js | 4 - .../packages/explicit-icon/package.json | 5 - .../packages/implicit-icon/icon.png | 0 .../packages/implicit-icon/icon64.png | 0 .../packages/implicit-icon/lib/main.js | 4 - .../packages/implicit-icon/package.json | 3 - .../bug-588119-files/packages/no-icon/lib/main.js | 4 - .../bug-588119-files/packages/no-icon/package.json | 3 - .../packages/bar/lib/bar-loader.js | 4 - .../bug-588661-files/packages/bar/package.json | 3 - .../packages/foo/lib/foo-loader.js | 4 - .../bug-588661-files/packages/foo/package.json | 4 - .../tests/bug-611495-files/jspath-one/docs/main.md | 5 - .../tests/bug-611495-files/jspath-one/lib/main.js | 8 - .../tests/bug-611495-files/jspath-one/package.json | 5 - .../packages/commonjs-naming/doc/foo.md | 5 - .../packages/commonjs-naming/lib/foo-loader.js | 5 - .../packages/commonjs-naming/package.json | 3 - .../packages/commonjs-naming/test/test-foo.js | 7 - .../packages/original-naming/docs/foo.md | 5 - .../packages/original-naming/lib/foo-loader.js | 5 - .../packages/original-naming/package.json | 3 - .../packages/original-naming/tests/test-foo.js | 7 - .../packages/default-lib/doc/foo.md | 5 - .../packages/default-lib/lib/foo.js | 5 - .../packages/default-lib/lib/loader.js | 5 - .../packages/default-lib/package.json | 3 - .../packages/default-lib/test/test-foo.js | 7 - .../packages/default-locale/locale/emptyFolder | 0 .../packages/default-locale/package.json | 1 - .../packages/default-root/doc/foo.md | 5 - .../bug-652227-files/packages/default-root/foo.js | 5 - .../packages/default-root/loader.js | 5 - .../packages/default-root/package.json | 3 - .../packages/default-root/test/test-foo.js | 7 - .../packages/explicit-dir-lib/alt-lib/foo.js | 5 - .../packages/explicit-dir-lib/alt-lib/loader.js | 5 - .../packages/explicit-dir-lib/doc/foo.md | 5 - .../packages/explicit-dir-lib/package.json | 4 - .../packages/explicit-dir-lib/test/test-foo.js | 7 - .../packages/explicit-lib/alt2-lib/foo.js | 5 - .../packages/explicit-lib/alt2-lib/loader.js | 5 - .../packages/explicit-lib/doc/foo.md | 5 - .../packages/explicit-lib/package.json | 4 - .../packages/explicit-lib/test/test-foo.js | 7 - .../packages/extra-options/docs/main.md | 5 - .../packages/extra-options/lib/main.js | 8 - .../packages/extra-options/package.json | 6 - .../tests/bug-715340-files/pkg-1-pack/package.json | 10 - .../bug-715340-files/pkg-2-unpack/package.json | 10 - .../tests/bug-715340-files/pkg-3-pack/package.json | 9 - .../tests/bug-906359-files/fullName/package.json | 9 - .../tests/bug-906359-files/none/package.json | 9 - .../tests/bug-906359-files/title/package.json | 9 - .../packages/foo/lib/bar-e10s-adapter.js | 11 - .../e10s-adapter-files/packages/foo/lib/bar.js | 5 - .../e10s-adapter-files/packages/foo/lib/foo.js | 5 - .../e10s-adapter-files/packages/foo/package.json | 1 - .../cuddlefish/tests/linker-files/five/lib/main.js | 5 - .../tests/linker-files/five/package.json | 3 - .../linker-files/four-deps/four-a/lib/misc.js | 5 - .../linker-files/four-deps/four-a/package.json | 4 - .../linker-files/four-deps/four-a/topfiles/main.js | 5 - .../cuddlefish/tests/linker-files/four/lib/main.js | 5 - .../tests/linker-files/four/package.json | 3 - .../cuddlefish/tests/linker-files/one/lib/main.js | 9 - .../tests/linker-files/one/lib/subdir/three.js | 6 - .../cuddlefish/tests/linker-files/one/lib/two.js | 8 - .../cuddlefish/tests/linker-files/one/package.json | 4 - .../tests/linker-files/seven/data/text.data | 1 - .../tests/linker-files/seven/lib/main.js | 6 - .../tests/linker-files/seven/lib/unused.js | 5 - .../tests/linker-files/seven/package.json | 4 - .../tests/linker-files/six/lib/unused.js | 5 - .../cuddlefish/tests/linker-files/six/package.json | 3 - .../tests/linker-files/six/unreachable.js | 5 - .../linker-files/three-deps/three-a/lib/main.js | 8 - .../three-deps/three-a/lib/subdir/subfile.js | 5 - .../linker-files/three-deps/three-a/lib/unused.js | 5 - .../three-deps/three-a/locale/fr-FR.properties | 5 - .../linker-files/three-deps/three-a/package.json | 3 - .../linker-files/three-deps/three-b/lib/main.js | 5 - .../three-deps/three-b/locale/fr-FR.properties | 6 - .../linker-files/three-deps/three-b/package.json | 3 - .../linker-files/three-deps/three-c/lib/main.js | 5 - .../linker-files/three-deps/three-c/lib/sub/foo.js | 6 - .../three-deps/three-c/locale/fr-FR.properties | 9 - .../linker-files/three-deps/three-c/package.json | 3 - .../tests/linker-files/three/data/msg.txt | 1 - .../linker-files/three/data/subdir/submsg.txt | 1 - .../tests/linker-files/three/lib/main.js | 8 - .../tests/linker-files/three/package.json | 3 - .../tests/linker-files/three/tests/nontest.js | 5 - .../tests/linker-files/three/tests/test-one.js | 5 - .../tests/linker-files/three/tests/test-two.js | 5 - .../static-files/packages/aardvark/doc/main.md | 0 .../static-files/packages/aardvark/lib/ignore_me | 3 - .../static-files/packages/aardvark/lib/main.js | 8 - .../aardvark/lib/surprise.js/ignore_me_too | 2 - .../static-files/packages/aardvark/package.json | 7 - .../packages/anteater_files/lib/main.js | 8 - .../packages/anteater_files/package.json | 8 - .../static-files/packages/api-utils/lib/loader.js | 7 - .../static-files/packages/api-utils/package.json | 5 - .../packages/barbeque/lib/bar-module.js | 7 - .../static-files/packages/barbeque/package.json | 4 - .../static-files/packages/minimal/lib/main.js | 8 - .../static-files/packages/minimal/package.json | 4 - .../packages/third_party/docs/third_party.md | 1 - .../packages/third_party/lib/third-party.js | 8 - .../static-files/packages/third_party/package.json | 7 - .../xpi-template/components/harness.js | 8 - .../python-lib/cuddlefish/tests/test_init.py | 211 - .../python-lib/cuddlefish/tests/test_licenses.py | 100 - .../python-lib/cuddlefish/tests/test_linker.py | 247 - .../python-lib/cuddlefish/tests/test_manifest.py | 257 - .../python-lib/cuddlefish/tests/test_packaging.py | 117 - .../python-lib/cuddlefish/tests/test_preflight.py | 147 - .../cuddlefish/tests/test_property_parser.py | 93 - .../source/python-lib/cuddlefish/tests/test_rdf.py | 54 - .../python-lib/cuddlefish/tests/test_runner.py | 27 - .../python-lib/cuddlefish/tests/test_util.py | 22 - .../python-lib/cuddlefish/tests/test_version.py | 28 - .../source/python-lib/cuddlefish/tests/test_xpi.py | 310 -- addon-sdk/source/python-lib/cuddlefish/util.py | 23 - .../python-lib/cuddlefish/version_comparator.py | 206 - addon-sdk/source/python-lib/cuddlefish/xpi.py | 169 - addon-sdk/source/python-lib/jetpack_sdk_env.py | 66 - addon-sdk/source/python-lib/mozrunner/__init__.py | 694 --- .../source/python-lib/mozrunner/killableprocess.py | 329 -- addon-sdk/source/python-lib/mozrunner/qijo.py | 166 - .../source/python-lib/mozrunner/winprocess.py | 379 -- addon-sdk/source/python-lib/mozrunner/wpk.py | 80 - .../source/python-lib/plural-rules-generator.py | 185 - addon-sdk/source/python-lib/simplejson/LICENSE.txt | 19 - addon-sdk/source/python-lib/simplejson/__init__.py | 376 -- addon-sdk/source/python-lib/simplejson/decoder.py | 343 -- addon-sdk/source/python-lib/simplejson/encoder.py | 395 -- addon-sdk/source/python-lib/simplejson/scanner.py | 67 - addon-sdk/source/python-lib/simplejson/tool.py | 44 - .../source/test/addons/addon-manager/lib/main.js | 8 - .../test/addons/addon-manager/lib/test-main.js | 12 - .../source/test/addons/addon-manager/package.json | 7 - addon-sdk/source/test/addons/author-email/main.js | 14 - .../source/test/addons/author-email/package.json | 6 - .../source/test/addons/child_process/index.js | 39 - .../source/test/addons/child_process/package.json | 5 - .../source/test/addons/chrome/chrome.manifest | 5 - .../addons/chrome/chrome/content/new-window.xul | 4 - .../test/addons/chrome/chrome/content/panel.html | 10 - .../chrome/locale/en-US/description.properties | 1 - .../chrome/locale/ja-JP/description.properties | 1 - .../test/addons/chrome/chrome/skin/style.css | 4 - addon-sdk/source/test/addons/chrome/data/panel.js | 10 - addon-sdk/source/test/addons/chrome/main.js | 97 - addon-sdk/source/test/addons/chrome/package.json | 5 - .../test/addons/content-permissions/httpd.js | 5211 ------------------- .../source/test/addons/content-permissions/main.js | 89 - .../test/addons/content-permissions/package.json | 8 - .../content-script-messages-latency/httpd.js | 5211 ------------------- .../addons/content-script-messages-latency/main.js | 90 - .../content-script-messages-latency/package.json | 6 - addon-sdk/source/test/addons/contributors/main.js | 19 - .../source/test/addons/contributors/package.json | 6 - addon-sdk/source/test/addons/curly-id/lib/main.js | 29 - addon-sdk/source/test/addons/curly-id/package.json | 13 - addon-sdk/source/test/addons/developers/main.js | 19 - .../source/test/addons/developers/package.json | 8 - .../e10s-content/data/test-contentScriptFile.js | 5 - .../addons/e10s-content/data/test-page-worker.html | 13 - .../addons/e10s-content/data/test-page-worker.js | 29 - .../source/test/addons/e10s-content/data/test.html | 13 - .../test/addons/e10s-content/lib/fixtures.js | 8 - .../source/test/addons/e10s-content/lib/httpd.js | 5212 -------------------- .../source/test/addons/e10s-content/lib/main.js | 22 - .../addons/e10s-content/lib/test-content-script.js | 845 ---- .../addons/e10s-content/lib/test-content-worker.js | 1127 ----- .../addons/e10s-content/lib/test-page-worker.js | 524 -- .../source/test/addons/e10s-content/package.json | 11 - .../addons/e10s-l10n/data/test-localization.html | 29 - .../test/addons/e10s-l10n/locale/en.properties | 38 - .../test/addons/e10s-l10n/locale/eo.properties | 5 - .../test/addons/e10s-l10n/locale/fr-FR.properties | 14 - addon-sdk/source/test/addons/e10s-l10n/main.js | 289 -- .../source/test/addons/e10s-l10n/package.json | 5 - addon-sdk/source/test/addons/e10s-remote/main.js | 578 --- .../source/test/addons/e10s-remote/package.json | 9 - .../test/addons/e10s-remote/remote-module.js | 129 - addon-sdk/source/test/addons/e10s-remote/utils.js | 110 - addon-sdk/source/test/addons/e10s-tabs/lib/main.js | 23 - .../e10s-tabs/lib/private-browsing/helper.js | 91 - .../test/addons/e10s-tabs/lib/test-tab-events.js | 238 - .../test/addons/e10s-tabs/lib/test-tab-observer.js | 46 - .../test/addons/e10s-tabs/lib/test-tab-utils.js | 67 - .../source/test/addons/e10s-tabs/lib/test-tab.js | 87 - .../source/test/addons/e10s-tabs/package.json | 11 - addon-sdk/source/test/addons/e10s/lib/main.js | 65 - addon-sdk/source/test/addons/e10s/package.json | 10 - .../test/addons/embedded-webextension/main.js | 159 - .../test/addons/embedded-webextension/package.json | 6 - .../webextension/background-page.js | 10 - .../webextension/content-script.js | 10 - .../webextension/manifest.json | 18 - addon-sdk/source/test/addons/jetpack-addon.ini | 48 - .../l10n-properties/app-extension/application.ini | 11 - .../l10n-properties/app-extension/bootstrap.js | 339 -- .../l10n-properties/app-extension/install.rdf | 33 - .../app-extension/locale/en-GB.properties | 28 - .../app-extension/locale/en-US.properties | 22 - .../app-extension/locale/eo.properties | 5 - .../app-extension/locale/fr-FR.properties | 14 - .../l10n-properties/data/test-localization.html | 24 - .../source/test/addons/l10n-properties/main.js | 202 - .../test/addons/l10n-properties/package.json | 6 - .../test/addons/l10n/data/test-localization.html | 29 - .../source/test/addons/l10n/locale/en.properties | 38 - .../source/test/addons/l10n/locale/eo.properties | 5 - .../test/addons/l10n/locale/fr-FR.properties | 14 - addon-sdk/source/test/addons/l10n/main.js | 289 -- addon-sdk/source/test/addons/l10n/package.json | 5 - .../source/test/addons/layout-change/lib/main.js | 15 - .../layout-change/lib/test-cuddlefish-loader.js | 164 - .../layout-change/lib/test-toolkit-loader.js | 10 - .../source/test/addons/layout-change/package.json | 7 - addon-sdk/source/test/addons/main/main.js | 37 - addon-sdk/source/test/addons/main/package.json | 5 - .../test/addons/name-in-numbers-plus/index.js | 12 - .../test/addons/name-in-numbers-plus/package.json | 6 - .../source/test/addons/name-in-numbers/index.js | 12 - .../test/addons/name-in-numbers/package.json | 6 - addon-sdk/source/test/addons/packaging/main.js | 57 - .../source/test/addons/packaging/package.json | 6 - addon-sdk/source/test/addons/packed/main.js | 20 - addon-sdk/source/test/addons/packed/package.json | 6 - .../addons/page-mod-debugger-post/data/index.html | 11 - .../addons/page-mod-debugger-post/data/script.js | 16 - .../test/addons/page-mod-debugger-post/main.js | 136 - .../addons/page-mod-debugger-post/package.json | 6 - .../addons/page-mod-debugger-pre/data/index.html | 11 - .../addons/page-mod-debugger-pre/data/script.js | 16 - .../test/addons/page-mod-debugger-pre/main.js | 134 - .../test/addons/page-mod-debugger-pre/package.json | 6 - .../source/test/addons/page-worker/data/page.html | 9 - .../source/test/addons/page-worker/data/page.js | 13 - addon-sdk/source/test/addons/page-worker/main.js | 53 - .../source/test/addons/page-worker/package.json | 3 - .../test/addons/places/lib/favicon-helpers.js | 54 - addon-sdk/source/test/addons/places/lib/httpd.js | 5211 ------------------- addon-sdk/source/test/addons/places/lib/main.js | 27 - .../source/test/addons/places/lib/places-helper.js | 239 - .../addons/places/lib/test-places-bookmarks.js | 948 ---- .../test/addons/places/lib/test-places-events.js | 328 -- .../test/addons/places/lib/test-places-favicon.js | 242 - .../test/addons/places/lib/test-places-history.js | 244 - .../test/addons/places/lib/test-places-host.js | 301 -- .../test/addons/places/lib/test-places-utils.js | 78 - addon-sdk/source/test/addons/places/package.json | 5 - .../test/addons/predefined-id-with-at/lib/main.js | 32 - .../test/addons/predefined-id-with-at/package.json | 13 - .../test/addons/preferences-branch/lib/main.js | 28 - .../test/addons/preferences-branch/package.json | 14 - .../test/addons/private-browsing-supported/main.js | 28 - .../addons/private-browsing-supported/package.json | 8 - .../private-browsing-supported/sidebar/utils.js | 67 - .../private-browsing-supported/test-page-mod.js | 119 - .../private-browsing-supported/test-panel.js | 99 - .../test-private-browsing.js | 111 - .../private-browsing-supported/test-selection.js | 447 -- .../private-browsing-supported/test-sidebar.js | 212 - .../addons/private-browsing-supported/test-tabs.js | 34 - .../private-browsing-supported/test-window-tabs.js | 75 - .../private-browsing-supported/test-windows.js | 240 - addon-sdk/source/test/addons/remote/main.js | 578 --- addon-sdk/source/test/addons/remote/package.json | 8 - .../source/test/addons/remote/remote-module.js | 129 - addon-sdk/source/test/addons/remote/utils.js | 110 - addon-sdk/source/test/addons/require/list.js | 6 - addon-sdk/source/test/addons/require/main.js | 87 - addon-sdk/source/test/addons/require/multiple/a.js | 5 - addon-sdk/source/test/addons/require/multiple/b.js | 5 - addon-sdk/source/test/addons/require/package.json | 8 - .../test/addons/require/packages/tabs/main.js | 5 - .../test/addons/require/packages/tabs/package.json | 3 - .../test/addons/require/packages/tabs/page-mod.js | 5 - .../source/test/addons/require/same-folder.js | 5 - .../test/addons/require/sub-folder/module.js | 5 - addon-sdk/source/test/addons/require/tabs.js | 5 - addon-sdk/source/test/addons/self/data/data.md | 1 - addon-sdk/source/test/addons/self/main.js | 23 - addon-sdk/source/test/addons/self/package.json | 5 - .../addons/simple-prefs-l10n/locale/en.properties | 5 - .../source/test/addons/simple-prefs-l10n/main.js | 65 - .../test/addons/simple-prefs-l10n/package.json | 10 - .../app-extension/application.ini | 11 - .../app-extension/bootstrap.js | 339 -- .../app-extension/defaults/preferences/prefs.js | 7 - .../app-extension/install.rdf | 34 - .../app-extension/options.xul | 5 - .../addons/simple-prefs-regression/lib/main.js | 94 - .../addons/simple-prefs-regression/package.json | 24 - .../source/test/addons/simple-prefs/lib/main.js | 109 - .../source/test/addons/simple-prefs/package.json | 32 - .../source/test/addons/standard-id/lib/main.js | 30 - .../source/test/addons/standard-id/package.json | 13 - .../test/addons/tab-close-on-startup/main.js | 31 - .../test/addons/tab-close-on-startup/package.json | 5 - .../test/addons/toolkit-require-reload/main.js | 77 - .../addons/toolkit-require-reload/package.json | 5 - addon-sdk/source/test/addons/translators/main.js | 20 - .../source/test/addons/translators/package.json | 8 - .../test/addons/unsafe-content-script/main.js | 68 - .../test/addons/unsafe-content-script/package.json | 8 - addon-sdk/source/test/buffers/test-read-types.js | 368 -- addon-sdk/source/test/buffers/test-write-types.js | 602 --- .../source/test/commonjs-test-adapter/asserts.js | 54 - addon-sdk/source/test/context-menu/framescript.js | 44 - addon-sdk/source/test/context-menu/test-helper.js | 539 -- addon-sdk/source/test/context-menu/util.js | 141 - addon-sdk/source/test/event/helpers.js | 112 - addon-sdk/source/test/fixtures.js | 41 - .../addon-install-unit-test@mozilla.com.xpi | Bin 5670 -> 0 bytes .../test/fixtures/addon-sdk/data/border-style.css | 4 - .../addon-sdk/data/test-contentScriptFile.js | 5 - .../fixtures/addon-sdk/data/test-page-worker.html | 13 - .../fixtures/addon-sdk/data/test-page-worker.js | 29 - .../source/test/fixtures/addon-sdk/data/test.html | 13 - addon-sdk/source/test/fixtures/addon/bootstrap.js | 9 - addon-sdk/source/test/fixtures/addon/index.js | 4 - addon-sdk/source/test/fixtures/addon/package.json | 5 - .../fixtures/bootstrap-addon/META-INF/manifest.mf | 17 - .../fixtures/bootstrap-addon/META-INF/mozilla.rsa | Bin 4191 -> 0 bytes .../fixtures/bootstrap-addon/META-INF/mozilla.sf | 4 - .../test/fixtures/bootstrap-addon/bootstrap.js | 10 - .../test/fixtures/bootstrap-addon/install.rdf | 27 - .../test/fixtures/bootstrap-addon/options.xul | 3 - addon-sdk/source/test/fixtures/bootstrap/utils.js | 52 - addon-sdk/source/test/fixtures/border-style.css | 4 - .../source/test/fixtures/child-process-scripts.js | 81 - .../fixtures/chrome-worker/addEventListener.js | 6 - .../source/test/fixtures/chrome-worker/jsctypes.js | 6 - .../source/test/fixtures/chrome-worker/onerror.js | 6 - .../test/fixtures/chrome-worker/onmessage.js | 8 - .../test/fixtures/chrome-worker/setTimeout.js | 8 - .../source/test/fixtures/chrome-worker/xhr.js | 11 - addon-sdk/source/test/fixtures/create_xpi.py | 15 - addon-sdk/source/test/fixtures/es5.js | 8 - addon-sdk/source/test/fixtures/include-file.css | 4 - addon-sdk/source/test/fixtures/index.html | 18 - .../source/test/fixtures/jsm-package/Test.jsm | 11 - .../source/test/fixtures/jsm-package/index.js | 46 - .../source/test/fixtures/jsm-package/package.json | 3 - addon-sdk/source/test/fixtures/loader/cycles/a.js | 7 - addon-sdk/source/test/fixtures/loader/cycles/b.js | 7 - addon-sdk/source/test/fixtures/loader/cycles/c.js | 7 - .../source/test/fixtures/loader/cycles/main.js | 14 - .../source/test/fixtures/loader/errors/boomer.js | 7 - .../source/test/fixtures/loader/errors/main.js | 9 - .../test/fixtures/loader/exceptions/boomer.js | 9 - .../source/test/fixtures/loader/exceptions/main.js | 11 - .../source/test/fixtures/loader/globals/main.js | 7 - .../source/test/fixtures/loader/json/invalid.json | 3 - .../source/test/fixtures/loader/json/manifest.json | 14 - .../source/test/fixtures/loader/json/mutation.json | 1 - .../test/fixtures/loader/json/nodotjson.json.js | 8 - .../source/test/fixtures/loader/json/test.json | 3 - .../source/test/fixtures/loader/json/test.json.js | 7 - addon-sdk/source/test/fixtures/loader/lazy/main.js | 9 - .../test/fixtures/loader/missing-twice/file.json | 1 - .../test/fixtures/loader/missing-twice/main.js | 32 - .../source/test/fixtures/loader/missing/main.js | 10 - addon-sdk/source/test/fixtures/loader/self/main.js | 8 - .../test/fixtures/loader/syntax-error/error.js | 11 - .../test/fixtures/loader/syntax-error/main.js | 10 - .../test/fixtures/loader/unsupported/fennec.js | 10 - .../test/fixtures/loader/unsupported/firefox.js | 10 - addon-sdk/source/test/fixtures/mofo_logo.SVG | 45 - addon-sdk/source/test/fixtures/moz.build | 22 - addon-sdk/source/test/fixtures/moz_favicon.ico | Bin 1406 -> 0 bytes .../test/fixtures/native-addon-test/dir/a.js | 5 - .../test/fixtures/native-addon-test/dir/a/index.js | 5 - .../test/fixtures/native-addon-test/dir/b.js | 6 - .../test/fixtures/native-addon-test/dir/c.js | 6 - .../test/fixtures/native-addon-test/dir/dummy.js | 6 - .../test/fixtures/native-addon-test/dir/test.jsm | 6 - .../fixtures/native-addon-test/expectedmap.json | 25 - .../test/fixtures/native-addon-test/index.js | 37 - .../fixtures/native-addon-test/newmodule/index.js | 5 - .../native-addon-test/newmodule/lib/file.js | 5 - .../native-addon-test/newmodule/package.json | 3 - .../test/fixtures/native-addon-test/package.json | 10 - .../test/fixtures/native-addon-test/utils/index.js | 7 - .../test/fixtures/native-overrides-test/ignore.js | 6 - .../test/fixtures/native-overrides-test/index.js | 19 - .../fixtures/native-overrides-test/lib/ignore.js | 6 - .../fixtures/native-overrides-test/lib/internal.js | 6 - .../fixtures/native-overrides-test/lib/tabs.js | 6 - .../fixtures/native-overrides-test/package.json | 18 - .../fixtures/preferences/curly-id/package.json | 14 - .../fixtures/preferences/no-prefs/package.json | 6 - .../preferences/preferences-branch/package.json | 14 - .../fixtures/preferences/simple-prefs/package.json | 75 - .../test/fixtures/sandbox-complex-character.js | 5 - addon-sdk/source/test/fixtures/sandbox-normal.js | 7 - .../test/fixtures/test-addon-extras-window.html | 21 - .../source/test/fixtures/test-addon-extras.html | 31 - .../source/test/fixtures/test-contentScriptFile.js | 5 - .../source/test/fixtures/test-context-menu.js | 5 - .../test/fixtures/test-iframe-postmessage.html | 23 - addon-sdk/source/test/fixtures/test-iframe.html | 12 - addon-sdk/source/test/fixtures/test-iframe.js | 16 - .../source/test/fixtures/test-message-manager.js | 6 - addon-sdk/source/test/fixtures/test-net-url.txt | 1 - addon-sdk/source/test/fixtures/test-page-mod.html | 12 - .../test/fixtures/test-sidebar-addon-global.html | 15 - .../test/fixtures/test-trusted-document.html | 20 - addon-sdk/source/test/fixtures/test.html | 25 - addon-sdk/source/test/fixtures/testLocalXhr.json | 1 - .../test/framescript-manager/frame-script.js | 13 - addon-sdk/source/test/framescript-manager/pong.js | 7 - .../source/test/framescript-util/frame-script.js | 21 - addon-sdk/source/test/jetpack-package.ini | 179 - addon-sdk/source/test/leak/jetpack-package.ini | 7 - addon-sdk/source/test/leak/leak-utils.js | 80 - .../test/leak/test-leak-event-dom-closed-window.js | 29 - addon-sdk/source/test/leak/test-leak-tab-events.js | 46 - .../source/test/leak/test-leak-window-events.js | 65 - addon-sdk/source/test/lib/httpd.js | 5212 -------------------- addon-sdk/source/test/loader/b2g.js | 41 - addon-sdk/source/test/loader/fixture.js | 7 - addon-sdk/source/test/loader/user-global.js | 11 - addon-sdk/source/test/modules/add.js | 9 - addon-sdk/source/test/modules/async1.js | 14 - addon-sdk/source/test/modules/async2.js | 8 - .../source/test/modules/badExportAndReturn.js | 10 - addon-sdk/source/test/modules/badFirst.js | 9 - addon-sdk/source/test/modules/badSecond.js | 8 - addon-sdk/source/test/modules/blue.js | 9 - addon-sdk/source/test/modules/castor.js | 10 - addon-sdk/source/test/modules/cheetah.js | 9 - addon-sdk/source/test/modules/color.js | 7 - addon-sdk/source/test/modules/dupe.js | 15 - addon-sdk/source/test/modules/dupeNested.js | 15 - addon-sdk/source/test/modules/dupeSetExports.js | 8 - addon-sdk/source/test/modules/exportsEquals.js | 5 - addon-sdk/source/test/modules/green.js | 10 - addon-sdk/source/test/modules/lion.js | 7 - addon-sdk/source/test/modules/orange.js | 10 - addon-sdk/source/test/modules/pollux.js | 10 - addon-sdk/source/test/modules/red.js | 16 - addon-sdk/source/test/modules/setExports.js | 5 - addon-sdk/source/test/modules/subtract.js | 9 - addon-sdk/source/test/modules/tiger.js | 8 - addon-sdk/source/test/modules/traditional1.js | 12 - addon-sdk/source/test/modules/traditional2.js | 6 - addon-sdk/source/test/modules/types/cat.js | 5 - addon-sdk/source/test/page-mod/helpers.js | 117 - addon-sdk/source/test/path/test-path.js | 430 -- addon-sdk/source/test/preferences/common.json | 16 - addon-sdk/source/test/preferences/e10s-off.json | 5 - addon-sdk/source/test/preferences/e10s-on.json | 3 - addon-sdk/source/test/preferences/firefox.json | 11 - .../source/test/preferences/no-connections.json | 41 - .../test/preferences/test-e10s-preferences.js | 15 - .../source/test/preferences/test-preferences.js | 15 - addon-sdk/source/test/preferences/test.json | 46 - addon-sdk/source/test/private-browsing/helper.js | 58 - addon-sdk/source/test/private-browsing/tabs.js | 25 - addon-sdk/source/test/private-browsing/windows.js | 115 - .../source/test/querystring/test-querystring.js | 205 - addon-sdk/source/test/sidebar/utils.js | 74 - addon-sdk/source/test/tabs/test-fennec-tabs.js | 595 --- addon-sdk/source/test/tabs/test-firefox-tabs.js | 1305 ----- addon-sdk/source/test/tabs/utils.js | 24 - addon-sdk/source/test/test-addon-bootstrap.js | 97 - addon-sdk/source/test/test-addon-extras.js | 70 - addon-sdk/source/test/test-addon-installer.js | 230 - addon-sdk/source/test/test-addon-window.js | 22 - addon-sdk/source/test/test-api-utils.js | 316 -- addon-sdk/source/test/test-array.js | 103 - addon-sdk/source/test/test-base64.js | 100 - addon-sdk/source/test/test-bootstrap.js | 19 - addon-sdk/source/test/test-browser-events.js | 102 - addon-sdk/source/test/test-buffer.js | 563 --- addon-sdk/source/test/test-byte-streams.js | 169 - addon-sdk/source/test/test-child_process.js | 545 -- addon-sdk/source/test/test-chrome.js | 84 - addon-sdk/source/test/test-clipboard.js | 170 - addon-sdk/source/test/test-collection.js | 128 - .../source/test/test-commonjs-test-adapter.js | 11 - addon-sdk/source/test/test-content-events.js | 92 - addon-sdk/source/test/test-content-script.js | 845 ---- addon-sdk/source/test/test-content-sync-worker.js | 965 ---- addon-sdk/source/test/test-content-worker.js | 1129 ----- addon-sdk/source/test/test-context-menu.html | 98 - addon-sdk/source/test/test-context-menu.js | 3763 -------------- addon-sdk/source/test/test-context-menu@2.js | 1350 ----- addon-sdk/source/test/test-cuddlefish.js | 78 - addon-sdk/source/test/test-deprecate.js | 160 - addon-sdk/source/test/test-dev-panel.js | 426 -- addon-sdk/source/test/test-diffpatcher.js | 8 - addon-sdk/source/test/test-dispatcher.js | 76 - addon-sdk/source/test/test-disposable.js | 393 -- addon-sdk/source/test/test-dom.js | 88 - addon-sdk/source/test/test-environment.js | 49 - addon-sdk/source/test/test-event-core.js | 347 -- addon-sdk/source/test/test-event-dom.js | 92 - addon-sdk/source/test/test-event-target.js | 222 - addon-sdk/source/test/test-event-utils.js | 285 -- addon-sdk/source/test/test-file.js | 271 - addon-sdk/source/test/test-frame-utils.js | 59 - addon-sdk/source/test/test-framescript-manager.js | 32 - addon-sdk/source/test/test-framescript-util.js | 45 - addon-sdk/source/test/test-fs.js | 621 --- addon-sdk/source/test/test-functional.js | 463 -- addon-sdk/source/test/test-globals.js | 30 - addon-sdk/source/test/test-heritage.js | 301 -- addon-sdk/source/test/test-hidden-frame.js | 71 - addon-sdk/source/test/test-host-events.js | 99 - addon-sdk/source/test/test-hotkeys.js | 183 - addon-sdk/source/test/test-httpd.js | 73 - addon-sdk/source/test/test-indexed-db.js | 182 - addon-sdk/source/test/test-jetpack-id.js | 64 - addon-sdk/source/test/test-keyboard-observer.js | 36 - addon-sdk/source/test/test-keyboard-utils.js | 61 - addon-sdk/source/test/test-l10n-locale.js | 169 - addon-sdk/source/test/test-l10n-plural-rules.js | 85 - addon-sdk/source/test/test-lang-type.js | 166 - addon-sdk/source/test/test-libxul.js | 18 - addon-sdk/source/test/test-list.js | 58 - addon-sdk/source/test/test-loader.js | 657 --- addon-sdk/source/test/test-match-pattern.js | 137 - addon-sdk/source/test/test-method.js | 7 - addon-sdk/source/test/test-module.js | 36 - addon-sdk/source/test/test-modules.js | 150 - .../source/test/test-mozilla-toolkit-versioning.js | 59 - addon-sdk/source/test/test-mpl2-license-header.js | 105 - addon-sdk/source/test/test-namespace.js | 120 - addon-sdk/source/test/test-native-loader.js | 423 -- addon-sdk/source/test/test-native-options.js | 183 - addon-sdk/source/test/test-net-url.js | 137 - addon-sdk/source/test/test-node-os.js | 33 - addon-sdk/source/test/test-notifications.js | 94 - addon-sdk/source/test/test-object.js | 36 - addon-sdk/source/test/test-observers.js | 183 - addon-sdk/source/test/test-page-mod-debug.js | 66 - addon-sdk/source/test/test-page-mod.js | 2214 --------- addon-sdk/source/test/test-page-worker.js | 558 --- addon-sdk/source/test/test-panel.js | 1426 ------ addon-sdk/source/test/test-passwords-utils.js | 141 - addon-sdk/source/test/test-passwords.js | 280 -- addon-sdk/source/test/test-path.js | 6 - addon-sdk/source/test/test-plain-text-console.js | 278 -- addon-sdk/source/test/test-preferences-service.js | 155 - addon-sdk/source/test/test-preferences-target.js | 42 - addon-sdk/source/test/test-private-browsing.js | 88 - addon-sdk/source/test/test-promise.js | 461 -- addon-sdk/source/test/test-querystring.js | 6 - addon-sdk/source/test/test-reference.js | 99 - addon-sdk/source/test/test-request.js | 548 -- addon-sdk/source/test/test-require.js | 67 - addon-sdk/source/test/test-rules.js | 79 - addon-sdk/source/test/test-sandbox.js | 161 - addon-sdk/source/test/test-selection.js | 985 ---- addon-sdk/source/test/test-self.js | 79 - addon-sdk/source/test/test-sequence.js | 1245 ----- addon-sdk/source/test/test-set-exports.js | 37 - addon-sdk/source/test/test-shared-require.js | 36 - addon-sdk/source/test/test-simple-prefs.js | 331 -- addon-sdk/source/test/test-simple-storage.js | 322 -- addon-sdk/source/test/test-system-events.js | 278 -- addon-sdk/source/test/test-system-input-output.js | 319 -- addon-sdk/source/test/test-system-runtime.js | 25 - addon-sdk/source/test/test-system-startup.js | 19 - addon-sdk/source/test/test-system.js | 37 - addon-sdk/source/test/test-tab-events.js | 238 - addon-sdk/source/test/test-tab-observer.js | 45 - addon-sdk/source/test/test-tab-utils.js | 69 - addon-sdk/source/test/test-tab.js | 228 - addon-sdk/source/test/test-tabs-common.js | 654 --- addon-sdk/source/test/test-tabs.js | 20 - addon-sdk/source/test/test-test-addon-file.js | 16 - addon-sdk/source/test/test-test-assert.js | 218 - addon-sdk/source/test/test-test-loader.js | 59 - addon-sdk/source/test/test-test-memory.js | 25 - addon-sdk/source/test/test-test-utils-async.js | 86 - addon-sdk/source/test/test-test-utils-generator.js | 76 - addon-sdk/source/test/test-test-utils-sync.js | 84 - addon-sdk/source/test/test-test-utils.js | 81 - addon-sdk/source/test/test-text-streams.js | 154 - addon-sdk/source/test/test-timer.js | 229 - addon-sdk/source/test/test-traceback.js | 139 - addon-sdk/source/test/test-ui-action-button.js | 1182 ----- addon-sdk/source/test/test-ui-frame.js | 252 - addon-sdk/source/test/test-ui-id.js | 43 - .../test/test-ui-sidebar-private-browsing.js | 203 - addon-sdk/source/test/test-ui-sidebar.js | 1579 ------ addon-sdk/source/test/test-ui-toggle-button.js | 1386 ------ addon-sdk/source/test/test-ui-toolbar.js | 511 -- addon-sdk/source/test/test-unit-test-finder.js | 57 - addon-sdk/source/test/test-unit-test.js | 270 - addon-sdk/source/test/test-unload.js | 71 - addon-sdk/source/test/test-unsupported-skip.js | 24 - addon-sdk/source/test/test-uri-resource.js | 43 - addon-sdk/source/test/test-url.js | 502 -- addon-sdk/source/test/test-uuid.js | 26 - addon-sdk/source/test/test-weak-set.js | 146 - addon-sdk/source/test/test-window-events.js | 63 - addon-sdk/source/test/test-window-observer.js | 61 - .../test/test-window-utils-private-browsing.js | 210 - addon-sdk/source/test/test-window-utils.js | 266 - addon-sdk/source/test/test-window-utils2.js | 112 - addon-sdk/source/test/test-windows-common.js | 104 - addon-sdk/source/test/test-windows.js | 24 - addon-sdk/source/test/test-xhr.js | 89 - addon-sdk/source/test/test-xpcom.js | 232 - addon-sdk/source/test/test-xul-app.js | 145 - addon-sdk/source/test/traits/assert.js | 101 - addon-sdk/source/test/traits/utils.js | 56 - addon-sdk/source/test/util.js | 90 - .../source/test/windows/test-fennec-windows.js | 48 - .../source/test/windows/test-firefox-windows.js | 621 --- addon-sdk/source/test/zip/utils.js | 126 - addon-sdk/test/Math.jsm | 11 - addon-sdk/test/browser.ini | 14 - addon-sdk/test/browser_sdk_loader_chrome.js | 24 - addon-sdk/test/browser_sdk_loader_chrome_in_sdk.js | 19 - addon-sdk/test/browser_sdk_loader_js_modules.js | 33 - addon-sdk/test/browser_sdk_loader_jsm_modules.js | 31 - addon-sdk/test/browser_sdk_loader_json.js | 23 - .../test/browser_sdk_loader_sdk_gui_modules.js | 18 - addon-sdk/test/browser_sdk_loader_sdk_modules.js | 22 - addon-sdk/test/data.json | 6 - addon-sdk/test/head.js | 79 - addon-sdk/test/invalid.json | 1 - addon-sdk/test/math.js | 7 - build/mach_bootstrap.py | 3 +- toolkit/jetpack/app-extension/application.ini | 11 + toolkit/jetpack/app-extension/bootstrap.js | 362 ++ toolkit/jetpack/app-extension/install.rdf | 33 + toolkit/jetpack/dev/debuggee.js | 95 + toolkit/jetpack/dev/frame-script.js | 120 + toolkit/jetpack/dev/panel.js | 259 + toolkit/jetpack/dev/panel/view.js | 14 + toolkit/jetpack/dev/ports.js | 64 + toolkit/jetpack/dev/theme.js | 135 + toolkit/jetpack/dev/theme/hooks.js | 17 + toolkit/jetpack/dev/toolbox.js | 107 + toolkit/jetpack/dev/utils.js | 40 + toolkit/jetpack/dev/volcan.js | 3848 +++++++++++++++ toolkit/jetpack/diffpatcher/.travis.yml | 5 + toolkit/jetpack/diffpatcher/History.md | 14 + toolkit/jetpack/diffpatcher/License.md | 18 + toolkit/jetpack/diffpatcher/Readme.md | 70 + toolkit/jetpack/diffpatcher/diff.js | 45 + toolkit/jetpack/diffpatcher/index.js | 5 + toolkit/jetpack/diffpatcher/package.json | 54 + toolkit/jetpack/diffpatcher/patch.js | 21 + toolkit/jetpack/diffpatcher/rebase.js | 36 + toolkit/jetpack/diffpatcher/test/common.js | 3 + toolkit/jetpack/diffpatcher/test/diff.js | 59 + toolkit/jetpack/diffpatcher/test/index.js | 14 + toolkit/jetpack/diffpatcher/test/patch.js | 83 + toolkit/jetpack/diffpatcher/test/tap.js | 3 + toolkit/jetpack/framescript/FrameScriptManager.jsm | 27 + toolkit/jetpack/framescript/content.jsm | 94 + toolkit/jetpack/framescript/context-menu.js | 215 + toolkit/jetpack/framescript/manager.js | 26 + toolkit/jetpack/framescript/util.js | 25 + toolkit/jetpack/index.js | 3 + toolkit/jetpack/jetpack-id/index.js | 53 + toolkit/jetpack/jetpack-id/package.json | 28 + toolkit/jetpack/method/.travis.yml | 5 + toolkit/jetpack/method/History.md | 55 + toolkit/jetpack/method/License.md | 18 + toolkit/jetpack/method/Readme.md | 117 + toolkit/jetpack/method/core.js | 225 + toolkit/jetpack/method/package.json | 41 + toolkit/jetpack/method/test/browser.js | 20 + toolkit/jetpack/method/test/common.js | 272 + toolkit/jetpack/modules/system/Startup.js | 57 + toolkit/jetpack/modules/system/moz.build | 9 + toolkit/jetpack/moz.build | 481 ++ .../jetpack/mozilla-toolkit-versioning/index.js | 112 + .../mozilla-toolkit-versioning/lib/utils.js | 15 + .../mozilla-toolkit-versioning/package.json | 21 + toolkit/jetpack/node/os.js | 90 + toolkit/jetpack/sdk/addon/bootstrap.js | 182 + toolkit/jetpack/sdk/addon/events.js | 56 + toolkit/jetpack/sdk/addon/host.js | 12 + toolkit/jetpack/sdk/addon/installer.js | 121 + toolkit/jetpack/sdk/addon/manager.js | 18 + toolkit/jetpack/sdk/addon/runner.js | 180 + toolkit/jetpack/sdk/addon/window.js | 66 + toolkit/jetpack/sdk/base64.js | 47 + toolkit/jetpack/sdk/browser/events.js | 20 + toolkit/jetpack/sdk/clipboard.js | 337 ++ toolkit/jetpack/sdk/console/plain-text.js | 78 + toolkit/jetpack/sdk/console/traceback.js | 86 + toolkit/jetpack/sdk/content/content-worker.js | 305 ++ toolkit/jetpack/sdk/content/content.js | 17 + toolkit/jetpack/sdk/content/context-menu.js | 408 ++ toolkit/jetpack/sdk/content/events.js | 57 + toolkit/jetpack/sdk/content/l10n-html.js | 133 + toolkit/jetpack/sdk/content/loader.js | 74 + toolkit/jetpack/sdk/content/mod.js | 68 + toolkit/jetpack/sdk/content/page-mod.js | 236 + toolkit/jetpack/sdk/content/page-worker.js | 154 + toolkit/jetpack/sdk/content/sandbox.js | 426 ++ toolkit/jetpack/sdk/content/sandbox/events.js | 12 + toolkit/jetpack/sdk/content/tab-events.js | 58 + toolkit/jetpack/sdk/content/thumbnail.js | 51 + toolkit/jetpack/sdk/content/utils.js | 105 + toolkit/jetpack/sdk/content/worker-child.js | 158 + toolkit/jetpack/sdk/content/worker.js | 180 + toolkit/jetpack/sdk/context-menu.js | 1188 +++++ toolkit/jetpack/sdk/context-menu/context.js | 147 + toolkit/jetpack/sdk/context-menu/core.js | 384 ++ toolkit/jetpack/sdk/context-menu/readers.js | 112 + toolkit/jetpack/sdk/context-menu@2.js | 32 + toolkit/jetpack/sdk/core/disposable.js | 186 + toolkit/jetpack/sdk/core/heritage.js | 184 + toolkit/jetpack/sdk/core/namespace.js | 43 + toolkit/jetpack/sdk/core/observer.js | 89 + toolkit/jetpack/sdk/core/promise.js | 118 + toolkit/jetpack/sdk/core/reference.js | 29 + toolkit/jetpack/sdk/deprecated/api-utils.js | 197 + toolkit/jetpack/sdk/deprecated/events/assembler.js | 54 + toolkit/jetpack/sdk/deprecated/sync-worker.js | 288 ++ toolkit/jetpack/sdk/deprecated/unit-test-finder.js | 199 + toolkit/jetpack/sdk/deprecated/unit-test.js | 584 +++ toolkit/jetpack/sdk/deprecated/window-utils.js | 193 + toolkit/jetpack/sdk/dom/events-shimmed.js | 18 + toolkit/jetpack/sdk/dom/events.js | 192 + toolkit/jetpack/sdk/dom/events/keys.js | 63 + toolkit/jetpack/sdk/event/chrome.js | 65 + toolkit/jetpack/sdk/event/core.js | 193 + toolkit/jetpack/sdk/event/dom.js | 78 + toolkit/jetpack/sdk/event/target.js | 74 + toolkit/jetpack/sdk/event/utils.js | 328 ++ toolkit/jetpack/sdk/frame/hidden-frame.js | 115 + toolkit/jetpack/sdk/frame/utils.js | 94 + toolkit/jetpack/sdk/fs/path.js | 500 ++ toolkit/jetpack/sdk/hotkeys.js | 40 + toolkit/jetpack/sdk/indexed-db.js | 79 + toolkit/jetpack/sdk/input/browser.js | 73 + toolkit/jetpack/sdk/input/customizable-ui.js | 28 + toolkit/jetpack/sdk/input/frame.js | 85 + toolkit/jetpack/sdk/input/system.js | 113 + toolkit/jetpack/sdk/io/buffer.js | 351 ++ toolkit/jetpack/sdk/io/byte-streams.js | 104 + toolkit/jetpack/sdk/io/file.js | 196 + toolkit/jetpack/sdk/io/fs.js | 984 ++++ toolkit/jetpack/sdk/io/stream.js | 440 ++ toolkit/jetpack/sdk/io/text-streams.js | 235 + toolkit/jetpack/sdk/keyboard/hotkeys.js | 110 + toolkit/jetpack/sdk/keyboard/observer.js | 58 + toolkit/jetpack/sdk/keyboard/utils.js | 189 + toolkit/jetpack/sdk/l10n.js | 91 + toolkit/jetpack/sdk/l10n/core.js | 9 + toolkit/jetpack/sdk/l10n/html.js | 32 + toolkit/jetpack/sdk/l10n/json/core.js | 36 + toolkit/jetpack/sdk/l10n/loader.js | 70 + toolkit/jetpack/sdk/l10n/locale.js | 127 + toolkit/jetpack/sdk/l10n/plural-rules.js | 407 ++ toolkit/jetpack/sdk/l10n/prefs.js | 51 + toolkit/jetpack/sdk/l10n/properties/core.js | 87 + toolkit/jetpack/sdk/lang/functional.js | 47 + toolkit/jetpack/sdk/lang/functional/concurrent.js | 110 + toolkit/jetpack/sdk/lang/functional/core.js | 290 ++ toolkit/jetpack/sdk/lang/functional/helpers.js | 29 + toolkit/jetpack/sdk/lang/type.js | 388 ++ toolkit/jetpack/sdk/lang/weak-set.js | 75 + toolkit/jetpack/sdk/loader/cuddlefish.js | 102 + toolkit/jetpack/sdk/loader/sandbox.js | 74 + toolkit/jetpack/sdk/messaging.js | 12 + toolkit/jetpack/sdk/model/core.js | 23 + toolkit/jetpack/sdk/net/url.js | 94 + toolkit/jetpack/sdk/net/xhr.js | 36 + toolkit/jetpack/sdk/notifications.js | 112 + toolkit/jetpack/sdk/output/system.js | 71 + toolkit/jetpack/sdk/page-mod.js | 190 + toolkit/jetpack/sdk/page-mod/match-pattern.js | 10 + toolkit/jetpack/sdk/page-worker.js | 194 + toolkit/jetpack/sdk/panel.js | 427 ++ toolkit/jetpack/sdk/panel/events.js | 27 + toolkit/jetpack/sdk/panel/utils.js | 451 ++ toolkit/jetpack/sdk/passwords.js | 61 + toolkit/jetpack/sdk/passwords/utils.js | 107 + toolkit/jetpack/sdk/places/bookmarks.js | 395 ++ toolkit/jetpack/sdk/places/contract.js | 73 + toolkit/jetpack/sdk/places/events.js | 128 + toolkit/jetpack/sdk/places/favicon.js | 49 + toolkit/jetpack/sdk/places/history.js | 65 + toolkit/jetpack/sdk/places/host/host-bookmarks.js | 238 + toolkit/jetpack/sdk/places/host/host-query.js | 179 + toolkit/jetpack/sdk/places/host/host-tags.js | 92 + toolkit/jetpack/sdk/places/utils.js | 268 + toolkit/jetpack/sdk/platform/xpcom.js | 241 + toolkit/jetpack/sdk/preferences/event-target.js | 61 + toolkit/jetpack/sdk/preferences/native-options.js | 193 + toolkit/jetpack/sdk/preferences/service.js | 137 + toolkit/jetpack/sdk/preferences/utils.js | 42 + toolkit/jetpack/sdk/private-browsing.js | 12 + toolkit/jetpack/sdk/private-browsing/utils.js | 54 + toolkit/jetpack/sdk/querystring.js | 121 + toolkit/jetpack/sdk/remote/child.js | 284 ++ toolkit/jetpack/sdk/remote/core.js | 8 + toolkit/jetpack/sdk/remote/parent.js | 338 ++ toolkit/jetpack/sdk/remote/utils.js | 39 + toolkit/jetpack/sdk/request.js | 248 + toolkit/jetpack/sdk/selection.js | 470 ++ toolkit/jetpack/sdk/self.js | 61 + toolkit/jetpack/sdk/simple-prefs.js | 26 + toolkit/jetpack/sdk/simple-storage.js | 235 + toolkit/jetpack/sdk/stylesheet/style.js | 71 + toolkit/jetpack/sdk/stylesheet/utils.js | 75 + toolkit/jetpack/sdk/system.js | 172 + toolkit/jetpack/sdk/system/child_process.js | 332 ++ .../jetpack/sdk/system/child_process/subprocess.js | 186 + toolkit/jetpack/sdk/system/environment.js | 33 + toolkit/jetpack/sdk/system/events-shimmed.js | 16 + toolkit/jetpack/sdk/system/events.js | 181 + toolkit/jetpack/sdk/system/globals.js | 46 + toolkit/jetpack/sdk/system/process.js | 62 + toolkit/jetpack/sdk/system/runtime.js | 28 + toolkit/jetpack/sdk/system/unload.js | 104 + toolkit/jetpack/sdk/system/xul-app.js | 12 + toolkit/jetpack/sdk/system/xul-app.jsm | 242 + toolkit/jetpack/sdk/tab/events.js | 74 + toolkit/jetpack/sdk/tabs.js | 17 + toolkit/jetpack/sdk/tabs/common.js | 34 + toolkit/jetpack/sdk/tabs/events.js | 39 + toolkit/jetpack/sdk/tabs/helpers.js | 22 + toolkit/jetpack/sdk/tabs/namespace.js | 10 + toolkit/jetpack/sdk/tabs/observer.js | 113 + toolkit/jetpack/sdk/tabs/tab-fennec.js | 249 + toolkit/jetpack/sdk/tabs/tab-firefox.js | 353 ++ toolkit/jetpack/sdk/tabs/tab.js | 24 + toolkit/jetpack/sdk/tabs/tabs-firefox.js | 135 + toolkit/jetpack/sdk/tabs/utils.js | 370 ++ toolkit/jetpack/sdk/tabs/worker.js | 17 + toolkit/jetpack/sdk/test.js | 114 + toolkit/jetpack/sdk/test/assert.js | 366 ++ toolkit/jetpack/sdk/test/harness.js | 645 +++ toolkit/jetpack/sdk/test/httpd.js | 6 + toolkit/jetpack/sdk/test/loader.js | 123 + toolkit/jetpack/sdk/test/memory.js | 11 + toolkit/jetpack/sdk/test/options.js | 23 + toolkit/jetpack/sdk/test/runner.js | 131 + toolkit/jetpack/sdk/test/utils.js | 199 + toolkit/jetpack/sdk/timers.js | 105 + toolkit/jetpack/sdk/ui.js | 17 + toolkit/jetpack/sdk/ui/button/action.js | 114 + toolkit/jetpack/sdk/ui/button/contract.js | 73 + toolkit/jetpack/sdk/ui/button/toggle.js | 127 + toolkit/jetpack/sdk/ui/button/view.js | 243 + toolkit/jetpack/sdk/ui/button/view/events.js | 18 + toolkit/jetpack/sdk/ui/component.js | 182 + toolkit/jetpack/sdk/ui/frame.js | 16 + toolkit/jetpack/sdk/ui/frame/model.js | 154 + toolkit/jetpack/sdk/ui/frame/view.html | 18 + toolkit/jetpack/sdk/ui/frame/view.js | 150 + toolkit/jetpack/sdk/ui/id.js | 27 + toolkit/jetpack/sdk/ui/sidebar.js | 311 ++ toolkit/jetpack/sdk/ui/sidebar/actions.js | 10 + toolkit/jetpack/sdk/ui/sidebar/contract.js | 27 + toolkit/jetpack/sdk/ui/sidebar/namespace.js | 15 + toolkit/jetpack/sdk/ui/sidebar/utils.js | 8 + toolkit/jetpack/sdk/ui/sidebar/view.js | 214 + toolkit/jetpack/sdk/ui/state.js | 239 + toolkit/jetpack/sdk/ui/state/events.js | 18 + toolkit/jetpack/sdk/ui/toolbar.js | 16 + toolkit/jetpack/sdk/ui/toolbar/model.js | 151 + toolkit/jetpack/sdk/ui/toolbar/view.js | 248 + toolkit/jetpack/sdk/uri/resource.js | 37 + toolkit/jetpack/sdk/url.js | 349 ++ toolkit/jetpack/sdk/url/utils.js | 29 + toolkit/jetpack/sdk/util/array.js | 123 + toolkit/jetpack/sdk/util/collection.js | 115 + toolkit/jetpack/sdk/util/contract.js | 55 + toolkit/jetpack/sdk/util/deprecate.js | 40 + toolkit/jetpack/sdk/util/dispatcher.js | 54 + toolkit/jetpack/sdk/util/list.js | 90 + toolkit/jetpack/sdk/util/match-pattern.js | 113 + toolkit/jetpack/sdk/util/object.js | 104 + toolkit/jetpack/sdk/util/rules.js | 53 + toolkit/jetpack/sdk/util/sequence.js | 593 +++ toolkit/jetpack/sdk/util/uuid.js | 19 + toolkit/jetpack/sdk/view/core.js | 26 + toolkit/jetpack/sdk/webextension.js | 43 + toolkit/jetpack/sdk/window/browser.js | 54 + toolkit/jetpack/sdk/window/events.js | 68 + toolkit/jetpack/sdk/window/helpers.js | 81 + toolkit/jetpack/sdk/window/namespace.js | 6 + toolkit/jetpack/sdk/window/utils.js | 460 ++ toolkit/jetpack/sdk/windows.js | 32 + toolkit/jetpack/sdk/windows/fennec.js | 83 + toolkit/jetpack/sdk/windows/firefox.js | 224 + toolkit/jetpack/sdk/windows/observer.js | 53 + toolkit/jetpack/sdk/windows/tabs-fennec.js | 172 + toolkit/jetpack/sdk/worker/utils.js | 19 + toolkit/jetpack/sdk/zip/utils.js | 16 + toolkit/jetpack/test.js | 11 + toolkit/jetpack/toolkit/loader.js | 1147 +++++ toolkit/jetpack/toolkit/require.js | 91 + toolkit/moz.build | 1 + toolkit/toolkit.mozbuild | 2 - 1261 files changed, 39417 insertions(+), 139596 deletions(-) delete mode 100644 addon-sdk/Makefile.in delete mode 100644 addon-sdk/mach_commands.py delete mode 100644 addon-sdk/moz.build delete mode 100644 addon-sdk/mozbuild.template delete mode 100644 addon-sdk/source/.gitattributes delete mode 100644 addon-sdk/source/.gitignore delete mode 100644 addon-sdk/source/.jpmignore delete mode 100644 addon-sdk/source/.travis.yml delete mode 100644 addon-sdk/source/CONTRIBUTING.md delete mode 100644 addon-sdk/source/LICENSE delete mode 100644 addon-sdk/source/README.md delete mode 100644 addon-sdk/source/app-extension/application.ini delete mode 100644 addon-sdk/source/app-extension/bootstrap.js delete mode 100644 addon-sdk/source/app-extension/install.rdf delete mode 100644 addon-sdk/source/bin/activate delete mode 100644 addon-sdk/source/bin/activate.bat delete mode 100644 addon-sdk/source/bin/activate.fish delete mode 100644 addon-sdk/source/bin/activate.ps1 delete mode 100755 addon-sdk/source/bin/cfx delete mode 100644 addon-sdk/source/bin/cfx.bat delete mode 100644 addon-sdk/source/bin/deactivate.bat delete mode 100644 addon-sdk/source/bin/fx-download.sh delete mode 100755 addon-sdk/source/bin/integration-scripts/buildbot-run-cfx-helper delete mode 100644 addon-sdk/source/bin/integration-scripts/integration-check delete mode 100644 addon-sdk/source/bin/jpm-test.js delete mode 100644 addon-sdk/source/bin/node-scripts/apply-patch.js delete mode 100644 addon-sdk/source/bin/node-scripts/test.addons.js delete mode 100644 addon-sdk/source/bin/node-scripts/test.docs.js delete mode 100644 addon-sdk/source/bin/node-scripts/test.examples.js delete mode 100644 addon-sdk/source/bin/node-scripts/test.firefox-bin.js delete mode 100644 addon-sdk/source/bin/node-scripts/test.ini.js delete mode 100644 addon-sdk/source/bin/node-scripts/test.modules.js delete mode 100644 addon-sdk/source/bin/node-scripts/update-ini.js delete mode 100644 addon-sdk/source/bin/node-scripts/utils.js delete mode 100644 addon-sdk/source/bin/node-scripts/words.txt delete mode 100644 addon-sdk/source/examples/actor-repl/README.md delete mode 100644 addon-sdk/source/examples/actor-repl/data/codemirror-compressed.js delete mode 100644 addon-sdk/source/examples/actor-repl/data/codemirror.css delete mode 100644 addon-sdk/source/examples/actor-repl/data/index.html delete mode 100644 addon-sdk/source/examples/actor-repl/data/main.css delete mode 100644 addon-sdk/source/examples/actor-repl/data/robot.png delete mode 100644 addon-sdk/source/examples/actor-repl/index.js delete mode 100644 addon-sdk/source/examples/actor-repl/package.json delete mode 100644 addon-sdk/source/examples/actor-repl/test/test-main.js delete mode 100644 addon-sdk/source/examples/debug-client/data/client.js delete mode 100644 addon-sdk/source/examples/debug-client/data/index.html delete mode 100644 addon-sdk/source/examples/debug-client/data/plugin.png delete mode 100644 addon-sdk/source/examples/debug-client/data/task.js delete mode 100644 addon-sdk/source/examples/debug-client/index.js delete mode 100644 addon-sdk/source/examples/debug-client/package.json delete mode 100644 addon-sdk/source/examples/debug-client/test/test-main.js delete mode 100644 addon-sdk/source/examples/reading-data/data/mom.png delete mode 100644 addon-sdk/source/examples/reading-data/data/sample.html delete mode 100644 addon-sdk/source/examples/reading-data/lib/main.js delete mode 100644 addon-sdk/source/examples/reading-data/package.json delete mode 100644 addon-sdk/source/examples/reading-data/tests/test-main.js delete mode 100644 addon-sdk/source/examples/theme/data/icon-16.png delete mode 100644 addon-sdk/source/examples/theme/data/index.html delete mode 100644 addon-sdk/source/examples/theme/data/theme.css delete mode 100644 addon-sdk/source/examples/theme/lib/main.js delete mode 100644 addon-sdk/source/examples/theme/package.json delete mode 100644 addon-sdk/source/examples/theme/test/test-main.js delete mode 100644 addon-sdk/source/examples/toolbar-api/data/favicon.ico delete mode 100644 addon-sdk/source/examples/toolbar-api/data/index.html delete mode 100644 addon-sdk/source/examples/toolbar-api/lib/main.js delete mode 100644 addon-sdk/source/examples/toolbar-api/package.json delete mode 100644 addon-sdk/source/examples/toolbar-api/test/test-main.js delete mode 100644 addon-sdk/source/examples/ui-button-apis/lib/main.js delete mode 100644 addon-sdk/source/examples/ui-button-apis/package.json delete mode 100644 addon-sdk/source/examples/ui-button-apis/tests/test-main.js delete mode 100644 addon-sdk/source/gulpfile.js delete mode 100644 addon-sdk/source/lib/dev/debuggee.js delete mode 100644 addon-sdk/source/lib/dev/frame-script.js delete mode 100644 addon-sdk/source/lib/dev/panel.js delete mode 100644 addon-sdk/source/lib/dev/panel/view.js delete mode 100644 addon-sdk/source/lib/dev/ports.js delete mode 100644 addon-sdk/source/lib/dev/theme.js delete mode 100644 addon-sdk/source/lib/dev/theme/hooks.js delete mode 100644 addon-sdk/source/lib/dev/toolbox.js delete mode 100644 addon-sdk/source/lib/dev/utils.js delete mode 100644 addon-sdk/source/lib/dev/volcan.js delete mode 100644 addon-sdk/source/lib/diffpatcher/.travis.yml delete mode 100644 addon-sdk/source/lib/diffpatcher/History.md delete mode 100644 addon-sdk/source/lib/diffpatcher/License.md delete mode 100644 addon-sdk/source/lib/diffpatcher/Readme.md delete mode 100644 addon-sdk/source/lib/diffpatcher/diff.js delete mode 100644 addon-sdk/source/lib/diffpatcher/index.js delete mode 100644 addon-sdk/source/lib/diffpatcher/package.json delete mode 100644 addon-sdk/source/lib/diffpatcher/patch.js delete mode 100644 addon-sdk/source/lib/diffpatcher/rebase.js delete mode 100644 addon-sdk/source/lib/diffpatcher/test/common.js delete mode 100644 addon-sdk/source/lib/diffpatcher/test/diff.js delete mode 100644 addon-sdk/source/lib/diffpatcher/test/index.js delete mode 100644 addon-sdk/source/lib/diffpatcher/test/patch.js delete mode 100644 addon-sdk/source/lib/diffpatcher/test/tap.js delete mode 100644 addon-sdk/source/lib/framescript/FrameScriptManager.jsm delete mode 100644 addon-sdk/source/lib/framescript/content.jsm delete mode 100644 addon-sdk/source/lib/framescript/context-menu.js delete mode 100644 addon-sdk/source/lib/framescript/manager.js delete mode 100644 addon-sdk/source/lib/framescript/util.js delete mode 100644 addon-sdk/source/lib/index.js delete mode 100644 addon-sdk/source/lib/jetpack-id/index.js delete mode 100644 addon-sdk/source/lib/jetpack-id/package.json delete mode 100644 addon-sdk/source/lib/method/.travis.yml delete mode 100644 addon-sdk/source/lib/method/History.md delete mode 100644 addon-sdk/source/lib/method/License.md delete mode 100644 addon-sdk/source/lib/method/Readme.md delete mode 100644 addon-sdk/source/lib/method/core.js delete mode 100644 addon-sdk/source/lib/method/package.json delete mode 100644 addon-sdk/source/lib/method/test/browser.js delete mode 100644 addon-sdk/source/lib/method/test/common.js delete mode 100644 addon-sdk/source/lib/mozilla-toolkit-versioning/index.js delete mode 100644 addon-sdk/source/lib/mozilla-toolkit-versioning/lib/utils.js delete mode 100644 addon-sdk/source/lib/mozilla-toolkit-versioning/package.json delete mode 100644 addon-sdk/source/lib/node/os.js delete mode 100644 addon-sdk/source/lib/sdk/addon/bootstrap.js delete mode 100644 addon-sdk/source/lib/sdk/addon/events.js delete mode 100644 addon-sdk/source/lib/sdk/addon/host.js delete mode 100644 addon-sdk/source/lib/sdk/addon/installer.js delete mode 100644 addon-sdk/source/lib/sdk/addon/manager.js delete mode 100644 addon-sdk/source/lib/sdk/addon/runner.js delete mode 100644 addon-sdk/source/lib/sdk/addon/window.js delete mode 100644 addon-sdk/source/lib/sdk/base64.js delete mode 100644 addon-sdk/source/lib/sdk/browser/events.js delete mode 100644 addon-sdk/source/lib/sdk/clipboard.js delete mode 100644 addon-sdk/source/lib/sdk/console/plain-text.js delete mode 100644 addon-sdk/source/lib/sdk/console/traceback.js delete mode 100644 addon-sdk/source/lib/sdk/content/content-worker.js delete mode 100644 addon-sdk/source/lib/sdk/content/content.js delete mode 100644 addon-sdk/source/lib/sdk/content/context-menu.js delete mode 100644 addon-sdk/source/lib/sdk/content/events.js delete mode 100644 addon-sdk/source/lib/sdk/content/l10n-html.js delete mode 100644 addon-sdk/source/lib/sdk/content/loader.js delete mode 100644 addon-sdk/source/lib/sdk/content/mod.js delete mode 100644 addon-sdk/source/lib/sdk/content/page-mod.js delete mode 100644 addon-sdk/source/lib/sdk/content/page-worker.js delete mode 100644 addon-sdk/source/lib/sdk/content/sandbox.js delete mode 100644 addon-sdk/source/lib/sdk/content/sandbox/events.js delete mode 100644 addon-sdk/source/lib/sdk/content/tab-events.js delete mode 100644 addon-sdk/source/lib/sdk/content/thumbnail.js delete mode 100644 addon-sdk/source/lib/sdk/content/utils.js delete mode 100644 addon-sdk/source/lib/sdk/content/worker-child.js delete mode 100644 addon-sdk/source/lib/sdk/content/worker.js delete mode 100644 addon-sdk/source/lib/sdk/context-menu.js delete mode 100644 addon-sdk/source/lib/sdk/context-menu/context.js delete mode 100644 addon-sdk/source/lib/sdk/context-menu/core.js delete mode 100644 addon-sdk/source/lib/sdk/context-menu/readers.js delete mode 100644 addon-sdk/source/lib/sdk/context-menu@2.js delete mode 100644 addon-sdk/source/lib/sdk/core/disposable.js delete mode 100644 addon-sdk/source/lib/sdk/core/heritage.js delete mode 100644 addon-sdk/source/lib/sdk/core/namespace.js delete mode 100644 addon-sdk/source/lib/sdk/core/observer.js delete mode 100644 addon-sdk/source/lib/sdk/core/promise.js delete mode 100644 addon-sdk/source/lib/sdk/core/reference.js delete mode 100644 addon-sdk/source/lib/sdk/deprecated/api-utils.js delete mode 100644 addon-sdk/source/lib/sdk/deprecated/events/assembler.js delete mode 100644 addon-sdk/source/lib/sdk/deprecated/sync-worker.js delete mode 100644 addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js delete mode 100644 addon-sdk/source/lib/sdk/deprecated/unit-test.js delete mode 100644 addon-sdk/source/lib/sdk/deprecated/window-utils.js delete mode 100644 addon-sdk/source/lib/sdk/dom/events-shimmed.js delete mode 100644 addon-sdk/source/lib/sdk/dom/events.js delete mode 100644 addon-sdk/source/lib/sdk/dom/events/keys.js delete mode 100644 addon-sdk/source/lib/sdk/event/chrome.js delete mode 100644 addon-sdk/source/lib/sdk/event/core.js delete mode 100644 addon-sdk/source/lib/sdk/event/dom.js delete mode 100644 addon-sdk/source/lib/sdk/event/target.js delete mode 100644 addon-sdk/source/lib/sdk/event/utils.js delete mode 100644 addon-sdk/source/lib/sdk/frame/hidden-frame.js delete mode 100644 addon-sdk/source/lib/sdk/frame/utils.js delete mode 100644 addon-sdk/source/lib/sdk/fs/path.js delete mode 100644 addon-sdk/source/lib/sdk/hotkeys.js delete mode 100644 addon-sdk/source/lib/sdk/indexed-db.js delete mode 100644 addon-sdk/source/lib/sdk/input/browser.js delete mode 100644 addon-sdk/source/lib/sdk/input/customizable-ui.js delete mode 100644 addon-sdk/source/lib/sdk/input/frame.js delete mode 100644 addon-sdk/source/lib/sdk/input/system.js delete mode 100644 addon-sdk/source/lib/sdk/io/buffer.js delete mode 100644 addon-sdk/source/lib/sdk/io/byte-streams.js delete mode 100644 addon-sdk/source/lib/sdk/io/file.js delete mode 100644 addon-sdk/source/lib/sdk/io/fs.js delete mode 100644 addon-sdk/source/lib/sdk/io/stream.js delete mode 100644 addon-sdk/source/lib/sdk/io/text-streams.js delete mode 100644 addon-sdk/source/lib/sdk/keyboard/hotkeys.js delete mode 100644 addon-sdk/source/lib/sdk/keyboard/observer.js delete mode 100644 addon-sdk/source/lib/sdk/keyboard/utils.js delete mode 100644 addon-sdk/source/lib/sdk/l10n.js delete mode 100644 addon-sdk/source/lib/sdk/l10n/core.js delete mode 100644 addon-sdk/source/lib/sdk/l10n/html.js delete mode 100644 addon-sdk/source/lib/sdk/l10n/json/core.js delete mode 100644 addon-sdk/source/lib/sdk/l10n/loader.js delete mode 100644 addon-sdk/source/lib/sdk/l10n/locale.js delete mode 100644 addon-sdk/source/lib/sdk/l10n/plural-rules.js delete mode 100644 addon-sdk/source/lib/sdk/l10n/prefs.js delete mode 100644 addon-sdk/source/lib/sdk/l10n/properties/core.js delete mode 100644 addon-sdk/source/lib/sdk/lang/functional.js delete mode 100644 addon-sdk/source/lib/sdk/lang/functional/concurrent.js delete mode 100644 addon-sdk/source/lib/sdk/lang/functional/core.js delete mode 100644 addon-sdk/source/lib/sdk/lang/functional/helpers.js delete mode 100644 addon-sdk/source/lib/sdk/lang/type.js delete mode 100644 addon-sdk/source/lib/sdk/lang/weak-set.js delete mode 100644 addon-sdk/source/lib/sdk/loader/cuddlefish.js delete mode 100644 addon-sdk/source/lib/sdk/loader/sandbox.js delete mode 100644 addon-sdk/source/lib/sdk/messaging.js delete mode 100644 addon-sdk/source/lib/sdk/model/core.js delete mode 100644 addon-sdk/source/lib/sdk/net/url.js delete mode 100644 addon-sdk/source/lib/sdk/net/xhr.js delete mode 100644 addon-sdk/source/lib/sdk/notifications.js delete mode 100644 addon-sdk/source/lib/sdk/output/system.js delete mode 100644 addon-sdk/source/lib/sdk/page-mod.js delete mode 100644 addon-sdk/source/lib/sdk/page-mod/match-pattern.js delete mode 100644 addon-sdk/source/lib/sdk/page-worker.js delete mode 100644 addon-sdk/source/lib/sdk/panel.js delete mode 100644 addon-sdk/source/lib/sdk/panel/events.js delete mode 100644 addon-sdk/source/lib/sdk/panel/utils.js delete mode 100644 addon-sdk/source/lib/sdk/passwords.js delete mode 100644 addon-sdk/source/lib/sdk/passwords/utils.js delete mode 100644 addon-sdk/source/lib/sdk/places/bookmarks.js delete mode 100644 addon-sdk/source/lib/sdk/places/contract.js delete mode 100644 addon-sdk/source/lib/sdk/places/events.js delete mode 100644 addon-sdk/source/lib/sdk/places/favicon.js delete mode 100644 addon-sdk/source/lib/sdk/places/history.js delete mode 100644 addon-sdk/source/lib/sdk/places/host/host-bookmarks.js delete mode 100644 addon-sdk/source/lib/sdk/places/host/host-query.js delete mode 100644 addon-sdk/source/lib/sdk/places/host/host-tags.js delete mode 100644 addon-sdk/source/lib/sdk/places/utils.js delete mode 100644 addon-sdk/source/lib/sdk/platform/xpcom.js delete mode 100644 addon-sdk/source/lib/sdk/preferences/event-target.js delete mode 100644 addon-sdk/source/lib/sdk/preferences/native-options.js delete mode 100644 addon-sdk/source/lib/sdk/preferences/service.js delete mode 100644 addon-sdk/source/lib/sdk/preferences/utils.js delete mode 100644 addon-sdk/source/lib/sdk/private-browsing.js delete mode 100644 addon-sdk/source/lib/sdk/private-browsing/utils.js delete mode 100644 addon-sdk/source/lib/sdk/querystring.js delete mode 100644 addon-sdk/source/lib/sdk/remote/child.js delete mode 100644 addon-sdk/source/lib/sdk/remote/core.js delete mode 100644 addon-sdk/source/lib/sdk/remote/parent.js delete mode 100644 addon-sdk/source/lib/sdk/remote/utils.js delete mode 100644 addon-sdk/source/lib/sdk/request.js delete mode 100644 addon-sdk/source/lib/sdk/selection.js delete mode 100644 addon-sdk/source/lib/sdk/self.js delete mode 100644 addon-sdk/source/lib/sdk/simple-prefs.js delete mode 100644 addon-sdk/source/lib/sdk/simple-storage.js delete mode 100644 addon-sdk/source/lib/sdk/stylesheet/style.js delete mode 100644 addon-sdk/source/lib/sdk/stylesheet/utils.js delete mode 100644 addon-sdk/source/lib/sdk/system.js delete mode 100644 addon-sdk/source/lib/sdk/system/child_process.js delete mode 100644 addon-sdk/source/lib/sdk/system/child_process/subprocess.js delete mode 100644 addon-sdk/source/lib/sdk/system/environment.js delete mode 100644 addon-sdk/source/lib/sdk/system/events-shimmed.js delete mode 100644 addon-sdk/source/lib/sdk/system/events.js delete mode 100644 addon-sdk/source/lib/sdk/system/globals.js delete mode 100644 addon-sdk/source/lib/sdk/system/process.js delete mode 100644 addon-sdk/source/lib/sdk/system/runtime.js delete mode 100644 addon-sdk/source/lib/sdk/system/unload.js delete mode 100644 addon-sdk/source/lib/sdk/system/xul-app.js delete mode 100644 addon-sdk/source/lib/sdk/system/xul-app.jsm delete mode 100644 addon-sdk/source/lib/sdk/tab/events.js delete mode 100644 addon-sdk/source/lib/sdk/tabs.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/common.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/events.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/helpers.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/namespace.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/observer.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/tab-fennec.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/tab-firefox.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/tab.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/tabs-firefox.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/utils.js delete mode 100644 addon-sdk/source/lib/sdk/tabs/worker.js delete mode 100644 addon-sdk/source/lib/sdk/test.js delete mode 100644 addon-sdk/source/lib/sdk/test/assert.js delete mode 100644 addon-sdk/source/lib/sdk/test/harness.js delete mode 100644 addon-sdk/source/lib/sdk/test/httpd.js delete mode 100644 addon-sdk/source/lib/sdk/test/loader.js delete mode 100644 addon-sdk/source/lib/sdk/test/memory.js delete mode 100644 addon-sdk/source/lib/sdk/test/options.js delete mode 100644 addon-sdk/source/lib/sdk/test/runner.js delete mode 100644 addon-sdk/source/lib/sdk/test/utils.js delete mode 100644 addon-sdk/source/lib/sdk/timers.js delete mode 100644 addon-sdk/source/lib/sdk/ui.js delete mode 100644 addon-sdk/source/lib/sdk/ui/button/action.js delete mode 100644 addon-sdk/source/lib/sdk/ui/button/contract.js delete mode 100644 addon-sdk/source/lib/sdk/ui/button/toggle.js delete mode 100644 addon-sdk/source/lib/sdk/ui/button/view.js delete mode 100644 addon-sdk/source/lib/sdk/ui/button/view/events.js delete mode 100644 addon-sdk/source/lib/sdk/ui/component.js delete mode 100644 addon-sdk/source/lib/sdk/ui/frame.js delete mode 100644 addon-sdk/source/lib/sdk/ui/frame/model.js delete mode 100644 addon-sdk/source/lib/sdk/ui/frame/view.html delete mode 100644 addon-sdk/source/lib/sdk/ui/frame/view.js delete mode 100644 addon-sdk/source/lib/sdk/ui/id.js delete mode 100644 addon-sdk/source/lib/sdk/ui/sidebar.js delete mode 100644 addon-sdk/source/lib/sdk/ui/sidebar/actions.js delete mode 100644 addon-sdk/source/lib/sdk/ui/sidebar/contract.js delete mode 100644 addon-sdk/source/lib/sdk/ui/sidebar/namespace.js delete mode 100644 addon-sdk/source/lib/sdk/ui/sidebar/utils.js delete mode 100644 addon-sdk/source/lib/sdk/ui/sidebar/view.js delete mode 100644 addon-sdk/source/lib/sdk/ui/state.js delete mode 100644 addon-sdk/source/lib/sdk/ui/state/events.js delete mode 100644 addon-sdk/source/lib/sdk/ui/toolbar.js delete mode 100644 addon-sdk/source/lib/sdk/ui/toolbar/model.js delete mode 100644 addon-sdk/source/lib/sdk/ui/toolbar/view.js delete mode 100644 addon-sdk/source/lib/sdk/uri/resource.js delete mode 100644 addon-sdk/source/lib/sdk/url.js delete mode 100644 addon-sdk/source/lib/sdk/url/utils.js delete mode 100644 addon-sdk/source/lib/sdk/util/array.js delete mode 100644 addon-sdk/source/lib/sdk/util/collection.js delete mode 100644 addon-sdk/source/lib/sdk/util/contract.js delete mode 100644 addon-sdk/source/lib/sdk/util/deprecate.js delete mode 100644 addon-sdk/source/lib/sdk/util/dispatcher.js delete mode 100644 addon-sdk/source/lib/sdk/util/list.js delete mode 100644 addon-sdk/source/lib/sdk/util/match-pattern.js delete mode 100644 addon-sdk/source/lib/sdk/util/object.js delete mode 100644 addon-sdk/source/lib/sdk/util/rules.js delete mode 100644 addon-sdk/source/lib/sdk/util/sequence.js delete mode 100644 addon-sdk/source/lib/sdk/util/uuid.js delete mode 100644 addon-sdk/source/lib/sdk/view/core.js delete mode 100644 addon-sdk/source/lib/sdk/webextension.js delete mode 100644 addon-sdk/source/lib/sdk/window/browser.js delete mode 100644 addon-sdk/source/lib/sdk/window/events.js delete mode 100644 addon-sdk/source/lib/sdk/window/helpers.js delete mode 100644 addon-sdk/source/lib/sdk/window/namespace.js delete mode 100644 addon-sdk/source/lib/sdk/window/utils.js delete mode 100644 addon-sdk/source/lib/sdk/windows.js delete mode 100644 addon-sdk/source/lib/sdk/windows/fennec.js delete mode 100644 addon-sdk/source/lib/sdk/windows/firefox.js delete mode 100644 addon-sdk/source/lib/sdk/windows/observer.js delete mode 100644 addon-sdk/source/lib/sdk/windows/tabs-fennec.js delete mode 100644 addon-sdk/source/lib/sdk/worker/utils.js delete mode 100644 addon-sdk/source/lib/sdk/zip/utils.js delete mode 100644 addon-sdk/source/lib/test.js delete mode 100644 addon-sdk/source/lib/toolkit/loader.js delete mode 100644 addon-sdk/source/lib/toolkit/require.js delete mode 100644 addon-sdk/source/mapping.json delete mode 100644 addon-sdk/source/modules/system/Startup.js delete mode 100644 addon-sdk/source/modules/system/moz.build delete mode 100644 addon-sdk/source/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/__init__.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/_version.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/bunch.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/manifest.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/mobile-utils/install.rdf delete mode 100644 addon-sdk/source/python-lib/cuddlefish/packaging.py delete mode 100755 addon-sdk/source/python-lib/cuddlefish/preflight.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/prefs.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/property_parser.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/rdf.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/runner.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/templates.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/__init__.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/fullName/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/none/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/title/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/two.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/data/text.data delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/unreachable.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/msg.txt delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/subdir/submsg.txt delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/docs/third_party.md delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/lib/third-party.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/package.json delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_init.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_licenses.py delete mode 100755 addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_manifest.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_packaging.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_preflight.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_property_parser.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_runner.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_util.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_version.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/util.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/version_comparator.py delete mode 100644 addon-sdk/source/python-lib/cuddlefish/xpi.py delete mode 100644 addon-sdk/source/python-lib/jetpack_sdk_env.py delete mode 100644 addon-sdk/source/python-lib/mozrunner/__init__.py delete mode 100644 addon-sdk/source/python-lib/mozrunner/killableprocess.py delete mode 100644 addon-sdk/source/python-lib/mozrunner/qijo.py delete mode 100644 addon-sdk/source/python-lib/mozrunner/winprocess.py delete mode 100644 addon-sdk/source/python-lib/mozrunner/wpk.py delete mode 100644 addon-sdk/source/python-lib/plural-rules-generator.py delete mode 100644 addon-sdk/source/python-lib/simplejson/LICENSE.txt delete mode 100644 addon-sdk/source/python-lib/simplejson/__init__.py delete mode 100644 addon-sdk/source/python-lib/simplejson/decoder.py delete mode 100644 addon-sdk/source/python-lib/simplejson/encoder.py delete mode 100644 addon-sdk/source/python-lib/simplejson/scanner.py delete mode 100644 addon-sdk/source/python-lib/simplejson/tool.py delete mode 100644 addon-sdk/source/test/addons/addon-manager/lib/main.js delete mode 100644 addon-sdk/source/test/addons/addon-manager/lib/test-main.js delete mode 100644 addon-sdk/source/test/addons/addon-manager/package.json delete mode 100644 addon-sdk/source/test/addons/author-email/main.js delete mode 100644 addon-sdk/source/test/addons/author-email/package.json delete mode 100644 addon-sdk/source/test/addons/child_process/index.js delete mode 100644 addon-sdk/source/test/addons/child_process/package.json delete mode 100644 addon-sdk/source/test/addons/chrome/chrome.manifest delete mode 100644 addon-sdk/source/test/addons/chrome/chrome/content/new-window.xul delete mode 100644 addon-sdk/source/test/addons/chrome/chrome/content/panel.html delete mode 100644 addon-sdk/source/test/addons/chrome/chrome/locale/en-US/description.properties delete mode 100644 addon-sdk/source/test/addons/chrome/chrome/locale/ja-JP/description.properties delete mode 100644 addon-sdk/source/test/addons/chrome/chrome/skin/style.css delete mode 100644 addon-sdk/source/test/addons/chrome/data/panel.js delete mode 100644 addon-sdk/source/test/addons/chrome/main.js delete mode 100644 addon-sdk/source/test/addons/chrome/package.json delete mode 100644 addon-sdk/source/test/addons/content-permissions/httpd.js delete mode 100644 addon-sdk/source/test/addons/content-permissions/main.js delete mode 100644 addon-sdk/source/test/addons/content-permissions/package.json delete mode 100644 addon-sdk/source/test/addons/content-script-messages-latency/httpd.js delete mode 100644 addon-sdk/source/test/addons/content-script-messages-latency/main.js delete mode 100644 addon-sdk/source/test/addons/content-script-messages-latency/package.json delete mode 100644 addon-sdk/source/test/addons/contributors/main.js delete mode 100644 addon-sdk/source/test/addons/contributors/package.json delete mode 100644 addon-sdk/source/test/addons/curly-id/lib/main.js delete mode 100644 addon-sdk/source/test/addons/curly-id/package.json delete mode 100644 addon-sdk/source/test/addons/developers/main.js delete mode 100644 addon-sdk/source/test/addons/developers/package.json delete mode 100644 addon-sdk/source/test/addons/e10s-content/data/test-contentScriptFile.js delete mode 100644 addon-sdk/source/test/addons/e10s-content/data/test-page-worker.html delete mode 100644 addon-sdk/source/test/addons/e10s-content/data/test-page-worker.js delete mode 100644 addon-sdk/source/test/addons/e10s-content/data/test.html delete mode 100644 addon-sdk/source/test/addons/e10s-content/lib/fixtures.js delete mode 100644 addon-sdk/source/test/addons/e10s-content/lib/httpd.js delete mode 100644 addon-sdk/source/test/addons/e10s-content/lib/main.js delete mode 100644 addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js delete mode 100644 addon-sdk/source/test/addons/e10s-content/lib/test-content-worker.js delete mode 100644 addon-sdk/source/test/addons/e10s-content/lib/test-page-worker.js delete mode 100644 addon-sdk/source/test/addons/e10s-content/package.json delete mode 100644 addon-sdk/source/test/addons/e10s-l10n/data/test-localization.html delete mode 100644 addon-sdk/source/test/addons/e10s-l10n/locale/en.properties delete mode 100644 addon-sdk/source/test/addons/e10s-l10n/locale/eo.properties delete mode 100644 addon-sdk/source/test/addons/e10s-l10n/locale/fr-FR.properties delete mode 100644 addon-sdk/source/test/addons/e10s-l10n/main.js delete mode 100644 addon-sdk/source/test/addons/e10s-l10n/package.json delete mode 100644 addon-sdk/source/test/addons/e10s-remote/main.js delete mode 100644 addon-sdk/source/test/addons/e10s-remote/package.json delete mode 100644 addon-sdk/source/test/addons/e10s-remote/remote-module.js delete mode 100644 addon-sdk/source/test/addons/e10s-remote/utils.js delete mode 100644 addon-sdk/source/test/addons/e10s-tabs/lib/main.js delete mode 100644 addon-sdk/source/test/addons/e10s-tabs/lib/private-browsing/helper.js delete mode 100644 addon-sdk/source/test/addons/e10s-tabs/lib/test-tab-events.js delete mode 100644 addon-sdk/source/test/addons/e10s-tabs/lib/test-tab-observer.js delete mode 100644 addon-sdk/source/test/addons/e10s-tabs/lib/test-tab-utils.js delete mode 100644 addon-sdk/source/test/addons/e10s-tabs/lib/test-tab.js delete mode 100644 addon-sdk/source/test/addons/e10s-tabs/package.json delete mode 100644 addon-sdk/source/test/addons/e10s/lib/main.js delete mode 100644 addon-sdk/source/test/addons/e10s/package.json delete mode 100644 addon-sdk/source/test/addons/embedded-webextension/main.js delete mode 100644 addon-sdk/source/test/addons/embedded-webextension/package.json delete mode 100644 addon-sdk/source/test/addons/embedded-webextension/webextension/background-page.js delete mode 100644 addon-sdk/source/test/addons/embedded-webextension/webextension/content-script.js delete mode 100644 addon-sdk/source/test/addons/embedded-webextension/webextension/manifest.json delete mode 100644 addon-sdk/source/test/addons/jetpack-addon.ini delete mode 100644 addon-sdk/source/test/addons/l10n-properties/app-extension/application.ini delete mode 100644 addon-sdk/source/test/addons/l10n-properties/app-extension/bootstrap.js delete mode 100644 addon-sdk/source/test/addons/l10n-properties/app-extension/install.rdf delete mode 100644 addon-sdk/source/test/addons/l10n-properties/app-extension/locale/en-GB.properties delete mode 100644 addon-sdk/source/test/addons/l10n-properties/app-extension/locale/en-US.properties delete mode 100644 addon-sdk/source/test/addons/l10n-properties/app-extension/locale/eo.properties delete mode 100644 addon-sdk/source/test/addons/l10n-properties/app-extension/locale/fr-FR.properties delete mode 100644 addon-sdk/source/test/addons/l10n-properties/data/test-localization.html delete mode 100644 addon-sdk/source/test/addons/l10n-properties/main.js delete mode 100644 addon-sdk/source/test/addons/l10n-properties/package.json delete mode 100644 addon-sdk/source/test/addons/l10n/data/test-localization.html delete mode 100644 addon-sdk/source/test/addons/l10n/locale/en.properties delete mode 100644 addon-sdk/source/test/addons/l10n/locale/eo.properties delete mode 100644 addon-sdk/source/test/addons/l10n/locale/fr-FR.properties delete mode 100644 addon-sdk/source/test/addons/l10n/main.js delete mode 100644 addon-sdk/source/test/addons/l10n/package.json delete mode 100644 addon-sdk/source/test/addons/layout-change/lib/main.js delete mode 100644 addon-sdk/source/test/addons/layout-change/lib/test-cuddlefish-loader.js delete mode 100644 addon-sdk/source/test/addons/layout-change/lib/test-toolkit-loader.js delete mode 100644 addon-sdk/source/test/addons/layout-change/package.json delete mode 100644 addon-sdk/source/test/addons/main/main.js delete mode 100644 addon-sdk/source/test/addons/main/package.json delete mode 100644 addon-sdk/source/test/addons/name-in-numbers-plus/index.js delete mode 100644 addon-sdk/source/test/addons/name-in-numbers-plus/package.json delete mode 100644 addon-sdk/source/test/addons/name-in-numbers/index.js delete mode 100644 addon-sdk/source/test/addons/name-in-numbers/package.json delete mode 100644 addon-sdk/source/test/addons/packaging/main.js delete mode 100644 addon-sdk/source/test/addons/packaging/package.json delete mode 100644 addon-sdk/source/test/addons/packed/main.js delete mode 100644 addon-sdk/source/test/addons/packed/package.json delete mode 100644 addon-sdk/source/test/addons/page-mod-debugger-post/data/index.html delete mode 100644 addon-sdk/source/test/addons/page-mod-debugger-post/data/script.js delete mode 100644 addon-sdk/source/test/addons/page-mod-debugger-post/main.js delete mode 100644 addon-sdk/source/test/addons/page-mod-debugger-post/package.json delete mode 100644 addon-sdk/source/test/addons/page-mod-debugger-pre/data/index.html delete mode 100644 addon-sdk/source/test/addons/page-mod-debugger-pre/data/script.js delete mode 100644 addon-sdk/source/test/addons/page-mod-debugger-pre/main.js delete mode 100644 addon-sdk/source/test/addons/page-mod-debugger-pre/package.json delete mode 100644 addon-sdk/source/test/addons/page-worker/data/page.html delete mode 100644 addon-sdk/source/test/addons/page-worker/data/page.js delete mode 100644 addon-sdk/source/test/addons/page-worker/main.js delete mode 100644 addon-sdk/source/test/addons/page-worker/package.json delete mode 100644 addon-sdk/source/test/addons/places/lib/favicon-helpers.js delete mode 100644 addon-sdk/source/test/addons/places/lib/httpd.js delete mode 100644 addon-sdk/source/test/addons/places/lib/main.js delete mode 100644 addon-sdk/source/test/addons/places/lib/places-helper.js delete mode 100644 addon-sdk/source/test/addons/places/lib/test-places-bookmarks.js delete mode 100644 addon-sdk/source/test/addons/places/lib/test-places-events.js delete mode 100644 addon-sdk/source/test/addons/places/lib/test-places-favicon.js delete mode 100644 addon-sdk/source/test/addons/places/lib/test-places-history.js delete mode 100644 addon-sdk/source/test/addons/places/lib/test-places-host.js delete mode 100644 addon-sdk/source/test/addons/places/lib/test-places-utils.js delete mode 100644 addon-sdk/source/test/addons/places/package.json delete mode 100644 addon-sdk/source/test/addons/predefined-id-with-at/lib/main.js delete mode 100644 addon-sdk/source/test/addons/predefined-id-with-at/package.json delete mode 100644 addon-sdk/source/test/addons/preferences-branch/lib/main.js delete mode 100644 addon-sdk/source/test/addons/preferences-branch/package.json delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/main.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/package.json delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/sidebar/utils.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-page-mod.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-panel.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-private-browsing.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-selection.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-sidebar.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js delete mode 100644 addon-sdk/source/test/addons/private-browsing-supported/test-windows.js delete mode 100644 addon-sdk/source/test/addons/remote/main.js delete mode 100644 addon-sdk/source/test/addons/remote/package.json delete mode 100644 addon-sdk/source/test/addons/remote/remote-module.js delete mode 100644 addon-sdk/source/test/addons/remote/utils.js delete mode 100644 addon-sdk/source/test/addons/require/list.js delete mode 100644 addon-sdk/source/test/addons/require/main.js delete mode 100644 addon-sdk/source/test/addons/require/multiple/a.js delete mode 100644 addon-sdk/source/test/addons/require/multiple/b.js delete mode 100644 addon-sdk/source/test/addons/require/package.json delete mode 100644 addon-sdk/source/test/addons/require/packages/tabs/main.js delete mode 100644 addon-sdk/source/test/addons/require/packages/tabs/package.json delete mode 100644 addon-sdk/source/test/addons/require/packages/tabs/page-mod.js delete mode 100644 addon-sdk/source/test/addons/require/same-folder.js delete mode 100644 addon-sdk/source/test/addons/require/sub-folder/module.js delete mode 100644 addon-sdk/source/test/addons/require/tabs.js delete mode 100644 addon-sdk/source/test/addons/self/data/data.md delete mode 100644 addon-sdk/source/test/addons/self/main.js delete mode 100644 addon-sdk/source/test/addons/self/package.json delete mode 100644 addon-sdk/source/test/addons/simple-prefs-l10n/locale/en.properties delete mode 100644 addon-sdk/source/test/addons/simple-prefs-l10n/main.js delete mode 100644 addon-sdk/source/test/addons/simple-prefs-l10n/package.json delete mode 100644 addon-sdk/source/test/addons/simple-prefs-regression/app-extension/application.ini delete mode 100644 addon-sdk/source/test/addons/simple-prefs-regression/app-extension/bootstrap.js delete mode 100644 addon-sdk/source/test/addons/simple-prefs-regression/app-extension/defaults/preferences/prefs.js delete mode 100644 addon-sdk/source/test/addons/simple-prefs-regression/app-extension/install.rdf delete mode 100644 addon-sdk/source/test/addons/simple-prefs-regression/app-extension/options.xul delete mode 100644 addon-sdk/source/test/addons/simple-prefs-regression/lib/main.js delete mode 100644 addon-sdk/source/test/addons/simple-prefs-regression/package.json delete mode 100644 addon-sdk/source/test/addons/simple-prefs/lib/main.js delete mode 100644 addon-sdk/source/test/addons/simple-prefs/package.json delete mode 100644 addon-sdk/source/test/addons/standard-id/lib/main.js delete mode 100644 addon-sdk/source/test/addons/standard-id/package.json delete mode 100644 addon-sdk/source/test/addons/tab-close-on-startup/main.js delete mode 100644 addon-sdk/source/test/addons/tab-close-on-startup/package.json delete mode 100644 addon-sdk/source/test/addons/toolkit-require-reload/main.js delete mode 100644 addon-sdk/source/test/addons/toolkit-require-reload/package.json delete mode 100644 addon-sdk/source/test/addons/translators/main.js delete mode 100644 addon-sdk/source/test/addons/translators/package.json delete mode 100644 addon-sdk/source/test/addons/unsafe-content-script/main.js delete mode 100644 addon-sdk/source/test/addons/unsafe-content-script/package.json delete mode 100644 addon-sdk/source/test/buffers/test-read-types.js delete mode 100644 addon-sdk/source/test/buffers/test-write-types.js delete mode 100644 addon-sdk/source/test/commonjs-test-adapter/asserts.js delete mode 100644 addon-sdk/source/test/context-menu/framescript.js delete mode 100644 addon-sdk/source/test/context-menu/test-helper.js delete mode 100644 addon-sdk/source/test/context-menu/util.js delete mode 100644 addon-sdk/source/test/event/helpers.js delete mode 100644 addon-sdk/source/test/fixtures.js delete mode 100644 addon-sdk/source/test/fixtures/addon-install-unit-test@mozilla.com.xpi delete mode 100644 addon-sdk/source/test/fixtures/addon-sdk/data/border-style.css delete mode 100644 addon-sdk/source/test/fixtures/addon-sdk/data/test-contentScriptFile.js delete mode 100644 addon-sdk/source/test/fixtures/addon-sdk/data/test-page-worker.html delete mode 100644 addon-sdk/source/test/fixtures/addon-sdk/data/test-page-worker.js delete mode 100644 addon-sdk/source/test/fixtures/addon-sdk/data/test.html delete mode 100644 addon-sdk/source/test/fixtures/addon/bootstrap.js delete mode 100644 addon-sdk/source/test/fixtures/addon/index.js delete mode 100644 addon-sdk/source/test/fixtures/addon/package.json delete mode 100644 addon-sdk/source/test/fixtures/bootstrap-addon/META-INF/manifest.mf delete mode 100644 addon-sdk/source/test/fixtures/bootstrap-addon/META-INF/mozilla.rsa delete mode 100644 addon-sdk/source/test/fixtures/bootstrap-addon/META-INF/mozilla.sf delete mode 100644 addon-sdk/source/test/fixtures/bootstrap-addon/bootstrap.js delete mode 100644 addon-sdk/source/test/fixtures/bootstrap-addon/install.rdf delete mode 100644 addon-sdk/source/test/fixtures/bootstrap-addon/options.xul delete mode 100644 addon-sdk/source/test/fixtures/bootstrap/utils.js delete mode 100644 addon-sdk/source/test/fixtures/border-style.css delete mode 100644 addon-sdk/source/test/fixtures/child-process-scripts.js delete mode 100644 addon-sdk/source/test/fixtures/chrome-worker/addEventListener.js delete mode 100644 addon-sdk/source/test/fixtures/chrome-worker/jsctypes.js delete mode 100644 addon-sdk/source/test/fixtures/chrome-worker/onerror.js delete mode 100644 addon-sdk/source/test/fixtures/chrome-worker/onmessage.js delete mode 100644 addon-sdk/source/test/fixtures/chrome-worker/setTimeout.js delete mode 100644 addon-sdk/source/test/fixtures/chrome-worker/xhr.js delete mode 100644 addon-sdk/source/test/fixtures/create_xpi.py delete mode 100644 addon-sdk/source/test/fixtures/es5.js delete mode 100644 addon-sdk/source/test/fixtures/include-file.css delete mode 100644 addon-sdk/source/test/fixtures/index.html delete mode 100644 addon-sdk/source/test/fixtures/jsm-package/Test.jsm delete mode 100644 addon-sdk/source/test/fixtures/jsm-package/index.js delete mode 100644 addon-sdk/source/test/fixtures/jsm-package/package.json delete mode 100644 addon-sdk/source/test/fixtures/loader/cycles/a.js delete mode 100644 addon-sdk/source/test/fixtures/loader/cycles/b.js delete mode 100644 addon-sdk/source/test/fixtures/loader/cycles/c.js delete mode 100644 addon-sdk/source/test/fixtures/loader/cycles/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/errors/boomer.js delete mode 100644 addon-sdk/source/test/fixtures/loader/errors/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/exceptions/boomer.js delete mode 100644 addon-sdk/source/test/fixtures/loader/exceptions/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/globals/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/json/invalid.json delete mode 100644 addon-sdk/source/test/fixtures/loader/json/manifest.json delete mode 100644 addon-sdk/source/test/fixtures/loader/json/mutation.json delete mode 100644 addon-sdk/source/test/fixtures/loader/json/nodotjson.json.js delete mode 100644 addon-sdk/source/test/fixtures/loader/json/test.json delete mode 100644 addon-sdk/source/test/fixtures/loader/json/test.json.js delete mode 100644 addon-sdk/source/test/fixtures/loader/lazy/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/missing-twice/file.json delete mode 100644 addon-sdk/source/test/fixtures/loader/missing-twice/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/missing/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/self/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/syntax-error/error.js delete mode 100644 addon-sdk/source/test/fixtures/loader/syntax-error/main.js delete mode 100644 addon-sdk/source/test/fixtures/loader/unsupported/fennec.js delete mode 100644 addon-sdk/source/test/fixtures/loader/unsupported/firefox.js delete mode 100644 addon-sdk/source/test/fixtures/mofo_logo.SVG delete mode 100644 addon-sdk/source/test/fixtures/moz.build delete mode 100644 addon-sdk/source/test/fixtures/moz_favicon.ico delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/dir/a.js delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/dir/a/index.js delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/dir/b.js delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/dir/c.js delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/dir/dummy.js delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/dir/test.jsm delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/expectedmap.json delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/index.js delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/newmodule/index.js delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/newmodule/lib/file.js delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/newmodule/package.json delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/package.json delete mode 100644 addon-sdk/source/test/fixtures/native-addon-test/utils/index.js delete mode 100644 addon-sdk/source/test/fixtures/native-overrides-test/ignore.js delete mode 100644 addon-sdk/source/test/fixtures/native-overrides-test/index.js delete mode 100644 addon-sdk/source/test/fixtures/native-overrides-test/lib/ignore.js delete mode 100644 addon-sdk/source/test/fixtures/native-overrides-test/lib/internal.js delete mode 100644 addon-sdk/source/test/fixtures/native-overrides-test/lib/tabs.js delete mode 100644 addon-sdk/source/test/fixtures/native-overrides-test/package.json delete mode 100644 addon-sdk/source/test/fixtures/preferences/curly-id/package.json delete mode 100644 addon-sdk/source/test/fixtures/preferences/no-prefs/package.json delete mode 100644 addon-sdk/source/test/fixtures/preferences/preferences-branch/package.json delete mode 100644 addon-sdk/source/test/fixtures/preferences/simple-prefs/package.json delete mode 100644 addon-sdk/source/test/fixtures/sandbox-complex-character.js delete mode 100644 addon-sdk/source/test/fixtures/sandbox-normal.js delete mode 100644 addon-sdk/source/test/fixtures/test-addon-extras-window.html delete mode 100644 addon-sdk/source/test/fixtures/test-addon-extras.html delete mode 100644 addon-sdk/source/test/fixtures/test-contentScriptFile.js delete mode 100644 addon-sdk/source/test/fixtures/test-context-menu.js delete mode 100644 addon-sdk/source/test/fixtures/test-iframe-postmessage.html delete mode 100644 addon-sdk/source/test/fixtures/test-iframe.html delete mode 100644 addon-sdk/source/test/fixtures/test-iframe.js delete mode 100644 addon-sdk/source/test/fixtures/test-message-manager.js delete mode 100644 addon-sdk/source/test/fixtures/test-net-url.txt delete mode 100644 addon-sdk/source/test/fixtures/test-page-mod.html delete mode 100644 addon-sdk/source/test/fixtures/test-sidebar-addon-global.html delete mode 100644 addon-sdk/source/test/fixtures/test-trusted-document.html delete mode 100644 addon-sdk/source/test/fixtures/test.html delete mode 100644 addon-sdk/source/test/fixtures/testLocalXhr.json delete mode 100644 addon-sdk/source/test/framescript-manager/frame-script.js delete mode 100644 addon-sdk/source/test/framescript-manager/pong.js delete mode 100644 addon-sdk/source/test/framescript-util/frame-script.js delete mode 100644 addon-sdk/source/test/jetpack-package.ini delete mode 100644 addon-sdk/source/test/leak/jetpack-package.ini delete mode 100644 addon-sdk/source/test/leak/leak-utils.js delete mode 100644 addon-sdk/source/test/leak/test-leak-event-dom-closed-window.js delete mode 100644 addon-sdk/source/test/leak/test-leak-tab-events.js delete mode 100644 addon-sdk/source/test/leak/test-leak-window-events.js delete mode 100644 addon-sdk/source/test/lib/httpd.js delete mode 100644 addon-sdk/source/test/loader/b2g.js delete mode 100644 addon-sdk/source/test/loader/fixture.js delete mode 100644 addon-sdk/source/test/loader/user-global.js delete mode 100644 addon-sdk/source/test/modules/add.js delete mode 100644 addon-sdk/source/test/modules/async1.js delete mode 100644 addon-sdk/source/test/modules/async2.js delete mode 100644 addon-sdk/source/test/modules/badExportAndReturn.js delete mode 100644 addon-sdk/source/test/modules/badFirst.js delete mode 100644 addon-sdk/source/test/modules/badSecond.js delete mode 100644 addon-sdk/source/test/modules/blue.js delete mode 100644 addon-sdk/source/test/modules/castor.js delete mode 100644 addon-sdk/source/test/modules/cheetah.js delete mode 100644 addon-sdk/source/test/modules/color.js delete mode 100644 addon-sdk/source/test/modules/dupe.js delete mode 100644 addon-sdk/source/test/modules/dupeNested.js delete mode 100644 addon-sdk/source/test/modules/dupeSetExports.js delete mode 100644 addon-sdk/source/test/modules/exportsEquals.js delete mode 100644 addon-sdk/source/test/modules/green.js delete mode 100644 addon-sdk/source/test/modules/lion.js delete mode 100644 addon-sdk/source/test/modules/orange.js delete mode 100644 addon-sdk/source/test/modules/pollux.js delete mode 100644 addon-sdk/source/test/modules/red.js delete mode 100644 addon-sdk/source/test/modules/setExports.js delete mode 100644 addon-sdk/source/test/modules/subtract.js delete mode 100644 addon-sdk/source/test/modules/tiger.js delete mode 100644 addon-sdk/source/test/modules/traditional1.js delete mode 100644 addon-sdk/source/test/modules/traditional2.js delete mode 100644 addon-sdk/source/test/modules/types/cat.js delete mode 100644 addon-sdk/source/test/page-mod/helpers.js delete mode 100644 addon-sdk/source/test/path/test-path.js delete mode 100644 addon-sdk/source/test/preferences/common.json delete mode 100644 addon-sdk/source/test/preferences/e10s-off.json delete mode 100644 addon-sdk/source/test/preferences/e10s-on.json delete mode 100644 addon-sdk/source/test/preferences/firefox.json delete mode 100644 addon-sdk/source/test/preferences/no-connections.json delete mode 100644 addon-sdk/source/test/preferences/test-e10s-preferences.js delete mode 100644 addon-sdk/source/test/preferences/test-preferences.js delete mode 100644 addon-sdk/source/test/preferences/test.json delete mode 100644 addon-sdk/source/test/private-browsing/helper.js delete mode 100644 addon-sdk/source/test/private-browsing/tabs.js delete mode 100644 addon-sdk/source/test/private-browsing/windows.js delete mode 100644 addon-sdk/source/test/querystring/test-querystring.js delete mode 100644 addon-sdk/source/test/sidebar/utils.js delete mode 100644 addon-sdk/source/test/tabs/test-fennec-tabs.js delete mode 100644 addon-sdk/source/test/tabs/test-firefox-tabs.js delete mode 100644 addon-sdk/source/test/tabs/utils.js delete mode 100644 addon-sdk/source/test/test-addon-bootstrap.js delete mode 100644 addon-sdk/source/test/test-addon-extras.js delete mode 100644 addon-sdk/source/test/test-addon-installer.js delete mode 100644 addon-sdk/source/test/test-addon-window.js delete mode 100644 addon-sdk/source/test/test-api-utils.js delete mode 100644 addon-sdk/source/test/test-array.js delete mode 100644 addon-sdk/source/test/test-base64.js delete mode 100644 addon-sdk/source/test/test-bootstrap.js delete mode 100644 addon-sdk/source/test/test-browser-events.js delete mode 100644 addon-sdk/source/test/test-buffer.js delete mode 100644 addon-sdk/source/test/test-byte-streams.js delete mode 100644 addon-sdk/source/test/test-child_process.js delete mode 100644 addon-sdk/source/test/test-chrome.js delete mode 100644 addon-sdk/source/test/test-clipboard.js delete mode 100644 addon-sdk/source/test/test-collection.js delete mode 100644 addon-sdk/source/test/test-commonjs-test-adapter.js delete mode 100644 addon-sdk/source/test/test-content-events.js delete mode 100644 addon-sdk/source/test/test-content-script.js delete mode 100644 addon-sdk/source/test/test-content-sync-worker.js delete mode 100644 addon-sdk/source/test/test-content-worker.js delete mode 100644 addon-sdk/source/test/test-context-menu.html delete mode 100644 addon-sdk/source/test/test-context-menu.js delete mode 100644 addon-sdk/source/test/test-context-menu@2.js delete mode 100644 addon-sdk/source/test/test-cuddlefish.js delete mode 100644 addon-sdk/source/test/test-deprecate.js delete mode 100644 addon-sdk/source/test/test-dev-panel.js delete mode 100644 addon-sdk/source/test/test-diffpatcher.js delete mode 100644 addon-sdk/source/test/test-dispatcher.js delete mode 100644 addon-sdk/source/test/test-disposable.js delete mode 100644 addon-sdk/source/test/test-dom.js delete mode 100644 addon-sdk/source/test/test-environment.js delete mode 100644 addon-sdk/source/test/test-event-core.js delete mode 100644 addon-sdk/source/test/test-event-dom.js delete mode 100644 addon-sdk/source/test/test-event-target.js delete mode 100644 addon-sdk/source/test/test-event-utils.js delete mode 100644 addon-sdk/source/test/test-file.js delete mode 100644 addon-sdk/source/test/test-frame-utils.js delete mode 100644 addon-sdk/source/test/test-framescript-manager.js delete mode 100644 addon-sdk/source/test/test-framescript-util.js delete mode 100644 addon-sdk/source/test/test-fs.js delete mode 100644 addon-sdk/source/test/test-functional.js delete mode 100644 addon-sdk/source/test/test-globals.js delete mode 100644 addon-sdk/source/test/test-heritage.js delete mode 100644 addon-sdk/source/test/test-hidden-frame.js delete mode 100644 addon-sdk/source/test/test-host-events.js delete mode 100644 addon-sdk/source/test/test-hotkeys.js delete mode 100644 addon-sdk/source/test/test-httpd.js delete mode 100644 addon-sdk/source/test/test-indexed-db.js delete mode 100644 addon-sdk/source/test/test-jetpack-id.js delete mode 100644 addon-sdk/source/test/test-keyboard-observer.js delete mode 100644 addon-sdk/source/test/test-keyboard-utils.js delete mode 100644 addon-sdk/source/test/test-l10n-locale.js delete mode 100644 addon-sdk/source/test/test-l10n-plural-rules.js delete mode 100644 addon-sdk/source/test/test-lang-type.js delete mode 100644 addon-sdk/source/test/test-libxul.js delete mode 100644 addon-sdk/source/test/test-list.js delete mode 100644 addon-sdk/source/test/test-loader.js delete mode 100644 addon-sdk/source/test/test-match-pattern.js delete mode 100644 addon-sdk/source/test/test-method.js delete mode 100644 addon-sdk/source/test/test-module.js delete mode 100644 addon-sdk/source/test/test-modules.js delete mode 100644 addon-sdk/source/test/test-mozilla-toolkit-versioning.js delete mode 100644 addon-sdk/source/test/test-mpl2-license-header.js delete mode 100644 addon-sdk/source/test/test-namespace.js delete mode 100644 addon-sdk/source/test/test-native-loader.js delete mode 100644 addon-sdk/source/test/test-native-options.js delete mode 100644 addon-sdk/source/test/test-net-url.js delete mode 100644 addon-sdk/source/test/test-node-os.js delete mode 100644 addon-sdk/source/test/test-notifications.js delete mode 100644 addon-sdk/source/test/test-object.js delete mode 100644 addon-sdk/source/test/test-observers.js delete mode 100644 addon-sdk/source/test/test-page-mod-debug.js delete mode 100644 addon-sdk/source/test/test-page-mod.js delete mode 100644 addon-sdk/source/test/test-page-worker.js delete mode 100644 addon-sdk/source/test/test-panel.js delete mode 100644 addon-sdk/source/test/test-passwords-utils.js delete mode 100644 addon-sdk/source/test/test-passwords.js delete mode 100644 addon-sdk/source/test/test-path.js delete mode 100644 addon-sdk/source/test/test-plain-text-console.js delete mode 100644 addon-sdk/source/test/test-preferences-service.js delete mode 100644 addon-sdk/source/test/test-preferences-target.js delete mode 100644 addon-sdk/source/test/test-private-browsing.js delete mode 100644 addon-sdk/source/test/test-promise.js delete mode 100644 addon-sdk/source/test/test-querystring.js delete mode 100644 addon-sdk/source/test/test-reference.js delete mode 100644 addon-sdk/source/test/test-request.js delete mode 100644 addon-sdk/source/test/test-require.js delete mode 100644 addon-sdk/source/test/test-rules.js delete mode 100644 addon-sdk/source/test/test-sandbox.js delete mode 100644 addon-sdk/source/test/test-selection.js delete mode 100644 addon-sdk/source/test/test-self.js delete mode 100644 addon-sdk/source/test/test-sequence.js delete mode 100644 addon-sdk/source/test/test-set-exports.js delete mode 100644 addon-sdk/source/test/test-shared-require.js delete mode 100644 addon-sdk/source/test/test-simple-prefs.js delete mode 100644 addon-sdk/source/test/test-simple-storage.js delete mode 100644 addon-sdk/source/test/test-system-events.js delete mode 100644 addon-sdk/source/test/test-system-input-output.js delete mode 100644 addon-sdk/source/test/test-system-runtime.js delete mode 100644 addon-sdk/source/test/test-system-startup.js delete mode 100644 addon-sdk/source/test/test-system.js delete mode 100644 addon-sdk/source/test/test-tab-events.js delete mode 100644 addon-sdk/source/test/test-tab-observer.js delete mode 100644 addon-sdk/source/test/test-tab-utils.js delete mode 100644 addon-sdk/source/test/test-tab.js delete mode 100644 addon-sdk/source/test/test-tabs-common.js delete mode 100644 addon-sdk/source/test/test-tabs.js delete mode 100644 addon-sdk/source/test/test-test-addon-file.js delete mode 100644 addon-sdk/source/test/test-test-assert.js delete mode 100644 addon-sdk/source/test/test-test-loader.js delete mode 100644 addon-sdk/source/test/test-test-memory.js delete mode 100644 addon-sdk/source/test/test-test-utils-async.js delete mode 100644 addon-sdk/source/test/test-test-utils-generator.js delete mode 100644 addon-sdk/source/test/test-test-utils-sync.js delete mode 100644 addon-sdk/source/test/test-test-utils.js delete mode 100644 addon-sdk/source/test/test-text-streams.js delete mode 100644 addon-sdk/source/test/test-timer.js delete mode 100644 addon-sdk/source/test/test-traceback.js delete mode 100644 addon-sdk/source/test/test-ui-action-button.js delete mode 100644 addon-sdk/source/test/test-ui-frame.js delete mode 100644 addon-sdk/source/test/test-ui-id.js delete mode 100644 addon-sdk/source/test/test-ui-sidebar-private-browsing.js delete mode 100644 addon-sdk/source/test/test-ui-sidebar.js delete mode 100644 addon-sdk/source/test/test-ui-toggle-button.js delete mode 100644 addon-sdk/source/test/test-ui-toolbar.js delete mode 100644 addon-sdk/source/test/test-unit-test-finder.js delete mode 100644 addon-sdk/source/test/test-unit-test.js delete mode 100644 addon-sdk/source/test/test-unload.js delete mode 100644 addon-sdk/source/test/test-unsupported-skip.js delete mode 100644 addon-sdk/source/test/test-uri-resource.js delete mode 100644 addon-sdk/source/test/test-url.js delete mode 100644 addon-sdk/source/test/test-uuid.js delete mode 100644 addon-sdk/source/test/test-weak-set.js delete mode 100644 addon-sdk/source/test/test-window-events.js delete mode 100644 addon-sdk/source/test/test-window-observer.js delete mode 100644 addon-sdk/source/test/test-window-utils-private-browsing.js delete mode 100644 addon-sdk/source/test/test-window-utils.js delete mode 100644 addon-sdk/source/test/test-window-utils2.js delete mode 100644 addon-sdk/source/test/test-windows-common.js delete mode 100644 addon-sdk/source/test/test-windows.js delete mode 100644 addon-sdk/source/test/test-xhr.js delete mode 100644 addon-sdk/source/test/test-xpcom.js delete mode 100644 addon-sdk/source/test/test-xul-app.js delete mode 100644 addon-sdk/source/test/traits/assert.js delete mode 100644 addon-sdk/source/test/traits/utils.js delete mode 100644 addon-sdk/source/test/util.js delete mode 100644 addon-sdk/source/test/windows/test-fennec-windows.js delete mode 100644 addon-sdk/source/test/windows/test-firefox-windows.js delete mode 100644 addon-sdk/source/test/zip/utils.js delete mode 100644 addon-sdk/test/Math.jsm delete mode 100644 addon-sdk/test/browser.ini delete mode 100644 addon-sdk/test/browser_sdk_loader_chrome.js delete mode 100644 addon-sdk/test/browser_sdk_loader_chrome_in_sdk.js delete mode 100644 addon-sdk/test/browser_sdk_loader_js_modules.js delete mode 100644 addon-sdk/test/browser_sdk_loader_jsm_modules.js delete mode 100644 addon-sdk/test/browser_sdk_loader_json.js delete mode 100644 addon-sdk/test/browser_sdk_loader_sdk_gui_modules.js delete mode 100644 addon-sdk/test/browser_sdk_loader_sdk_modules.js delete mode 100644 addon-sdk/test/data.json delete mode 100644 addon-sdk/test/head.js delete mode 100644 addon-sdk/test/invalid.json delete mode 100644 addon-sdk/test/math.js create mode 100644 toolkit/jetpack/app-extension/application.ini create mode 100644 toolkit/jetpack/app-extension/bootstrap.js create mode 100644 toolkit/jetpack/app-extension/install.rdf create mode 100644 toolkit/jetpack/dev/debuggee.js create mode 100644 toolkit/jetpack/dev/frame-script.js create mode 100644 toolkit/jetpack/dev/panel.js create mode 100644 toolkit/jetpack/dev/panel/view.js create mode 100644 toolkit/jetpack/dev/ports.js create mode 100644 toolkit/jetpack/dev/theme.js create mode 100644 toolkit/jetpack/dev/theme/hooks.js create mode 100644 toolkit/jetpack/dev/toolbox.js create mode 100644 toolkit/jetpack/dev/utils.js create mode 100644 toolkit/jetpack/dev/volcan.js create mode 100644 toolkit/jetpack/diffpatcher/.travis.yml create mode 100644 toolkit/jetpack/diffpatcher/History.md create mode 100644 toolkit/jetpack/diffpatcher/License.md create mode 100644 toolkit/jetpack/diffpatcher/Readme.md create mode 100644 toolkit/jetpack/diffpatcher/diff.js create mode 100644 toolkit/jetpack/diffpatcher/index.js create mode 100644 toolkit/jetpack/diffpatcher/package.json create mode 100644 toolkit/jetpack/diffpatcher/patch.js create mode 100644 toolkit/jetpack/diffpatcher/rebase.js create mode 100644 toolkit/jetpack/diffpatcher/test/common.js create mode 100644 toolkit/jetpack/diffpatcher/test/diff.js create mode 100644 toolkit/jetpack/diffpatcher/test/index.js create mode 100644 toolkit/jetpack/diffpatcher/test/patch.js create mode 100644 toolkit/jetpack/diffpatcher/test/tap.js create mode 100644 toolkit/jetpack/framescript/FrameScriptManager.jsm create mode 100644 toolkit/jetpack/framescript/content.jsm create mode 100644 toolkit/jetpack/framescript/context-menu.js create mode 100644 toolkit/jetpack/framescript/manager.js create mode 100644 toolkit/jetpack/framescript/util.js create mode 100644 toolkit/jetpack/index.js create mode 100644 toolkit/jetpack/jetpack-id/index.js create mode 100644 toolkit/jetpack/jetpack-id/package.json create mode 100644 toolkit/jetpack/method/.travis.yml create mode 100644 toolkit/jetpack/method/History.md create mode 100644 toolkit/jetpack/method/License.md create mode 100644 toolkit/jetpack/method/Readme.md create mode 100644 toolkit/jetpack/method/core.js create mode 100644 toolkit/jetpack/method/package.json create mode 100644 toolkit/jetpack/method/test/browser.js create mode 100644 toolkit/jetpack/method/test/common.js create mode 100644 toolkit/jetpack/modules/system/Startup.js create mode 100644 toolkit/jetpack/modules/system/moz.build create mode 100644 toolkit/jetpack/moz.build create mode 100644 toolkit/jetpack/mozilla-toolkit-versioning/index.js create mode 100644 toolkit/jetpack/mozilla-toolkit-versioning/lib/utils.js create mode 100644 toolkit/jetpack/mozilla-toolkit-versioning/package.json create mode 100644 toolkit/jetpack/node/os.js create mode 100644 toolkit/jetpack/sdk/addon/bootstrap.js create mode 100644 toolkit/jetpack/sdk/addon/events.js create mode 100644 toolkit/jetpack/sdk/addon/host.js create mode 100644 toolkit/jetpack/sdk/addon/installer.js create mode 100644 toolkit/jetpack/sdk/addon/manager.js create mode 100644 toolkit/jetpack/sdk/addon/runner.js create mode 100644 toolkit/jetpack/sdk/addon/window.js create mode 100644 toolkit/jetpack/sdk/base64.js create mode 100644 toolkit/jetpack/sdk/browser/events.js create mode 100644 toolkit/jetpack/sdk/clipboard.js create mode 100644 toolkit/jetpack/sdk/console/plain-text.js create mode 100644 toolkit/jetpack/sdk/console/traceback.js create mode 100644 toolkit/jetpack/sdk/content/content-worker.js create mode 100644 toolkit/jetpack/sdk/content/content.js create mode 100644 toolkit/jetpack/sdk/content/context-menu.js create mode 100644 toolkit/jetpack/sdk/content/events.js create mode 100644 toolkit/jetpack/sdk/content/l10n-html.js create mode 100644 toolkit/jetpack/sdk/content/loader.js create mode 100644 toolkit/jetpack/sdk/content/mod.js create mode 100644 toolkit/jetpack/sdk/content/page-mod.js create mode 100644 toolkit/jetpack/sdk/content/page-worker.js create mode 100644 toolkit/jetpack/sdk/content/sandbox.js create mode 100644 toolkit/jetpack/sdk/content/sandbox/events.js create mode 100644 toolkit/jetpack/sdk/content/tab-events.js create mode 100644 toolkit/jetpack/sdk/content/thumbnail.js create mode 100644 toolkit/jetpack/sdk/content/utils.js create mode 100644 toolkit/jetpack/sdk/content/worker-child.js create mode 100644 toolkit/jetpack/sdk/content/worker.js create mode 100644 toolkit/jetpack/sdk/context-menu.js create mode 100644 toolkit/jetpack/sdk/context-menu/context.js create mode 100644 toolkit/jetpack/sdk/context-menu/core.js create mode 100644 toolkit/jetpack/sdk/context-menu/readers.js create mode 100644 toolkit/jetpack/sdk/context-menu@2.js create mode 100644 toolkit/jetpack/sdk/core/disposable.js create mode 100644 toolkit/jetpack/sdk/core/heritage.js create mode 100644 toolkit/jetpack/sdk/core/namespace.js create mode 100644 toolkit/jetpack/sdk/core/observer.js create mode 100644 toolkit/jetpack/sdk/core/promise.js create mode 100644 toolkit/jetpack/sdk/core/reference.js create mode 100644 toolkit/jetpack/sdk/deprecated/api-utils.js create mode 100644 toolkit/jetpack/sdk/deprecated/events/assembler.js create mode 100644 toolkit/jetpack/sdk/deprecated/sync-worker.js create mode 100644 toolkit/jetpack/sdk/deprecated/unit-test-finder.js create mode 100644 toolkit/jetpack/sdk/deprecated/unit-test.js create mode 100644 toolkit/jetpack/sdk/deprecated/window-utils.js create mode 100644 toolkit/jetpack/sdk/dom/events-shimmed.js create mode 100644 toolkit/jetpack/sdk/dom/events.js create mode 100644 toolkit/jetpack/sdk/dom/events/keys.js create mode 100644 toolkit/jetpack/sdk/event/chrome.js create mode 100644 toolkit/jetpack/sdk/event/core.js create mode 100644 toolkit/jetpack/sdk/event/dom.js create mode 100644 toolkit/jetpack/sdk/event/target.js create mode 100644 toolkit/jetpack/sdk/event/utils.js create mode 100644 toolkit/jetpack/sdk/frame/hidden-frame.js create mode 100644 toolkit/jetpack/sdk/frame/utils.js create mode 100644 toolkit/jetpack/sdk/fs/path.js create mode 100644 toolkit/jetpack/sdk/hotkeys.js create mode 100644 toolkit/jetpack/sdk/indexed-db.js create mode 100644 toolkit/jetpack/sdk/input/browser.js create mode 100644 toolkit/jetpack/sdk/input/customizable-ui.js create mode 100644 toolkit/jetpack/sdk/input/frame.js create mode 100644 toolkit/jetpack/sdk/input/system.js create mode 100644 toolkit/jetpack/sdk/io/buffer.js create mode 100644 toolkit/jetpack/sdk/io/byte-streams.js create mode 100644 toolkit/jetpack/sdk/io/file.js create mode 100644 toolkit/jetpack/sdk/io/fs.js create mode 100644 toolkit/jetpack/sdk/io/stream.js create mode 100644 toolkit/jetpack/sdk/io/text-streams.js create mode 100644 toolkit/jetpack/sdk/keyboard/hotkeys.js create mode 100644 toolkit/jetpack/sdk/keyboard/observer.js create mode 100644 toolkit/jetpack/sdk/keyboard/utils.js create mode 100644 toolkit/jetpack/sdk/l10n.js create mode 100644 toolkit/jetpack/sdk/l10n/core.js create mode 100644 toolkit/jetpack/sdk/l10n/html.js create mode 100644 toolkit/jetpack/sdk/l10n/json/core.js create mode 100644 toolkit/jetpack/sdk/l10n/loader.js create mode 100644 toolkit/jetpack/sdk/l10n/locale.js create mode 100644 toolkit/jetpack/sdk/l10n/plural-rules.js create mode 100644 toolkit/jetpack/sdk/l10n/prefs.js create mode 100644 toolkit/jetpack/sdk/l10n/properties/core.js create mode 100644 toolkit/jetpack/sdk/lang/functional.js create mode 100644 toolkit/jetpack/sdk/lang/functional/concurrent.js create mode 100644 toolkit/jetpack/sdk/lang/functional/core.js create mode 100644 toolkit/jetpack/sdk/lang/functional/helpers.js create mode 100644 toolkit/jetpack/sdk/lang/type.js create mode 100644 toolkit/jetpack/sdk/lang/weak-set.js create mode 100644 toolkit/jetpack/sdk/loader/cuddlefish.js create mode 100644 toolkit/jetpack/sdk/loader/sandbox.js create mode 100644 toolkit/jetpack/sdk/messaging.js create mode 100644 toolkit/jetpack/sdk/model/core.js create mode 100644 toolkit/jetpack/sdk/net/url.js create mode 100644 toolkit/jetpack/sdk/net/xhr.js create mode 100644 toolkit/jetpack/sdk/notifications.js create mode 100644 toolkit/jetpack/sdk/output/system.js create mode 100644 toolkit/jetpack/sdk/page-mod.js create mode 100644 toolkit/jetpack/sdk/page-mod/match-pattern.js create mode 100644 toolkit/jetpack/sdk/page-worker.js create mode 100644 toolkit/jetpack/sdk/panel.js create mode 100644 toolkit/jetpack/sdk/panel/events.js create mode 100644 toolkit/jetpack/sdk/panel/utils.js create mode 100644 toolkit/jetpack/sdk/passwords.js create mode 100644 toolkit/jetpack/sdk/passwords/utils.js create mode 100644 toolkit/jetpack/sdk/places/bookmarks.js create mode 100644 toolkit/jetpack/sdk/places/contract.js create mode 100644 toolkit/jetpack/sdk/places/events.js create mode 100644 toolkit/jetpack/sdk/places/favicon.js create mode 100644 toolkit/jetpack/sdk/places/history.js create mode 100644 toolkit/jetpack/sdk/places/host/host-bookmarks.js create mode 100644 toolkit/jetpack/sdk/places/host/host-query.js create mode 100644 toolkit/jetpack/sdk/places/host/host-tags.js create mode 100644 toolkit/jetpack/sdk/places/utils.js create mode 100644 toolkit/jetpack/sdk/platform/xpcom.js create mode 100644 toolkit/jetpack/sdk/preferences/event-target.js create mode 100644 toolkit/jetpack/sdk/preferences/native-options.js create mode 100644 toolkit/jetpack/sdk/preferences/service.js create mode 100644 toolkit/jetpack/sdk/preferences/utils.js create mode 100644 toolkit/jetpack/sdk/private-browsing.js create mode 100644 toolkit/jetpack/sdk/private-browsing/utils.js create mode 100644 toolkit/jetpack/sdk/querystring.js create mode 100644 toolkit/jetpack/sdk/remote/child.js create mode 100644 toolkit/jetpack/sdk/remote/core.js create mode 100644 toolkit/jetpack/sdk/remote/parent.js create mode 100644 toolkit/jetpack/sdk/remote/utils.js create mode 100644 toolkit/jetpack/sdk/request.js create mode 100644 toolkit/jetpack/sdk/selection.js create mode 100644 toolkit/jetpack/sdk/self.js create mode 100644 toolkit/jetpack/sdk/simple-prefs.js create mode 100644 toolkit/jetpack/sdk/simple-storage.js create mode 100644 toolkit/jetpack/sdk/stylesheet/style.js create mode 100644 toolkit/jetpack/sdk/stylesheet/utils.js create mode 100644 toolkit/jetpack/sdk/system.js create mode 100644 toolkit/jetpack/sdk/system/child_process.js create mode 100644 toolkit/jetpack/sdk/system/child_process/subprocess.js create mode 100644 toolkit/jetpack/sdk/system/environment.js create mode 100644 toolkit/jetpack/sdk/system/events-shimmed.js create mode 100644 toolkit/jetpack/sdk/system/events.js create mode 100644 toolkit/jetpack/sdk/system/globals.js create mode 100644 toolkit/jetpack/sdk/system/process.js create mode 100644 toolkit/jetpack/sdk/system/runtime.js create mode 100644 toolkit/jetpack/sdk/system/unload.js create mode 100644 toolkit/jetpack/sdk/system/xul-app.js create mode 100644 toolkit/jetpack/sdk/system/xul-app.jsm create mode 100644 toolkit/jetpack/sdk/tab/events.js create mode 100644 toolkit/jetpack/sdk/tabs.js create mode 100644 toolkit/jetpack/sdk/tabs/common.js create mode 100644 toolkit/jetpack/sdk/tabs/events.js create mode 100644 toolkit/jetpack/sdk/tabs/helpers.js create mode 100644 toolkit/jetpack/sdk/tabs/namespace.js create mode 100644 toolkit/jetpack/sdk/tabs/observer.js create mode 100644 toolkit/jetpack/sdk/tabs/tab-fennec.js create mode 100644 toolkit/jetpack/sdk/tabs/tab-firefox.js create mode 100644 toolkit/jetpack/sdk/tabs/tab.js create mode 100644 toolkit/jetpack/sdk/tabs/tabs-firefox.js create mode 100644 toolkit/jetpack/sdk/tabs/utils.js create mode 100644 toolkit/jetpack/sdk/tabs/worker.js create mode 100644 toolkit/jetpack/sdk/test.js create mode 100644 toolkit/jetpack/sdk/test/assert.js create mode 100644 toolkit/jetpack/sdk/test/harness.js create mode 100644 toolkit/jetpack/sdk/test/httpd.js create mode 100644 toolkit/jetpack/sdk/test/loader.js create mode 100644 toolkit/jetpack/sdk/test/memory.js create mode 100644 toolkit/jetpack/sdk/test/options.js create mode 100644 toolkit/jetpack/sdk/test/runner.js create mode 100644 toolkit/jetpack/sdk/test/utils.js create mode 100644 toolkit/jetpack/sdk/timers.js create mode 100644 toolkit/jetpack/sdk/ui.js create mode 100644 toolkit/jetpack/sdk/ui/button/action.js create mode 100644 toolkit/jetpack/sdk/ui/button/contract.js create mode 100644 toolkit/jetpack/sdk/ui/button/toggle.js create mode 100644 toolkit/jetpack/sdk/ui/button/view.js create mode 100644 toolkit/jetpack/sdk/ui/button/view/events.js create mode 100644 toolkit/jetpack/sdk/ui/component.js create mode 100644 toolkit/jetpack/sdk/ui/frame.js create mode 100644 toolkit/jetpack/sdk/ui/frame/model.js create mode 100644 toolkit/jetpack/sdk/ui/frame/view.html create mode 100644 toolkit/jetpack/sdk/ui/frame/view.js create mode 100644 toolkit/jetpack/sdk/ui/id.js create mode 100644 toolkit/jetpack/sdk/ui/sidebar.js create mode 100644 toolkit/jetpack/sdk/ui/sidebar/actions.js create mode 100644 toolkit/jetpack/sdk/ui/sidebar/contract.js create mode 100644 toolkit/jetpack/sdk/ui/sidebar/namespace.js create mode 100644 toolkit/jetpack/sdk/ui/sidebar/utils.js create mode 100644 toolkit/jetpack/sdk/ui/sidebar/view.js create mode 100644 toolkit/jetpack/sdk/ui/state.js create mode 100644 toolkit/jetpack/sdk/ui/state/events.js create mode 100644 toolkit/jetpack/sdk/ui/toolbar.js create mode 100644 toolkit/jetpack/sdk/ui/toolbar/model.js create mode 100644 toolkit/jetpack/sdk/ui/toolbar/view.js create mode 100644 toolkit/jetpack/sdk/uri/resource.js create mode 100644 toolkit/jetpack/sdk/url.js create mode 100644 toolkit/jetpack/sdk/url/utils.js create mode 100644 toolkit/jetpack/sdk/util/array.js create mode 100644 toolkit/jetpack/sdk/util/collection.js create mode 100644 toolkit/jetpack/sdk/util/contract.js create mode 100644 toolkit/jetpack/sdk/util/deprecate.js create mode 100644 toolkit/jetpack/sdk/util/dispatcher.js create mode 100644 toolkit/jetpack/sdk/util/list.js create mode 100644 toolkit/jetpack/sdk/util/match-pattern.js create mode 100644 toolkit/jetpack/sdk/util/object.js create mode 100644 toolkit/jetpack/sdk/util/rules.js create mode 100644 toolkit/jetpack/sdk/util/sequence.js create mode 100644 toolkit/jetpack/sdk/util/uuid.js create mode 100644 toolkit/jetpack/sdk/view/core.js create mode 100644 toolkit/jetpack/sdk/webextension.js create mode 100644 toolkit/jetpack/sdk/window/browser.js create mode 100644 toolkit/jetpack/sdk/window/events.js create mode 100644 toolkit/jetpack/sdk/window/helpers.js create mode 100644 toolkit/jetpack/sdk/window/namespace.js create mode 100644 toolkit/jetpack/sdk/window/utils.js create mode 100644 toolkit/jetpack/sdk/windows.js create mode 100644 toolkit/jetpack/sdk/windows/fennec.js create mode 100644 toolkit/jetpack/sdk/windows/firefox.js create mode 100644 toolkit/jetpack/sdk/windows/observer.js create mode 100644 toolkit/jetpack/sdk/windows/tabs-fennec.js create mode 100644 toolkit/jetpack/sdk/worker/utils.js create mode 100644 toolkit/jetpack/sdk/zip/utils.js create mode 100644 toolkit/jetpack/test.js create mode 100644 toolkit/jetpack/toolkit/loader.js create mode 100644 toolkit/jetpack/toolkit/require.js diff --git a/addon-sdk/Makefile.in b/addon-sdk/Makefile.in deleted file mode 100644 index 4ef3e5a73..000000000 --- a/addon-sdk/Makefile.in +++ /dev/null @@ -1,29 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -TESTADDONS = source/test/addons -ADDONSRC = $(srcdir)/$(TESTADDONS) - -include $(topsrcdir)/config/rules.mk - -# This can switch to just zipping the files when native jetpacks land -%.xpi: FORCE - $(PYTHON) $(srcdir)/source/bin/cfx xpi --no-strip-xpi --pkgdir=$(ADDONSRC)/$* --output-file=$@ - -TEST_FILES = \ - $(srcdir)/source/app-extension \ - $(srcdir)/source/bin \ - $(srcdir)/source/python-lib \ - $(srcdir)/source/test \ - $(srcdir)/source/package.json \ - $(srcdir)/source/mapping.json \ - $(NULL) - -# Remove this once the test harness uses the APIs built into Firefox -TEST_FILES += $(srcdir)/source/lib - -PKG_STAGE = $(DIST)/test-stage - -stage-tests-package:: $(TEST_FILES) - $(INSTALL) $^ $(PKG_STAGE)/jetpack diff --git a/addon-sdk/mach_commands.py b/addon-sdk/mach_commands.py deleted file mode 100644 index 36a8a24ba..000000000 --- a/addon-sdk/mach_commands.py +++ /dev/null @@ -1,107 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Integrates the xpcshell test runner with mach. - -from __future__ import absolute_import - -import os - -import mozpack.path as mozpath - -from mozbuild.base import ( - MachCommandBase, -) - -from mach.decorators import ( - CommandArgument, - CommandProvider, - Command, -) - -@CommandProvider -class MachCommands(MachCommandBase): - @Command('generate-addon-sdk-moz-build', category='misc', - description='Generates the moz.build file for the addon-sdk/ directory.') - def run_addon_sdk_moz_build(self, **params): - addon_sdk_dir = mozpath.join(self.topsrcdir, 'addon-sdk') - js_src_dir = mozpath.join(addon_sdk_dir, 'source/lib') - dirs_to_files = {} - - for path, dirs, files in os.walk(js_src_dir): - js_files = [f for f in files if f.endswith(('.js', '.jsm', '.html'))] - if not js_files: - continue - - relative = mozpath.relpath(path, js_src_dir) - dirs_to_files[relative] = js_files - - moz_build = """# AUTOMATICALLY GENERATED FROM mozbuild.template AND mach. DO NOT EDIT. -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -%(moz-build-template)s -if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk": -%(non-b2g-modules)s -%(always-on-modules)s""" - - non_b2g_paths = [ - 'method/test', - 'sdk/ui', - 'sdk/ui/button', - 'sdk/ui/sidebar', - 'sdk/places', - 'sdk/places/host', - 'sdk/tabs', - 'sdk/panel', - 'sdk/frame', - 'sdk/test', - 'sdk/window', - 'sdk/windows', - 'sdk/deprecated', - ] - - non_b2g_modules = [] - always_on_modules = [] - - for d, files in sorted(dirs_to_files.items()): - if d in non_b2g_paths: - non_b2g_modules.append((d, files)) - else: - always_on_modules.append((d, files)) - - def list_to_js_modules(l, indent=''): - js_modules = [] - for d, files in l: - if d == '': - module_path = '' - dir_path = '' - else: - # Ensure that we don't have things like: - # EXTRA_JS_MODULES.commonjs.sdk.private-browsing - # which would be a Python syntax error. - path = d.split('/') - module_path = ''.join('.' + p if p.find('-') == -1 else "['%s']" % p for p in path) - dir_path = d + '/' - filelist = ["'source/lib/%s%s'" % (dir_path, f) - for f in sorted(files, key=lambda x: x.lower())] - js_modules.append("EXTRA_JS_MODULES.commonjs%s += [\n %s,\n]\n" - % (module_path, ',\n '.join(filelist))) - stringified = '\n'.join(js_modules) - # This isn't the same thing as |js_modules|, since |js_modules| had - # embedded newlines. - lines = stringified.split('\n') - # Indent lines while avoiding trailing whitespace. - lines = [indent + line if line else line for line in lines] - return '\n'.join(lines) - - moz_build_output = mozpath.join(addon_sdk_dir, 'moz.build') - moz_build_template = mozpath.join(addon_sdk_dir, 'mozbuild.template') - with open(moz_build_output, 'w') as f, open(moz_build_template, 'r') as t: - substs = { 'moz-build-template': t.read(), - 'non-b2g-modules': list_to_js_modules(non_b2g_modules, - indent=' '), - 'always-on-modules': list_to_js_modules(always_on_modules) } - f.write(moz_build % substs) diff --git a/addon-sdk/moz.build b/addon-sdk/moz.build deleted file mode 100644 index 8be23f1ed..000000000 --- a/addon-sdk/moz.build +++ /dev/null @@ -1,542 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Makefile.in uses a misc target through test_addons_TARGET. -HAS_MISC_RULE = True - -BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] -JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini', - 'source/test/leak/jetpack-package.ini'] -JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini'] - -DIRS += ['source/test/fixtures'] - -addons = [ - 'addon-manager', - 'author-email', - 'child_process', - 'chrome', - 'content-permissions', - 'content-script-messages-latency', - 'contributors', - 'curly-id', - 'developers', - 'e10s-content', - 'e10s-l10n', - 'e10s-remote', - 'e10s-tabs', - 'e10s', - 'embedded-webextension', - 'l10n-properties', - 'l10n', - 'layout-change', - 'main', - 'name-in-numbers-plus', - 'name-in-numbers', - 'packaging', - 'packed', - 'page-mod-debugger-post', - 'page-mod-debugger-pre', - 'page-worker', - 'places', - 'predefined-id-with-at', - 'preferences-branch', - 'private-browsing-supported', - 'remote', - 'require', - 'self', - 'simple-prefs-l10n', - 'simple-prefs-regression', - 'simple-prefs', - 'standard-id', - 'tab-close-on-startup', - 'toolkit-require-reload', - 'translators', - 'unsafe-content-script', -] - -addons = ['%s.xpi' % f for f in addons] -GENERATED_FILES += addons - -TEST_HARNESS_FILES.testing.mochitest['jetpack-addon']['addon-sdk'].source.test.addons += [ - '!%s' % f for f in addons -] - -EXTRA_JS_MODULES.sdk += [ - 'source/app-extension/bootstrap.js', -] - -EXTRA_JS_MODULES.sdk.system += [ - 'source/modules/system/Startup.js', -] - -if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk": - EXTRA_JS_MODULES.commonjs.method.test += [ - 'source/lib/method/test/browser.js', - 'source/lib/method/test/common.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.deprecated += [ - 'source/lib/sdk/deprecated/api-utils.js', - 'source/lib/sdk/deprecated/sync-worker.js', - 'source/lib/sdk/deprecated/unit-test-finder.js', - 'source/lib/sdk/deprecated/unit-test.js', - 'source/lib/sdk/deprecated/window-utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.frame += [ - 'source/lib/sdk/frame/hidden-frame.js', - 'source/lib/sdk/frame/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.panel += [ - 'source/lib/sdk/panel/events.js', - 'source/lib/sdk/panel/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.places += [ - 'source/lib/sdk/places/bookmarks.js', - 'source/lib/sdk/places/contract.js', - 'source/lib/sdk/places/events.js', - 'source/lib/sdk/places/favicon.js', - 'source/lib/sdk/places/history.js', - 'source/lib/sdk/places/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.places.host += [ - 'source/lib/sdk/places/host/host-bookmarks.js', - 'source/lib/sdk/places/host/host-query.js', - 'source/lib/sdk/places/host/host-tags.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.tabs += [ - 'source/lib/sdk/tabs/common.js', - 'source/lib/sdk/tabs/events.js', - 'source/lib/sdk/tabs/helpers.js', - 'source/lib/sdk/tabs/namespace.js', - 'source/lib/sdk/tabs/observer.js', - 'source/lib/sdk/tabs/tab-fennec.js', - 'source/lib/sdk/tabs/tab-firefox.js', - 'source/lib/sdk/tabs/tab.js', - 'source/lib/sdk/tabs/tabs-firefox.js', - 'source/lib/sdk/tabs/utils.js', - 'source/lib/sdk/tabs/worker.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.test += [ - 'source/lib/sdk/test/assert.js', - 'source/lib/sdk/test/harness.js', - 'source/lib/sdk/test/httpd.js', - 'source/lib/sdk/test/loader.js', - 'source/lib/sdk/test/memory.js', - 'source/lib/sdk/test/options.js', - 'source/lib/sdk/test/runner.js', - 'source/lib/sdk/test/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.ui += [ - 'source/lib/sdk/ui/component.js', - 'source/lib/sdk/ui/frame.js', - 'source/lib/sdk/ui/id.js', - 'source/lib/sdk/ui/sidebar.js', - 'source/lib/sdk/ui/state.js', - 'source/lib/sdk/ui/toolbar.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.ui.button += [ - 'source/lib/sdk/ui/button/action.js', - 'source/lib/sdk/ui/button/contract.js', - 'source/lib/sdk/ui/button/toggle.js', - 'source/lib/sdk/ui/button/view.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.ui.sidebar += [ - 'source/lib/sdk/ui/sidebar/actions.js', - 'source/lib/sdk/ui/sidebar/contract.js', - 'source/lib/sdk/ui/sidebar/namespace.js', - 'source/lib/sdk/ui/sidebar/utils.js', - 'source/lib/sdk/ui/sidebar/view.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.window += [ - 'source/lib/sdk/window/browser.js', - 'source/lib/sdk/window/events.js', - 'source/lib/sdk/window/helpers.js', - 'source/lib/sdk/window/namespace.js', - 'source/lib/sdk/window/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.windows += [ - 'source/lib/sdk/windows/fennec.js', - 'source/lib/sdk/windows/firefox.js', - 'source/lib/sdk/windows/observer.js', - 'source/lib/sdk/windows/tabs-fennec.js', - ] - -EXTRA_JS_MODULES.commonjs += [ - 'source/lib/index.js', - 'source/lib/test.js', -] - -EXTRA_JS_MODULES.commonjs.sdk += [ - 'source/lib/sdk/webextension.js', -] - -EXTRA_JS_MODULES.commonjs.dev += [ - 'source/lib/dev/debuggee.js', - 'source/lib/dev/frame-script.js', - 'source/lib/dev/panel.js', - 'source/lib/dev/ports.js', - 'source/lib/dev/theme.js', - 'source/lib/dev/toolbox.js', - 'source/lib/dev/utils.js', - 'source/lib/dev/volcan.js', -] - -EXTRA_JS_MODULES.commonjs.dev.panel += [ - 'source/lib/dev/panel/view.js', -] - -EXTRA_JS_MODULES.commonjs.dev.theme += [ - 'source/lib/dev/theme/hooks.js', -] - -EXTRA_JS_MODULES.commonjs.diffpatcher += [ - 'source/lib/diffpatcher/diff.js', - 'source/lib/diffpatcher/index.js', - 'source/lib/diffpatcher/patch.js', - 'source/lib/diffpatcher/rebase.js', -] - -EXTRA_JS_MODULES.commonjs.diffpatcher.test += [ - 'source/lib/diffpatcher/test/common.js', - 'source/lib/diffpatcher/test/diff.js', - 'source/lib/diffpatcher/test/index.js', - 'source/lib/diffpatcher/test/patch.js', - 'source/lib/diffpatcher/test/tap.js', -] - -EXTRA_JS_MODULES.commonjs.framescript += [ - 'source/lib/framescript/content.jsm', - 'source/lib/framescript/context-menu.js', - 'source/lib/framescript/FrameScriptManager.jsm', - 'source/lib/framescript/manager.js', - 'source/lib/framescript/util.js', -] - -EXTRA_JS_MODULES.commonjs['jetpack-id'] += [ - 'source/lib/jetpack-id/index.js', -] - -EXTRA_JS_MODULES.commonjs.method += [ - 'source/lib/method/core.js', -] - -EXTRA_JS_MODULES.commonjs['mozilla-toolkit-versioning'] += [ - 'source/lib/mozilla-toolkit-versioning/index.js', -] - -EXTRA_JS_MODULES.commonjs['mozilla-toolkit-versioning'].lib += [ - 'source/lib/mozilla-toolkit-versioning/lib/utils.js', -] - -EXTRA_JS_MODULES.commonjs.node += [ - 'source/lib/node/os.js', -] - -EXTRA_JS_MODULES.commonjs.sdk += [ - 'source/lib/sdk/base64.js', - 'source/lib/sdk/clipboard.js', - 'source/lib/sdk/context-menu.js', - 'source/lib/sdk/context-menu@2.js', - 'source/lib/sdk/hotkeys.js', - 'source/lib/sdk/indexed-db.js', - 'source/lib/sdk/l10n.js', - 'source/lib/sdk/messaging.js', - 'source/lib/sdk/notifications.js', - 'source/lib/sdk/page-mod.js', - 'source/lib/sdk/page-worker.js', - 'source/lib/sdk/panel.js', - 'source/lib/sdk/passwords.js', - 'source/lib/sdk/private-browsing.js', - 'source/lib/sdk/querystring.js', - 'source/lib/sdk/request.js', - 'source/lib/sdk/selection.js', - 'source/lib/sdk/self.js', - 'source/lib/sdk/simple-prefs.js', - 'source/lib/sdk/simple-storage.js', - 'source/lib/sdk/system.js', - 'source/lib/sdk/tabs.js', - 'source/lib/sdk/test.js', - 'source/lib/sdk/timers.js', - 'source/lib/sdk/ui.js', - 'source/lib/sdk/url.js', - 'source/lib/sdk/windows.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.addon += [ - 'source/lib/sdk/addon/bootstrap.js', - 'source/lib/sdk/addon/events.js', - 'source/lib/sdk/addon/host.js', - 'source/lib/sdk/addon/installer.js', - 'source/lib/sdk/addon/manager.js', - 'source/lib/sdk/addon/runner.js', - 'source/lib/sdk/addon/window.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.browser += [ - 'source/lib/sdk/browser/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.console += [ - 'source/lib/sdk/console/plain-text.js', - 'source/lib/sdk/console/traceback.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.content += [ - 'source/lib/sdk/content/content-worker.js', - 'source/lib/sdk/content/content.js', - 'source/lib/sdk/content/context-menu.js', - 'source/lib/sdk/content/events.js', - 'source/lib/sdk/content/l10n-html.js', - 'source/lib/sdk/content/loader.js', - 'source/lib/sdk/content/mod.js', - 'source/lib/sdk/content/page-mod.js', - 'source/lib/sdk/content/page-worker.js', - 'source/lib/sdk/content/sandbox.js', - 'source/lib/sdk/content/tab-events.js', - 'source/lib/sdk/content/thumbnail.js', - 'source/lib/sdk/content/utils.js', - 'source/lib/sdk/content/worker-child.js', - 'source/lib/sdk/content/worker.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.content.sandbox += [ - 'source/lib/sdk/content/sandbox/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk['context-menu'] += [ - 'source/lib/sdk/context-menu/context.js', - 'source/lib/sdk/context-menu/core.js', - 'source/lib/sdk/context-menu/readers.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.core += [ - 'source/lib/sdk/core/disposable.js', - 'source/lib/sdk/core/heritage.js', - 'source/lib/sdk/core/namespace.js', - 'source/lib/sdk/core/observer.js', - 'source/lib/sdk/core/promise.js', - 'source/lib/sdk/core/reference.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.deprecated.events += [ - 'source/lib/sdk/deprecated/events/assembler.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.dom += [ - 'source/lib/sdk/dom/events-shimmed.js', - 'source/lib/sdk/dom/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.dom.events += [ - 'source/lib/sdk/dom/events/keys.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.event += [ - 'source/lib/sdk/event/chrome.js', - 'source/lib/sdk/event/core.js', - 'source/lib/sdk/event/dom.js', - 'source/lib/sdk/event/target.js', - 'source/lib/sdk/event/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.fs += [ - 'source/lib/sdk/fs/path.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.input += [ - 'source/lib/sdk/input/browser.js', - 'source/lib/sdk/input/customizable-ui.js', - 'source/lib/sdk/input/frame.js', - 'source/lib/sdk/input/system.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.io += [ - 'source/lib/sdk/io/buffer.js', - 'source/lib/sdk/io/byte-streams.js', - 'source/lib/sdk/io/file.js', - 'source/lib/sdk/io/fs.js', - 'source/lib/sdk/io/stream.js', - 'source/lib/sdk/io/text-streams.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.keyboard += [ - 'source/lib/sdk/keyboard/hotkeys.js', - 'source/lib/sdk/keyboard/observer.js', - 'source/lib/sdk/keyboard/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.l10n += [ - 'source/lib/sdk/l10n/core.js', - 'source/lib/sdk/l10n/html.js', - 'source/lib/sdk/l10n/loader.js', - 'source/lib/sdk/l10n/locale.js', - 'source/lib/sdk/l10n/plural-rules.js', - 'source/lib/sdk/l10n/prefs.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.l10n.json += [ - 'source/lib/sdk/l10n/json/core.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.l10n.properties += [ - 'source/lib/sdk/l10n/properties/core.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.lang += [ - 'source/lib/sdk/lang/functional.js', - 'source/lib/sdk/lang/type.js', - 'source/lib/sdk/lang/weak-set.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.lang.functional += [ - 'source/lib/sdk/lang/functional/concurrent.js', - 'source/lib/sdk/lang/functional/core.js', - 'source/lib/sdk/lang/functional/helpers.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.loader += [ - 'source/lib/sdk/loader/cuddlefish.js', - 'source/lib/sdk/loader/sandbox.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.model += [ - 'source/lib/sdk/model/core.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.net += [ - 'source/lib/sdk/net/url.js', - 'source/lib/sdk/net/xhr.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.output += [ - 'source/lib/sdk/output/system.js', -] - -EXTRA_JS_MODULES.commonjs.sdk['page-mod'] += [ - 'source/lib/sdk/page-mod/match-pattern.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.passwords += [ - 'source/lib/sdk/passwords/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.platform += [ - 'source/lib/sdk/platform/xpcom.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.preferences += [ - 'source/lib/sdk/preferences/event-target.js', - 'source/lib/sdk/preferences/native-options.js', - 'source/lib/sdk/preferences/service.js', - 'source/lib/sdk/preferences/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk['private-browsing'] += [ - 'source/lib/sdk/private-browsing/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.remote += [ - 'source/lib/sdk/remote/child.js', - 'source/lib/sdk/remote/core.js', - 'source/lib/sdk/remote/parent.js', - 'source/lib/sdk/remote/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.stylesheet += [ - 'source/lib/sdk/stylesheet/style.js', - 'source/lib/sdk/stylesheet/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.system += [ - 'source/lib/sdk/system/child_process.js', - 'source/lib/sdk/system/environment.js', - 'source/lib/sdk/system/events-shimmed.js', - 'source/lib/sdk/system/events.js', - 'source/lib/sdk/system/globals.js', - 'source/lib/sdk/system/process.js', - 'source/lib/sdk/system/runtime.js', - 'source/lib/sdk/system/unload.js', - 'source/lib/sdk/system/xul-app.js', - 'source/lib/sdk/system/xul-app.jsm', -] - -EXTRA_JS_MODULES.commonjs.sdk.system.child_process += [ - 'source/lib/sdk/system/child_process/subprocess.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.tab += [ - 'source/lib/sdk/tab/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.ui.button.view += [ - 'source/lib/sdk/ui/button/view/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.ui.frame += [ - 'source/lib/sdk/ui/frame/model.js', - 'source/lib/sdk/ui/frame/view.html', - 'source/lib/sdk/ui/frame/view.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.ui.state += [ - 'source/lib/sdk/ui/state/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.ui.toolbar += [ - 'source/lib/sdk/ui/toolbar/model.js', - 'source/lib/sdk/ui/toolbar/view.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.uri += [ - 'source/lib/sdk/uri/resource.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.url += [ - 'source/lib/sdk/url/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.util += [ - 'source/lib/sdk/util/array.js', - 'source/lib/sdk/util/collection.js', - 'source/lib/sdk/util/contract.js', - 'source/lib/sdk/util/deprecate.js', - 'source/lib/sdk/util/dispatcher.js', - 'source/lib/sdk/util/list.js', - 'source/lib/sdk/util/match-pattern.js', - 'source/lib/sdk/util/object.js', - 'source/lib/sdk/util/rules.js', - 'source/lib/sdk/util/sequence.js', - 'source/lib/sdk/util/uuid.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.view += [ - 'source/lib/sdk/view/core.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.worker += [ - 'source/lib/sdk/worker/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.zip += [ - 'source/lib/sdk/zip/utils.js', -] - -EXTRA_JS_MODULES.commonjs.toolkit += [ - 'source/lib/toolkit/loader.js', - 'source/lib/toolkit/require.js', -] diff --git a/addon-sdk/mozbuild.template b/addon-sdk/mozbuild.template deleted file mode 100644 index 56b377cbd..000000000 --- a/addon-sdk/mozbuild.template +++ /dev/null @@ -1,20 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Makefile.in uses a misc target through test_addons_TARGET. -HAS_MISC_RULE = True - -BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] -JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini'] -JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini'] - -EXTRA_JS_MODULES.sdk += [ - 'source/app-extension/bootstrap.js', -] - -EXTRA_JS_MODULES.sdk.system += [ - 'source/modules/system/Startup.js', -] diff --git a/addon-sdk/source/.gitattributes b/addon-sdk/source/.gitattributes deleted file mode 100644 index 9006725a3..000000000 --- a/addon-sdk/source/.gitattributes +++ /dev/null @@ -1,5 +0,0 @@ -.gitignore export-ignore -.hgignore export-ignore -.hgtags export-ignore -.gitattributes export-ignore -python-lib/cuddlefish/_version.py export-subst diff --git a/addon-sdk/source/.gitignore b/addon-sdk/source/.gitignore deleted file mode 100644 index 80d235bd1..000000000 --- a/addon-sdk/source/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -local.json -python-lib/cuddlefish/app-extension/components/jetpack.xpt -testdocs.tgz -jetpack-sdk-docs.tgz -.test_tmp/ -doc/dev-guide/ -doc/index.html -doc/modules/ -doc/status.md5 -packages/* -node_modules -cache - -# Python -*.pyc - -# OSX -*.DS_Store - -# Windows -*Thumbs.db - -# Ignore subtrees - -# git@github.com:jsantell/jetpack-id.git -lib/jetpack-id/* -!lib/jetpack-id/index.js -!lib/jetpack-id/package.json - -# git@github.com:jsantell/mozilla-toolkit-versioning.git -lib/mozilla-toolkit-versioning/* -!lib/mozilla-toolkit-versioning/index.js -!lib/mozilla-toolkit-versioning/lib -lib/mozilla-toolkit-versioning/lib/* -!lib/mozilla-toolkit-versioning/lib/*.js -!lib/mozilla-toolkit-versioning/package.json diff --git a/addon-sdk/source/.jpmignore b/addon-sdk/source/.jpmignore deleted file mode 100644 index 5c22f9c45..000000000 --- a/addon-sdk/source/.jpmignore +++ /dev/null @@ -1,18 +0,0 @@ -local.json -mapping.json -CONTRIBUTING.md -@addon-sdk.xpi -.* -app-extension/ -bin/ -modules/ -node_modules/ -examples/ -cache/ - -# Python -python-lib/ -*.pyc - -# Windows -*Thumbs.db diff --git a/addon-sdk/source/.travis.yml b/addon-sdk/source/.travis.yml deleted file mode 100644 index 287b62a4f..000000000 --- a/addon-sdk/source/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -sudo: false -language: node_js -node_js: - - "0.12" - -env: - - JPM_FX_DEBUG=0 - - JPM_FX_DEBUG=1 - -notifications: - irc: "irc.mozilla.org#jetpack" - -cache: - directories: - - cache - -before_install: - - "export DISPLAY=:99.0" - - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 -extension RANDR" - -before_script: - - npm install fx-download -g - - npm install gulp -g - - bash bin/fx-download.sh - - export JPM_FIREFOX_BINARY=$TRAVIS_BUILD_DIR/../firefox/firefox - - cd $TRAVIS_BUILD_DIR diff --git a/addon-sdk/source/CONTRIBUTING.md b/addon-sdk/source/CONTRIBUTING.md deleted file mode 100644 index 059df64c4..000000000 --- a/addon-sdk/source/CONTRIBUTING.md +++ /dev/null @@ -1,54 +0,0 @@ -## Overview - -- Changes should follow the [design guidelines], as well as the [coding style guide] -- All changes need tests -- In order to land, changes need a review by one of the Jetpack reviewers -- Changes may need an API review -- Changes may need a review from a Mozilla platform domain-expert - -If you have questions, ask in [#jetpack on IRC][jetpack irc channel] or on the [Jetpack mailing list]. - -## How to Make Code Contributions - -Follow the [standard mozilla contribution guidelines](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Introduction). All contributions and patches should be through Bugzilla. - -Pull requests on github are not accepted and new pull requests on github will be rejected. - -## Good First Bugs - -There is a list of [good first bugs here][good first bugs]. - -## Reviewers - -Changes should be reviewed by someone on the [add-on sdk review team](https://bugzilla.mozilla.org/page.cgi?id=review_suggestions.html#Add-on%20SDK) within Bugzilla. - -Others who might be able to help include: - -- [@mossop] -- [@gozala] -- [@ZER0] -- [@jsantell] -- [@zombie] - -For review of Mozilla platform usage and best practices, ask [@autonome], -[@0c0w3], or [@mossop] to find the domain expert. - -For API and developer ergonomics review, ask [@gozala]. - -[design guidelines]:https://wiki.mozilla.org/Labs/Jetpack/Design_Guidelines -[jetpack irc channel]:irc://irc.mozilla.org/#jetpack -[Jetpack mailing list]:http://groups.google.com/group/mozilla-labs-jetpack -[open bugs]:https://bugzilla.mozilla.org/buglist.cgi?quicksearch=product%3ASDK -[make bug]:https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK&component=general -[test intro]:https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Unit_testing -[test API]:https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/test_assert -[coding style guide]:https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide -[Add-on SDK repo]:https://github.com/mozilla/addon-sdk -[GitHub]:https://github.com/ -[good first bugs]:https://bugzilla.mozilla.org/buglist.cgi?list_id=7345714&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&query_based_on=jetpack-good-1st-bugs&status_whiteboard_type=allwordssubstr&query_format=advanced&status_whiteboard=[good%20first%20bug]&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=VERIFIED&product=Add-on%20SDK&known_name=jetpack-good-1st-bugs - -[@mossop]:https://github.com/mossop/ -[@gozala]:https://github.com/Gozala/ -[@ZER0]:https://github.com/ZER0/ -[@jsantell]:https://github.com/jsantell -[@zombie]:https://github.com/zombie diff --git a/addon-sdk/source/LICENSE b/addon-sdk/source/LICENSE deleted file mode 100644 index 22e1dc915..000000000 --- a/addon-sdk/source/LICENSE +++ /dev/null @@ -1,30 +0,0 @@ -The files which make up the SDK are developed by Mozilla and licensed -under the MPL 2.0 (http://mozilla.org/MPL/2.0/), with the exception of the -components listed below, which are made available by their authors under -the licenses listed alongside. - -syntaxhighlighter ------------------- -doc/static-files/syntaxhighlighter -Made available under the MIT license. - -jQuery ------- -examples/reddit-panel/data/jquery-1.4.4.min.js -examples/annotator/data/jquery-1.4.2.min.js -Made available under the MIT license. - -simplejson ----------- -python-lib/simplejson -Made available under the MIT license. - -Python Markdown ---------------- -python-lib/markdown -Made available under the BSD license. - -LibraryDetector ---------------- -examples/library-detector/data/library-detector.js -Made available under the MIT license. diff --git a/addon-sdk/source/README.md b/addon-sdk/source/README.md deleted file mode 100644 index 4efa86dae..000000000 --- a/addon-sdk/source/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Mozilla Add-on SDK [![Build Status](https://travis-ci.org/mozilla/addon-sdk.png)](https://travis-ci.org/mozilla/addon-sdk) - -We suggest that developers of new add-ons [should look at using WebExtensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions). - -Using the Add-on SDK you can create Firefox add-ons using standard Web technologies: JavaScript, HTML, and CSS. The SDK includes JavaScript APIs which you can use to create add-ons, and tools for creating, running, testing, and packaging add-ons. - -If you find a problem, please [report the bug here](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK). - -## Developing Add-ons - -These resources offer some help: - -* [Add-on SDK Documentation](https://developer.mozilla.org/en-US/Add-ons/SDK) -* [Community Developed Modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules) -* [Jetpack FAQ](https://wiki.mozilla.org/Jetpack/FAQ) -* [StackOverflow Questions](http://stackoverflow.com/questions/tagged/firefox-addon-sdk) -* [Mailing List](https://wiki.mozilla.org/Jetpack#Mailing_list) -* #jetpack on irc.mozilla.org - -## Contributing Code - -Please read these two guides if you wish to make some patches to the addon-sdk: - -* [Contribute Guide](https://github.com/mozilla/addon-sdk/blob/master/CONTRIBUTING.md) -* [Style Guide](https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide) - -## Issues - -We use [bugzilla](https://bugzilla.mozilla.org/) as our issue tracker, here are some useful links: - -* [File a bug](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK) -* [Open bugs](https://bugzilla.mozilla.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&product=Add-on%20SDK&query_format=advanced&order=priority) -* [Good first bugs](https://bugzilla.mozilla.org/buglist.cgi?status_whiteboard=[good+first+bug]&&resolution=---&product=Add-on+SDK) -* [Good next bugs](https://bugzilla.mozilla.org/buglist.cgi?status_whiteboard=[good+next+bug]&&resolution=---&product=Add-on+SDK) diff --git a/addon-sdk/source/app-extension/application.ini b/addon-sdk/source/app-extension/application.ini deleted file mode 100644 index 6cec69a16..000000000 --- a/addon-sdk/source/app-extension/application.ini +++ /dev/null @@ -1,11 +0,0 @@ -[App] -Vendor=Varma -Name=Test App -Version=1.0 -BuildID=20060101 -Copyright=Copyright (c) 2009 Atul Varma -ID=xulapp@toolness.com - -[Gecko] -MinVersion=1.9.2.0 -MaxVersion=2.0.* diff --git a/addon-sdk/source/app-extension/bootstrap.js b/addon-sdk/source/app-extension/bootstrap.js deleted file mode 100644 index c2207c75f..000000000 --- a/addon-sdk/source/app-extension/bootstrap.js +++ /dev/null @@ -1,362 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// @see http://dxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp - -'use strict'; - -// IMPORTANT: Avoid adding any initialization tasks here, if you need to do -// something before add-on is loaded consider addon/runner module instead! - -const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu, - results: Cr, manager: Cm } = Components; -const ioService = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); -const resourceHandler = ioService.getProtocolHandler('resource'). - QueryInterface(Ci.nsIResProtocolHandler); -const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); -const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']. - getService(Ci.mozIJSSubScriptLoader); -const prefService = Cc['@mozilla.org/preferences-service;1']. - getService(Ci.nsIPrefService). - QueryInterface(Ci.nsIPrefBranch); -const appInfo = Cc["@mozilla.org/xre/app-info;1"]. - getService(Ci.nsIXULAppInfo); -const vc = Cc["@mozilla.org/xpcom/version-comparator;1"]. - getService(Ci.nsIVersionComparator); - -const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); - -const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports; - - -const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable', - 'install', 'uninstall', 'upgrade', 'downgrade' ]; - -const bind = Function.call.bind(Function.bind); - -var loader = null; -var unload = null; -var cuddlefishSandbox = null; -var nukeTimer = null; - -var resourceDomains = []; -function setResourceSubstitution(domain, uri) { - resourceDomains.push(domain); - resourceHandler.setSubstitution(domain, uri); -} - -// Utility function that synchronously reads local resource from the given -// `uri` and returns content string. -function readURI(uri) { - let channel = NetUtil.newChannel({ - uri: NetUtil.newURI(uri, 'UTF-8'), - loadUsingSystemPrincipal: true - }); - - let stream = channel.open2(); - - let cstream = Cc['@mozilla.org/intl/converter-input-stream;1']. - createInstance(Ci.nsIConverterInputStream); - cstream.init(stream, 'UTF-8', 0, 0); - - let str = {}; - let data = ''; - let read = 0; - do { - read = cstream.readString(0xffffffff, str); - data += str.value; - } while (read != 0); - - cstream.close(); - - return data; -} - -// We don't do anything on install & uninstall yet, but in a future -// we should allow add-ons to cleanup after uninstall. -function install(data, reason) {} -function uninstall(data, reason) {} - -function startup(data, reasonCode) { - try { - let reason = REASON[reasonCode]; - // URI for the root of the XPI file. - // 'jar:' URI if the addon is packed, 'file:' URI otherwise. - // (Used by l10n module in order to fetch `locale` folder) - let rootURI = data.resourceURI.spec; - - // TODO: Maybe we should perform read harness-options.json asynchronously, - // since we can't do anything until 'sessionstore-windows-restored' anyway. - let options = JSON.parse(readURI(rootURI + './harness-options.json')); - - let id = options.jetpackID; - let name = options.name; - - // Clean the metadata - options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {}; - - // freeze the permissionss - Object.freeze(options.metadata[name]['permissions']); - // freeze the metadata - Object.freeze(options.metadata[name]); - - // Register a new resource 'domain' for this addon which is mapping to - // XPI's `resources` folder. - // Generate the domain name by using jetpack ID, which is the extension ID - // by stripping common characters that doesn't work as a domain name: - 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'); - - let prefixURI = 'resource://' + domain + '/'; - let resourcesURI = ioService.newURI(rootURI + '/resources/', null, null); - setResourceSubstitution(domain, resourcesURI); - - // Create path to URLs mapping supported by loader. - let paths = { - // Relative modules resolve to add-on package lib - './': prefixURI + name + '/lib/', - './tests/': prefixURI + name + '/tests/', - '': 'resource://gre/modules/commonjs/' - }; - - // Maps addon lib and tests ressource folders for each package - paths = Object.keys(options.metadata).reduce(function(result, name) { - result[name + '/'] = prefixURI + name + '/lib/' - result[name + '/tests/'] = prefixURI + name + '/tests/' - return result; - }, paths); - - // We need to map tests folder when we run sdk tests whose package name - // is stripped - if (name == 'addon-sdk') - paths['tests/'] = prefixURI + name + '/tests/'; - - let useBundledSDK = options['force-use-bundled-sdk']; - if (!useBundledSDK) { - try { - useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK"); - } - catch (e) { - // Pref doesn't exist, allow using Firefox shipped SDK - } - } - - // Starting with Firefox 21.0a1, we start using modules shipped into firefox - // Still allow using modules from the xpi if the manifest tell us to do so. - // And only try to look for sdk modules in xpi if the xpi actually ship them - if (options['is-sdk-bundled'] && - (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) { - // Maps sdk module folders to their resource folder - paths[''] = prefixURI + 'addon-sdk/lib/'; - // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder, - // so that it isn't shipped in the xpi. Keep a copy of it in sdk/ folder - // until we no longer support SDK modules in XPI: - paths['test'] = prefixURI + 'addon-sdk/lib/sdk/test.js'; - } - - // Retrieve list of module folder overloads based on preferences in order to - // eventually used a local modules instead of files shipped into Firefox. - let branch = prefService.getBranch('extensions.modules.' + id + '.path'); - paths = branch.getChildList('', {}).reduce(function (result, name) { - // Allows overloading of any sub folder by replacing . by / in pref name - let path = name.substr(1).split('.').join('/'); - // Only accept overloading folder by ensuring always ending with `/` - if (path) path += '/'; - let fileURI = branch.getCharPref(name); - - // On mobile, file URI has to end with a `/` otherwise, setSubstitution - // takes the parent folder instead. - if (fileURI[fileURI.length-1] !== '/') - fileURI += '/'; - - // Maps the given file:// URI to a resource:// in order to avoid various - // failure that happens with file:// URI and be close to production env - let resourcesURI = ioService.newURI(fileURI, null, null); - let resName = 'extensions.modules.' + domain + '.commonjs.path' + name; - setResourceSubstitution(resName, resourcesURI); - - result[path] = 'resource://' + resName + '/'; - return result; - }, paths); - - // Make version 2 of the manifest - let manifest = options.manifest; - - // Import `cuddlefish.js` module using a Sandbox and bootstrap loader. - let cuddlefishPath = 'loader/cuddlefish.js'; - let cuddlefishURI = 'resource://gre/modules/commonjs/sdk/' + cuddlefishPath; - if (paths['sdk/']) { // sdk folder has been overloaded - // (from pref, or cuddlefish is still in the xpi) - cuddlefishURI = paths['sdk/'] + cuddlefishPath; - } - else if (paths['']) { // root modules folder has been overloaded - cuddlefishURI = paths[''] + 'sdk/' + cuddlefishPath; - } - - cuddlefishSandbox = loadSandbox(cuddlefishURI); - let cuddlefish = cuddlefishSandbox.exports; - - // Normalize `options.mainPath` so that it looks like one that will come - // in a new version of linker. - let main = options.mainPath; - - unload = cuddlefish.unload; - loader = cuddlefish.Loader({ - paths: paths, - // modules manifest. - manifest: manifest, - - // Add-on ID used by different APIs as a unique identifier. - id: id, - // Add-on name. - name: name, - // Add-on version. - version: options.metadata[name].version, - // Add-on package descriptor. - metadata: options.metadata[name], - // Add-on load reason. - loadReason: reason, - - prefixURI: prefixURI, - // Add-on URI. - rootURI: rootURI, - // options used by system module. - // File to write 'OK' or 'FAIL' (exit code emulation). - resultFile: options.resultFile, - // Arguments passed as --static-args - staticArgs: options.staticArgs, - - // Option to prevent automatic kill of firefox during tests - noQuit: options.no_quit, - - // Add-on preferences branch name - preferencesBranch: options.preferencesBranch, - - // Arguments related to test runner. - modules: { - '@test/options': { - iterations: options.iterations, - filter: options.filter, - profileMemory: options.profileMemory, - stopOnError: options.stopOnError, - verbose: options.verbose, - parseable: options.parseable, - checkMemory: options.check_memory, - } - } - }); - - let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI); - let require = cuddlefish.Require(loader, module); - - // Init the 'sdk/webextension' module from the bootstrap addon parameter. - require("sdk/webextension").initFromBootstrapAddonParam(data); - - require('sdk/addon/runner').startup(reason, { - loader: loader, - main: main, - prefsURI: rootURI + 'defaults/preferences/prefs.js' - }); - } catch (error) { - dump('Bootstrap error: ' + - (error.message ? error.message : String(error)) + '\n' + - (error.stack || error.fileName + ': ' + error.lineNumber) + '\n'); - throw error; - } -}; - -function loadSandbox(uri) { - let proto = { - sandboxPrototype: { - loadSandbox: loadSandbox, - ChromeWorker: ChromeWorker - } - }; - let sandbox = Cu.Sandbox(systemPrincipal, proto); - // Create a fake commonjs environnement just to enable loading loader.js - // correctly - sandbox.exports = {}; - sandbox.module = { uri: uri, exports: sandbox.exports }; - sandbox.require = function (id) { - if (id !== "chrome") - throw new Error("Bootstrap sandbox `require` method isn't implemented."); - - return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, - CC: bind(CC, Components), components: Components, - ChromeWorker: ChromeWorker }); - }; - scriptLoader.loadSubScript(uri, sandbox, 'UTF-8'); - return sandbox; -} - -function unloadSandbox(sandbox) { - if (Cu.getClassName(sandbox, true) == "Sandbox") - Cu.nukeSandbox(sandbox); -} - -function setTimeout(callback, delay) { - let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - timer.initWithCallback({ notify: callback }, delay, - Ci.nsITimer.TYPE_ONE_SHOT); - return timer; -} - -function shutdown(data, reasonCode) { - let reason = REASON[reasonCode]; - if (loader) { - unload(loader, reason); - unload = null; - - // Don't waste time cleaning up if the application is shutting down - if (reason != "shutdown") { - // Avoid leaking all modules when something goes wrong with one particular - // module. Do not clean it up immediatly in order to allow executing some - // actions on addon disabling. - // We need to keep a reference to the timer, otherwise it is collected - // and won't ever fire. - nukeTimer = setTimeout(nukeModules, 1000); - - // Bug 944951 - bootstrap.js must remove the added resource: URIs on unload - resourceDomains.forEach(domain => { - resourceHandler.setSubstitution(domain, null); - }) - } - } -}; - -function nukeModules() { - nukeTimer = null; - // module objects store `exports` which comes from sandboxes - // We should avoid keeping link to these object to avoid leaking sandboxes - for (let key in loader.modules) { - delete loader.modules[key]; - } - // Direct links to sandboxes should be removed too - for (let key in loader.sandboxes) { - let sandbox = loader.sandboxes[key]; - delete loader.sandboxes[key]; - // Bug 775067: From FF17 we can kill all CCW from a given sandbox - unloadSandbox(sandbox); - } - unloadSandbox(loader.sharedGlobalSandbox); - loader = null; - - // both `toolkit/loader` and `system/xul-app` are loaded as JSM's via - // `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when - // the addon is unload. - - unloadSandbox(cuddlefishSandbox.loaderSandbox); - - // Bug 764840: We need to unload cuddlefish otherwise it will stay alive - // and keep a reference to this compartment. - unloadSandbox(cuddlefishSandbox); - cuddlefishSandbox = null; -} diff --git a/addon-sdk/source/app-extension/install.rdf b/addon-sdk/source/app-extension/install.rdf deleted file mode 100644 index 641d1cc21..000000000 --- a/addon-sdk/source/app-extension/install.rdf +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - xulapp@toolness.com - 1.0 - 2 - true - false - - - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - 26.0 - 30.0 - - - - - Test App - Harness for tests. - Mozilla Corporation - - - - - - diff --git a/addon-sdk/source/bin/activate b/addon-sdk/source/bin/activate deleted file mode 100644 index 0104f7d9d..000000000 --- a/addon-sdk/source/bin/activate +++ /dev/null @@ -1,84 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# This file must be used with "source bin/activate" *from bash* -# you cannot run it directly - -deactivate () { - if [ -n "$_OLD_VIRTUAL_PATH" ] ; then - PATH="$_OLD_VIRTUAL_PATH" - export PATH - unset _OLD_VIRTUAL_PATH - fi - - # This should detect bash and zsh, which have a hash command that must - # be called to get it to forget past commands. Without forgetting - # past commands the $PATH changes we made may not be respected - if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then - hash -r - fi - - if [ -n "$_OLD_VIRTUAL_PS1" ] ; then - PS1="$_OLD_VIRTUAL_PS1" - export PS1 - unset _OLD_VIRTUAL_PS1 - fi - - PYTHONPATH="$_OLD_PYTHONPATH" - export PYTHONPATH - unset _OLD_PYTHONPATH - - unset CUDDLEFISH_ROOT - - unset VIRTUAL_ENV - if [ ! "$1" = "nondestructive" ] ; then - # Self destruct! - unset deactivate - fi -} - -# unset irrelavent variables -deactivate nondestructive - -_OLD_PYTHONPATH="$PYTHONPATH" -_OLD_VIRTUAL_PATH="$PATH" - -VIRTUAL_ENV="`pwd`" - -if [ "x$OSTYPE" = "xmsys" ] ; then - CUDDLEFISH_ROOT="`pwd -W | sed s,/,\\\\\\\\,g`" - PATH="`pwd`/bin:$PATH" - # msys will convert any env vars with PATH in it to use msys - # form and will unconvert before launching - PYTHONPATH="`pwd -W`/python-lib;$PYTHONPATH" -else - CUDDLEFISH_ROOT="$VIRTUAL_ENV" - PYTHONPATH="$VIRTUAL_ENV/python-lib:$PYTHONPATH" - PATH="$VIRTUAL_ENV/bin:$PATH" -fi - -VIRTUAL_ENV="`pwd`" - -export CUDDLEFISH_ROOT -export PYTHONPATH -export PATH - -_OLD_VIRTUAL_PS1="$PS1" -if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then - # special case for Aspen magic directories - # see http://www.zetadev.com/software/aspen/ - PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1" -else - PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1" -fi -export PS1 - -# This should detect bash and zsh, which have a hash command that must -# be called to get it to forget past commands. Without forgetting -# past commands the $PATH changes we made may not be respected -if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then - hash -r -fi - -python -c "from jetpack_sdk_env import welcome; welcome()" diff --git a/addon-sdk/source/bin/activate.bat b/addon-sdk/source/bin/activate.bat deleted file mode 100644 index 7d1f968a7..000000000 --- a/addon-sdk/source/bin/activate.bat +++ /dev/null @@ -1,134 +0,0 @@ -@echo off -rem This Source Code Form is subject to the terms of the Mozilla Public -rem License, v. 2.0. If a copy of the MPL was not distributed with this -rem file, You can obtain one at http://mozilla.org/MPL/2.0/. - -set VIRTUAL_ENV=%~dp0 -set VIRTUAL_ENV=%VIRTUAL_ENV:~0,-5% -set CUDDLEFISH_ROOT=%VIRTUAL_ENV% - -SET PYTHONKEY=SOFTWARE\Python\PythonCore - -rem look for 32-bit windows and python, or 64-bit windows and python - -SET PYTHONVERSION=2.7 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -SET PYTHONVERSION=2.6 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -SET PYTHONVERSION=2.5 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -if not defined ProgramFiles(x86) goto win32 - -rem look for 32-bit python on 64-bit windows - -SET PYTHONKEY=SOFTWARE\Wow6432Node\Python\PythonCore - -SET PYTHONVERSION=2.7 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -SET PYTHONVERSION=2.6 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -SET PYTHONVERSION=2.5 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -:win32 - -SET PYTHONVERSION= -set PYTHONKEY= -echo Warning: Failed to find Python installation directory -goto :EOF - -:FoundPython - -if defined _OLD_PYTHONPATH ( - set PYTHONPATH=%_OLD_PYTHONPATH% -) -if not defined PYTHONPATH ( - set PYTHONPATH=; -) -set _OLD_PYTHONPATH=%PYTHONPATH% -set PYTHONPATH=%VIRTUAL_ENV%\python-lib;%PYTHONPATH% - -if not defined PROMPT ( - set PROMPT=$P$G -) - -if defined _OLD_VIRTUAL_PROMPT ( - set PROMPT=%_OLD_VIRTUAL_PROMPT% -) - -set _OLD_VIRTUAL_PROMPT=%PROMPT% -set PROMPT=(%VIRTUAL_ENV%) %PROMPT% - -if defined _OLD_VIRTUAL_PATH goto OLDPATH -goto SKIPPATH -:OLDPATH -PATH %_OLD_VIRTUAL_PATH% - -:SKIPPATH -set _OLD_VIRTUAL_PATH=%PATH% -PATH %VIRTUAL_ENV%\bin;%PYTHONINSTALL%;%PATH% -set PYTHONKEY= -set PYTHONINSTALL= -set PYTHONVERSION= -set key= -set reg= -set _tokens= -python -c "from jetpack_sdk_env import welcome; welcome()" -GOTO :EOF - -:CheckPython -::CheckPython(retVal, key) -::Reads the registry at %2% and checks if a Python exists there. -::Checks both HKLM and HKCU, then checks the executable actually exists. -SET key=%2% -SET "%~1=" -SET reg=reg -if defined ProgramFiles(x86) ( - rem 32-bit cmd on 64-bit windows - if exist %WINDIR%\sysnative\reg.exe SET reg=%WINDIR%\sysnative\reg.exe -) -rem On Vista+, the last line of output is: -rem (default) REG_SZ the_value -rem (but note the word "default" will be localized. -rem On XP, the last line of output is: -rem \tREG_SZ\tthe_value -rem (not sure if "NO NAME" is localized or not!) -rem SO: we use ")>" as the tokens to split on, then nuke -rem the REG_SZ and any tabs or spaces. -FOR /F "usebackq tokens=2 delims=)>" %%A IN (`%reg% QUERY HKLM\%key% /ve 2^>NUL`) DO SET "%~1=%%A" -rem Remove the REG_SZ -set PYTHONINSTALL=%PYTHONINSTALL:REG_SZ=% -rem Remove tabs (note the literal \t in the next line -set PYTHONINSTALL=%PYTHONINSTALL: =% -rem Remove spaces. -set PYTHONINSTALL=%PYTHONINSTALL: =% -if exist %PYTHONINSTALL%\python.exe goto :EOF -rem It may be a 32bit Python directory built from source, in which case the -rem executable is in the PCBuild directory. -if exist %PYTHONINSTALL%\PCBuild\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild" & goto :EOF) -rem Or maybe a 64bit build directory. -if exist %PYTHONINSTALL%\PCBuild\amd64\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild\amd64" & goto :EOF) - -rem And try HKCU -FOR /F "usebackq tokens=2 delims=)>" %%A IN (`%reg% QUERY HKCU\%key% /ve 2^>NUL`) DO SET "%~1=%%A" -set PYTHONINSTALL=%PYTHONINSTALL:REG_SZ=% -set PYTHONINSTALL=%PYTHONINSTALL: =% -set PYTHONINSTALL=%PYTHONINSTALL: =% -if exist %PYTHONINSTALL%\python.exe goto :EOF -if exist %PYTHONINSTALL%\PCBuild\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild" & goto :EOF) -if exist %PYTHONINSTALL%\PCBuild\amd64\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild\amd64" & goto :EOF) -rem can't find it here, so arrange to try the next key -set PYTHONINSTALL= - -GOTO :EOF diff --git a/addon-sdk/source/bin/activate.fish b/addon-sdk/source/bin/activate.fish deleted file mode 100644 index 1f728b69b..000000000 --- a/addon-sdk/source/bin/activate.fish +++ /dev/null @@ -1,66 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# This file must be used with "source bin/activate.fish" *from fish* -# you cannot run it directly - -# Much of this code is based off of the activate.fish file for the -# virtualenv project. http://ur1.ca/ehmd6 - -function deactivate -d "Exit addon-sdk and return to normal shell environment" - if test -n "$_OLD_VIRTUAL_PATH" - set -gx PATH $_OLD_VIRTUAL_PATH - set -e _OLD_VIRTUAL_PATH - end - - if test -n "$_OLD_PYTHONPATH" - set -gx PYTHONPATH $_OLD_PYTHONPATH - set -e _OLD_PYTHONPATH - end - - if test -n "$_OLD_FISH_PROMPT_OVERRIDE" - functions -e fish_prompt - set -e _OLD_FISH_PROMPT_OVERRIDE - . ( begin - printf "function fish_prompt\n\t#" - functions _old_fish_prompt - end | psub ) - - functions -e _old_fish_prompt - end - - set -e CUDDLEFISH_ROOT - set -e VIRTUAL_ENV - - if test "$argv[1]" != "nondestructive" - functions -e deactivate - end -end - -# unset irrelavent variables -deactivate nondestructive - -set -gx _OLD_PYTHONPATH $PYTHONPATH -set -gx _OLD_VIRTUAL_PATH $PATH -set -gx _OLD_FISH_PROMPT_OVERRIDE "true" - -set VIRTUAL_ENV (pwd) - -set -gx CUDDLEFISH_ROOT $VIRTUAL_ENV -set -gx PYTHONPATH "$VIRTUAL_ENV/python-lib" $PYTHONPATH -set -gx PATH "$VIRTUAL_ENV/bin" $PATH - -# save the current fish_prompt function as the function _old_fish_prompt -. ( begin - printf "function _old_fish_prompt\n\t#" - functions fish_prompt - end | psub ) - -# with the original prompt function renamed, we can override with our own. -function fish_prompt - printf "(%s)%s%s" (basename "$VIRTUAL_ENV") (set_color normal) (_old_fish_prompt) - return -end - -python -c "from jetpack_sdk_env import welcome; welcome()" diff --git a/addon-sdk/source/bin/activate.ps1 b/addon-sdk/source/bin/activate.ps1 deleted file mode 100644 index 5d939d468..000000000 --- a/addon-sdk/source/bin/activate.ps1 +++ /dev/null @@ -1,99 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -$Env:VIRTUAL_ENV = (gl); -$Env:CUDDLEFISH_ROOT = $Env:VIRTUAL_ENV; - -# http://stackoverflow.com/questions/5648931/powershell-test-if-registry-value-exists/5652674#5652674 -Function Test-RegistryValue { - param( - [Alias("PSPath")] - [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - [String]$Path - , - [Parameter(Position = 1, Mandatory = $true)] - [String]$Name - , - [Switch]$PassThru - ) - - process { - if (Test-Path $Path) { - $Key = Get-Item -LiteralPath $Path - if ($Key.GetValue($Name, $null) -ne $null) { - if ($PassThru) { - Get-ItemProperty $Path $Name - } else { - $true - } - } else { - $false - } - } else { - $false - } - } -} - -$WINCURVERKEY = 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion'; -$WIN64 = (Test-RegistryValue $WINCURVERKEY 'ProgramFilesDir (x86)'); - -if($WIN64) { - $PYTHONKEY='HKLM:SOFTWARE\Wow6432Node\Python\PythonCore'; -} -else { - $PYTHONKEY='HKLM:SOFTWARE\Python\PythonCore'; -} - -$Env:PYTHONVERSION = ''; -$Env:PYTHONINSTALL = ''; - -foreach ($version in @('2.6', '2.5', '2.4')) { - if (Test-RegistryValue "$PYTHONKEY\$version\InstallPath" '(default)') { - $Env:PYTHONVERSION = $version; - } -} - -if ($Env:PYTHONVERSION) { - $Env:PYTHONINSTALL = (Get-Item "$PYTHONKEY\$version\InstallPath)").'(default)'; -} - -if ($Env:PYTHONINSTALL) { - $Env:Path += ";$Env:PYTHONINSTALL"; -} - -if (Test-Path Env:_OLD_PYTHONPATH) { - $Env:PYTHONPATH = $Env:_OLD_PYTHONPATH; -} -else { - $Env:PYTHONPATH = ''; -} - -$Env:_OLD_PYTHONPATH=$Env:PYTHONPATH; -$Env:PYTHONPATH= "$Env:VIRTUAL_ENV\python-lib;$Env:PYTHONPATH"; - -if (Test-Path Function:_OLD_VIRTUAL_PROMPT) { - Set-Content Function:Prompt (Get-Content Function:_OLD_VIRTUAL_PROMPT); -} -else { - function global:_OLD_VIRTUAL_PROMPT {} -} - -Set-Content Function:_OLD_VIRTUAL_PROMPT (Get-Content Function:Prompt); - -function global:prompt { "($Env:VIRTUAL_ENV) $(_OLD_VIRTUAL_PROMPT)"; }; - -if (Test-Path Env:_OLD_VIRTUAL_PATH) { - $Env:PATH = $Env:_OLD_VIRTUAL_PATH; -} -else { - $Env:_OLD_VIRTUAL_PATH = $Env:PATH; -} - -$Env:Path="$Env:VIRTUAL_ENV\bin;$Env:Path" - -[System.Console]::WriteLine("Note: this PowerShell SDK activation script is experimental.") - -python -c "from jetpack_sdk_env import welcome; welcome()" - diff --git a/addon-sdk/source/bin/cfx b/addon-sdk/source/bin/cfx deleted file mode 100755 index 3e781faf8..000000000 --- a/addon-sdk/source/bin/cfx +++ /dev/null @@ -1,33 +0,0 @@ -#! /usr/bin/env python -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -import os -import sys - -# set the cuddlefish "root directory" for this process if it's not already -# set in the environment -cuddlefish_root = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))) - -if 'CUDDLEFISH_ROOT' not in os.environ: - os.environ['CUDDLEFISH_ROOT'] = cuddlefish_root - -# add our own python-lib path to the python module search path. -python_lib_dir = os.path.join(cuddlefish_root, "python-lib") -if python_lib_dir not in sys.path: - sys.path.insert(0, python_lib_dir) - -# now export to env so sub-processes get it too -if 'PYTHONPATH' not in os.environ: - os.environ['PYTHONPATH'] = python_lib_dir -elif python_lib_dir not in os.environ['PYTHONPATH'].split(os.pathsep): - paths = os.environ['PYTHONPATH'].split(os.pathsep) - paths.insert(0, python_lib_dir) - os.environ['PYTHONPATH'] = os.pathsep.join(paths) - -import cuddlefish - -if __name__ == '__main__': - cuddlefish.run() diff --git a/addon-sdk/source/bin/cfx.bat b/addon-sdk/source/bin/cfx.bat deleted file mode 100644 index 215b034f5..000000000 --- a/addon-sdk/source/bin/cfx.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -rem This Source Code Form is subject to the terms of the Mozilla Public -rem License, v. 2.0. If a copy of the MPL was not distributed with this -rem file, You can obtain one at http://mozilla.org/MPL/2.0/. - -python "%VIRTUAL_ENV%\bin\cfx" %* diff --git a/addon-sdk/source/bin/deactivate.bat b/addon-sdk/source/bin/deactivate.bat deleted file mode 100644 index e6bcd9293..000000000 --- a/addon-sdk/source/bin/deactivate.bat +++ /dev/null @@ -1,23 +0,0 @@ -@echo off -rem This Source Code Form is subject to the terms of the Mozilla Public -rem License, v. 2.0. If a copy of the MPL was not distributed with this -rem file, You can obtain one at http://mozilla.org/MPL/2.0/. - -if defined _OLD_VIRTUAL_PROMPT ( - set "PROMPT=%_OLD_VIRTUAL_PROMPT%" -) -set _OLD_VIRTUAL_PROMPT= - -if defined _OLD_VIRTUAL_PATH ( - set "PATH=%_OLD_VIRTUAL_PATH%" -) -set _OLD_VIRTUAL_PATH= - -if defined _OLD_PYTHONPATH ( - set "PYTHONPATH=%_OLD_PYTHONPATH%" -) -set _OLD_PYTHONPATH= - -set CUDDLEFISH_ROOT= - -:END diff --git a/addon-sdk/source/bin/fx-download.sh b/addon-sdk/source/bin/fx-download.sh deleted file mode 100644 index 690208a38..000000000 --- a/addon-sdk/source/bin/fx-download.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -if [ "$JPM_FX_DEBUG" = "1" ]; then - fx-download --branch nightly -c prerelease --host ftp.mozilla.org ../firefox --debug -else - fx-download --branch nightly -c prerelease --host ftp.mozilla.org ../firefox -fi diff --git a/addon-sdk/source/bin/integration-scripts/buildbot-run-cfx-helper b/addon-sdk/source/bin/integration-scripts/buildbot-run-cfx-helper deleted file mode 100755 index 56c76ad32..000000000 --- a/addon-sdk/source/bin/integration-scripts/buildbot-run-cfx-helper +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -source ./bin/activate -if [ type -P xvfb-run ] -then - xvfb-run cfx $* -else - cfx $* -fi -deactivate diff --git a/addon-sdk/source/bin/integration-scripts/integration-check b/addon-sdk/source/bin/integration-scripts/integration-check deleted file mode 100644 index 2505c15dc..000000000 --- a/addon-sdk/source/bin/integration-scripts/integration-check +++ /dev/null @@ -1,364 +0,0 @@ -#!/usr/bin/env python -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import signal -import threading -import urllib2, urllib -import zipfile -import tarfile -import subprocess -import optparse -import sys, re -#import win32api - - -class SDK: - def __init__(self): - try: - # Take the current working directory - self.default_path = os.getcwd() - if sys.platform == "win32": - self.mswindows = True - else: - self.mswindows = False - # Take the default home path of the user. - home = os.path.expanduser('~') - - # The following are the parameters that can be used to pass a dynamic URL, a specific path or a binry. The binary is not used yet. It will be used in version 2.0 - # If a dynamic path is to be mentioned, it should start with a '/'. For eg. "/Desktop" - parser = optparse.OptionParser() - parser.add_option('-u', '--url', dest = 'url', default = 'https://ftp.mozilla.org/pub/mozilla.org/labs/jetpack/addon-sdk-latest.zip') - parser.add_option('-p', '--path', dest = 'path', default = self.default_path) - parser.add_option('-b', '--binary', dest = 'binary')#, default='/Applications/Firefox.app') - (options, args) = parser.parse_args() - - # Get the URL from the parameter - self.link = options.url - # Set the base path for the user. If the user supplies the path, use the home variable as well. Else, take the default path of this script as the installation directory. - if options.path!=self.default_path: - if self.mswindows: - self.base_path = home + str(options.path).strip() + '\\' - else: - self.base_path = home + str(options.path).strip() + '/' - else: - if self.mswindows: - self.base_path = str(options.path).strip() + '\\' - else: - self.base_path = str(options.path).strip() + '/' - assert ' ' not in self.base_path, "You cannot have a space in your home path. Please remove the space before you continue." - print('Your Base path is =' + self.base_path) - - # This assignment is not used in this program. It will be used in version 2 of this script. - self.bin = options.binary - # if app or bin is empty, dont pass anything - - # Search for the .zip file or tarball file in the URL. - i = self.link.rfind('/') - - self.fname = self.link[i+1:] - z = re.search('zip',self.fname,re.I) - g = re.search('gz',self.fname,re.I) - if z: - print 'zip file present in the URL.' - self.zip = True - self.gz = False - elif g: - print 'gz file present in the URL' - self.gz = True - self.zip = False - else: - print 'zip/gz file not present. Check the URL.' - return - print("File name is =" + self.fname) - - # Join the base path and the zip/tar file name to crate a complete Local file path. - self.fpath = self.base_path + self.fname - print('Your local file path will be=' + self.fpath) - except AssertionError, e: - print e.args[0] - sys.exit(1) - - # Download function - to download the SDK from the URL to the local machine. - def download(self,url,fpath,fname): - try: - # Start the download - print("Downloading...Please be patient!") - urllib.urlretrieve(url,filename = fname) - print('Download was successful.') - except ValueError: # Handles broken URL errors. - print 'The URL is ether broken or the file does not exist. Please enter the correct URL.' - raise - except urllib2.URLError: # Handles URL errors - print '\nURL not correct. Check again!' - raise - - # Function to extract the downloaded zipfile. - def extract(self, zipfilepath, extfile): - try: - # Timeout is set to 30 seconds. - timeout = 30 - # Change the directory to the location of the zip file. - try: - os.chdir(zipfilepath) - except OSError: - # Will reach here if zip file doesnt exist - print 'O/S Error:' + zipfilepath + 'does not exist' - raise - - # Get the folder name of Jetpack to get the exact version number. - if self.zip: - try: - f = zipfile.ZipFile(extfile, "r") - except IOError as (errno, strerror): # Handles file errors - print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror) - raise - list = f.namelist()[0] - temp_name = list.split('/') - print('Folder Name= ' +temp_name[0]) - self.folder_name = temp_name[0] - elif self.gz: - try: - f = tarfile.open(extfile,'r') - except IOError as (errno, strerror): # Handles file errors - print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror) - raise - list = f.getnames()[0] - temp_name = list.split('/') - print('Folder Name= ' +temp_name[0]) - self.folder_name = temp_name[0] - - print ('Starting to Extract...') - - # Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned- - # timeout, the process is killed. - kill_check = threading.Event() - - if self.zip: - # Call the command to unzip the file. - if self.mswindows: - zipfile.ZipFile.extractall(f) - else: - p = subprocess.Popen('unzip '+extfile, stdout=subprocess.PIPE, shell=True) - pid = p.pid - elif self.gz: - # Call the command to untar the file. - if self.mswindows: - tarfile.TarFile.extractall(f) - else: - p = subprocess.Popen('tar -xf '+extfile, stdout=subprocess.PIPE, shell=True) - pid = p.pid - - #No need to handle for windows because windows automatically replaces old files with new files. It does not ask the user(as it does in Mac/Unix) - if self.mswindows==False: - watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows )) - watch.start() - (stdout, stderr) = p.communicate() - watch.cancel() # if it's still waiting to run - success = not kill_check.isSet() - - # Abort process if process fails. - if not success: - raise RuntimeError - kill_check.clear() - print('Extraction Successful.') - except RuntimeError: - print "Ending the program" - sys.exit(1) - except: - print "Error during file extraction: ", sys.exc_info()[0] - raise - - # Function to run the cfx testall comands and to make sure the SDK is not broken. - def run_testall(self, home_path, folder_name): - try: - timeout = 500 - - self.new_dir = home_path + folder_name - try: - os.chdir(self.new_dir) - except OSError: - # Will reach here if the jetpack 0.X directory doesnt exist - print 'O/S Error: Jetpack directory does not exist at ' + self.new_dir - raise - print '\nStarting tests...' - # Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned- - # timeout, the process is killed. - kill_check = threading.Event() - - # Set the path for the logs. They will be in the parent directory of the Jetpack SDK. - log_path = home_path + 'tests.log' - - # Subprocess call to set up the jetpack environment and to start the tests. Also sends the output to a log file. - if self.bin != None: - if self.mswindows: - p = subprocess.Popen("bin\\activate && cfx testall -a firefox -b \"" + self.bin + "\"" , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc_handle = p._handle - (stdout,stderr) = p.communicate() - else: - p = subprocess.Popen('. bin/activate; cfx testall -a firefox -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - pid = p.pid - (stdout,stderr) = p.communicate() - elif self.bin == None: - if self.mswindows: - p=subprocess.Popen('bin\\activate && cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc_handle = p._handle - (stdout,stderr) = p.communicate() - else: - p = subprocess.Popen('. bin/activate; cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - pid = p.pid - (stdout,stderr) = p.communicate() - - #Write the output to log file - f=open(log_path,"w") - f.write(stdout+stderr) - f.close() - - #Watchdog for timeout process - if self.mswindows: - watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows)) - else: - watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows)) - watch.start() - watch.cancel() # if it's still waiting to run - success = not kill_check.isSet() - if not success: - raise RuntimeError - kill_check.clear() - - if p.returncode!=0: - print('\nAll tests were not successful. Check the test-logs in the jetpack directory.') - result_sdk(home_path) - #sys.exit(1) - raise RuntimeError - else: - ret_code=result_sdk(home_path) - if ret_code==0: - print('\nAll tests were successful. Yay \o/ . Running a sample package test now...') - else: - print ('\nThere were errors during the tests.Take a look at logs') - raise RuntimeError - except RuntimeError: - print "Ending the program" - sys.exit(1) - except: - print "Error during the testall command execution:", sys.exc_info()[0] - raise - - def package(self, example_dir): - try: - timeout = 30 - - print '\nNow Running packaging tests...' - - kill_check = threading.Event() - - # Set the path for the example logs. They will be in the parent directory of the Jetpack SDK. - exlog_path = example_dir + 'test-example.log' - # Subprocess call to test the sample example for packaging. - if self.bin!=None: - if self.mswindows: - p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\\"quitWhenDone\\":true}" -b \"" + self.bin + "\"' , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc_handle = p._handle - (stdout, stderr) = p.communicate() - else: - p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - pid = p.pid - (stdout, stderr) = p.communicate() - elif self.bin==None: - if self.mswindows: - p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\\"quitWhenDone\\":true}"', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc_handle = p._handle - (stdout, stderr) = p.communicate() - else: - p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' ', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - pid = p.pid - (stdout, stderr) = p.communicate() - - #Write the output to log file - f=open(exlog_path,"w") - f.write(stdout+stderr) - f.close() - - #Watch dog for timeout process - if self.mswindows: - watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows)) - else: - watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows)) - watch.start() - watch.cancel() # if it's still waiting to run - success = not kill_check.isSet() - if not success: - raise RuntimeError - kill_check.clear() - - if p.returncode != 0: - print('\nSample tests were not executed correctly. Check the test-example log in jetpack diretory.') - result_example(example_dir) - raise RuntimeError - else: - ret_code=result_example(example_dir) - if ret_code==0: - print('\nAll tests pass. The SDK is working! Yay \o/') - else: - print ('\nTests passed with warning.Take a look at logs') - sys.exit(1) - - except RuntimeError: - print "Ending program" - sys.exit(1) - except: - print "Error during running sample tests:", sys.exc_info()[0] - raise - -def result_sdk(sdk_dir): - log_path = sdk_dir + 'tests.log' - print 'Results are logged at:' + log_path - try: - f = open(log_path,'r') - # Handles file errors - except IOError : - print 'I/O error - Cannot open test log at ' + log_path - raise - - for line in reversed(open(log_path).readlines()): - if line.strip()=='FAIL': - print ('\nOverall result - FAIL. Look at the test log at '+log_path) - return 1 - return 0 - - -def result_example(sdk_dir): - exlog_path = sdk_dir + 'test-example.log' - print 'Sample test results are logged at:' + exlog_path - try: - f = open(exlog_path,'r') - # Handles file errors - except IOError : - print 'I/O error - Cannot open sample test log at ' + exlog_path - raise - - #Read the file in reverse and check for the keyword 'FAIL'. - for line in reversed(open(exlog_path).readlines()): - if line.strip()=='FAIL': - print ('\nOverall result for Sample tests - FAIL. Look at the test log at '+exlog_path) - return 1 - return 0 - -def kill_process(process, kill_check, mswindows): - print '\nProcess Timedout. Killing the process. Please Rerun this script.' - if mswindows: - win32api.TerminateProcess(process, -1) - else: - os.kill(process, signal.SIGKILL) - kill_check.set()# tell the main routine to kill. Used SIGKILL to hard kill the process. - return - -if __name__ == "__main__": - obj = SDK() - obj.download(obj.link,obj.fpath,obj.fname) - obj.extract(obj.base_path,obj.fname) - obj.run_testall(obj.base_path,obj.folder_name) - obj.package(obj.base_path) diff --git a/addon-sdk/source/bin/jpm-test.js b/addon-sdk/source/bin/jpm-test.js deleted file mode 100644 index f22a552ea..000000000 --- a/addon-sdk/source/bin/jpm-test.js +++ /dev/null @@ -1,34 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var Promise = require("promise"); -var Mocha = require("mocha"); -var mocha = new Mocha({ - ui: "bdd", - reporter: "spec", - timeout: 900000 -}); - -var isDebug = require("./node-scripts/utils").isDebug; - -exports.run = function(type) { - return new Promise(function(resolve) { - type = type || ""; - [ - (!isDebug && /^(firefox-bin)?$/.test(type)) && require.resolve("../bin/node-scripts/test.firefox-bin"), - (!isDebug && /^(docs)?$/.test(type)) && require.resolve("../bin/node-scripts/test.docs"), - (!isDebug && /^(ini)?$/.test(type)) && require.resolve("../bin/node-scripts/test.ini"), - (/^(examples)?$/.test(type)) && require.resolve("../bin/node-scripts/test.examples"), - (!isDebug && /^(addons)?$/.test(type)) && require.resolve("../bin/node-scripts/test.addons"), - (!isDebug && /^(modules)?$/.test(type)) && require.resolve("../bin/node-scripts/test.modules"), - ].forEach(function(filepath) { - filepath && mocha.addFile(filepath); - }) - - mocha.run(function(failures) { - resolve(failures); - }); - }); -} diff --git a/addon-sdk/source/bin/node-scripts/apply-patch.js b/addon-sdk/source/bin/node-scripts/apply-patch.js deleted file mode 100644 index 31fbf7d31..000000000 --- a/addon-sdk/source/bin/node-scripts/apply-patch.js +++ /dev/null @@ -1,64 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var path = require("path"); -var cp = require("child_process"); -var fs = require("fs"); -var Promise = require("promise"); -var patcher = require("patch-editor"); -var readParam = require("./utils").readParam; - -var isKeeper = /\/addon-sdk\/source/; - -function apply(options) { - return clean(options).then(function() { - return new Promise(function(resolve) { - var patch = path.resolve(readParam("patch")); - var proc = cp.spawn("git", ["apply", patch]); - proc.stdout.pipe(process.stdout); - proc.stderr.pipe(process.stderr); - proc.on("close", resolve); - }); - }); -} -exports.apply = apply; - -function clean(options) { - return new Promise(function(resolve) { - var patch = path.resolve(readParam("patch")); - if (!patch) { - throw new Error("no --patch was provided."); - } - console.log("Cleaning patch " + patch); - - patcher.getChunks({ patch: patch }).then(function(chunks) { - var keepers = []; - - for (var i = chunks.length - 1; i >= 0; i--) { - var chunk = chunks[i]; - var files = chunk.getFilesChanged(); - - // check if the file changed is related to the addon-sdk/source directory - var keepIt = files.map(function(file) { - return (isKeeper.test(file)); - }).reduce(function(prev, curr) { - return prev || curr; - }, false); - - if (keepIt) { - keepers.push(chunk); - } - } - - var contents = "\n" + keepers.join("\n") + "\n"; - contents = contents.replace(/\/addon-sdk\/source/g, ""); - - fs.writeFileSync(patch, contents, { encoding: "utf8" }); - - console.log("Done cleaning patch."); - }).then(resolve).catch(console.error); - }); -} -exports.clean = clean; diff --git a/addon-sdk/source/bin/node-scripts/test.addons.js b/addon-sdk/source/bin/node-scripts/test.addons.js deleted file mode 100644 index dc7c6dfce..000000000 --- a/addon-sdk/source/bin/node-scripts/test.addons.js +++ /dev/null @@ -1,57 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var utils = require("./utils"); -var path = require("path"); -var fs = require("fs"); -var jpm = utils.run; -var readParam = utils.readParam; -var isDebug = utils.isDebug; - -var addonsPath = path.join(__dirname, "..", "..", "test", "addons"); - -var binary = process.env.JPM_FIREFOX_BINARY || "nightly"; -var filterPattern = readParam("filter"); - -describe("jpm test sdk addons", function () { - fs.readdirSync(addonsPath) - .filter(fileFilter.bind(null, addonsPath)) - .forEach(function (file) { - it(file, function (done) { - var addonPath = path.join(addonsPath, file); - process.chdir(addonPath); - - var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }}; - if (process.env.DISPLAY) { - options.env.DISPLAY = process.env.DISPLAY; - } - if (/^e10s/.test(file)) { - options.e10s = true; - } - - jpm("run", options).then(done).catch(done); - }); - }); -}); - -function fileFilter(root, file) { - var matcher = filterPattern && new RegExp(filterPattern); - if (/^(l10n-properties|simple-prefs|page-mod-debugger)/.test(file)) { - return false; - } - - // filter additional add-ons when using debug builds - if (isDebug) { - if (/^(chrome|e10s)/.test(file)) { - return false; - } - } - - if (matcher && !matcher.test(file)) { - return false; - } - var stat = fs.statSync(path.join(root, file)) - return (stat && stat.isDirectory()); -} diff --git a/addon-sdk/source/bin/node-scripts/test.docs.js b/addon-sdk/source/bin/node-scripts/test.docs.js deleted file mode 100644 index e6aef516d..000000000 --- a/addon-sdk/source/bin/node-scripts/test.docs.js +++ /dev/null @@ -1,145 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var createHash = require('crypto').createHash; -var fs = require("fs"); -var fsExtra = require("fs-extra") -var path = require("path"); -var Promise = require("promise"); -var chai = require("chai"); -var expect = chai.expect; -var teacher = require("teacher"); - -var rootURI = path.join(__dirname, "..", ".."); - -// get a list of words that fail spell check but are still acceptable -var NEW_WORDS = fs.readFileSync(path.join(__dirname, "words.txt")).toString().trim().split("\n"); - -var CACHE_PATH = path.join(__dirname, "..", "..", "cache", "spellchecks.json"); - -var CACHE = {}; - -try { - CACHE = JSON.parse(fs.readFileSync(CACHE_PATH).toString()); -} -catch (e) {} - -function md5(str) { - return createHash("md5").update(str).digest("utf8"); -} - -function addCacheHash(hash) { - CACHE[hash] = true; - fsExtra.ensureFileSync(CACHE_PATH); - fsExtra.writeJSONSync(CACHE_PATH, CACHE); -} - -describe("Spell Checking", function () { - it("Spellcheck CONTRIBUTING.md", function (done) { - var readme = path.join(rootURI, "CONTRIBUTING.md"); - - fs.readFile(readme, function (err, data) { - if (err) { - throw err; - } - var text = data.toString(); - var hash = md5(text); - - // skip this test if we know we have done the - // exact same test with positive results before - if (CACHE[hash]) { - expect(CACHE[hash]).to.be.equal(true); - return done(); - } - - teacher.check(text, function(err, data) { - expect(err).to.be.equal(null); - - var results = data || []; - results = results.filter(function(result) { - if (NEW_WORDS.indexOf(result.string.toLowerCase()) != -1) { - return false; - } - - // ignore anything that starts with a dash - if (result.string[0] == "-") { - return false; - } - - if (!(new RegExp(result.string)).test(text)) { - return false; - } - - return true; - }) - - if (results.length > 0) { - console.log(results); - } - else { - addCacheHash(hash); - } - - expect(results.length).to.be.equal(0); - - setTimeout(done, 500); - }); - }); - }); - - it("Spellcheck README.md", function (done) { - var readme = path.join(rootURI, "README.md"); - - fs.readFile(readme, function (err, data) { - if (err) { - throw err; - } - var text = data.toString(); - var hash = md5(text); - - // skip this test if we know we have done the - // exact same test with positive results before - if (CACHE[hash]) { - expect(CACHE[hash]).to.be.equal(true); - return done(); - } - - teacher.check(text, function(err, data) { - expect(err).to.be.equal(null); - - var results = data || []; - results = results.filter(function(result) { - if (NEW_WORDS.indexOf(result.string.toLowerCase()) != -1) { - return false; - } - - // ignore anything that starts with a dash - if (result.string[0] == "-") { - return false; - } - - // ignore anything that we don't find in the original text, - // for some reason "bootstrap.js" becomes "bootstrapjs". - if (!(new RegExp(result.string)).test(text)) { - return false; - } - - return true; - }) - - if (results.length > 0) { - console.log(results); - } - else { - addCacheHash(hash); - } - - expect(results.length).to.be.equal(0); - - done(); - }); - }); - }); -}); diff --git a/addon-sdk/source/bin/node-scripts/test.examples.js b/addon-sdk/source/bin/node-scripts/test.examples.js deleted file mode 100644 index 71f7ee43c..000000000 --- a/addon-sdk/source/bin/node-scripts/test.examples.js +++ /dev/null @@ -1,45 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var utils = require("./utils"); -var path = require("path"); -var fs = require("fs"); -var jpm = utils.run; -var readParam = utils.readParam; - -var examplesPath = path.join(__dirname, "..", "..", "examples"); - -var binary = process.env.JPM_FIREFOX_BINARY || "nightly"; -var filterPattern = readParam("filter"); - -describe("jpm test sdk examples", function () { - fs.readdirSync(examplesPath) - .filter(fileFilter.bind(null, examplesPath)) - .forEach(function (file) { - it(file, function (done) { - var addonPath = path.join(examplesPath, file); - process.chdir(addonPath); - - var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }}; - if (process.env.DISPLAY) { - options.env.DISPLAY = process.env.DISPLAY; - } - - jpm("test", options).then(done); - }); - }); -}); - -function fileFilter(root, file) { - var matcher = filterPattern && new RegExp(filterPattern); - if (/^(reading-data)/.test(file)) { - return false; - } - if (matcher && !matcher.test(file)) { - return false; - } - var stat = fs.statSync(path.join(root, file)) - return (stat && stat.isDirectory()); -} diff --git a/addon-sdk/source/bin/node-scripts/test.firefox-bin.js b/addon-sdk/source/bin/node-scripts/test.firefox-bin.js deleted file mode 100644 index 2570dae20..000000000 --- a/addon-sdk/source/bin/node-scripts/test.firefox-bin.js +++ /dev/null @@ -1,37 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var fs = require("fs"); -var Promise = require("promise"); -var chai = require("chai"); -var expect = chai.expect; -var normalizeBinary = require("fx-runner/lib/utils").normalizeBinary; - -//var firefox_binary = process.env["JPM_FIREFOX_BINARY"] || normalizeBinary("nightly"); - -describe("Checking Firefox binary", function () { - - it("using matching fx-runner version with jpm", function () { - var sdkPackageJSON = require("../../package.json"); - var jpmPackageINI = require("jpm/package.json"); - expect(sdkPackageJSON.devDependencies["fx-runner"]).to.be.equal(jpmPackageINI.dependencies["fx-runner"]); - }); - - it("exists", function (done) { - var useEnvVar = new Promise(function(resolve) { - resolve(process.env["JPM_FIREFOX_BINARY"]); - }); - - var firefox_binary = process.env["JPM_FIREFOX_BINARY"] ? useEnvVar : normalizeBinary("nightly"); - firefox_binary.then(function(path) { - expect(path).to.be.ok; - fs.exists(path, function (exists) { - expect(exists).to.be.ok; - done(); - }); - }) - }); - -}); diff --git a/addon-sdk/source/bin/node-scripts/test.ini.js b/addon-sdk/source/bin/node-scripts/test.ini.js deleted file mode 100644 index 07bd15d1f..000000000 --- a/addon-sdk/source/bin/node-scripts/test.ini.js +++ /dev/null @@ -1,68 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var fs = require("fs"); -var path = require("path"); -var Promise = require("promise"); -var chai = require("chai"); -var expect = chai.expect; -var ini = require("./update-ini"); - -var addonINI = path.resolve("./test/addons/jetpack-addon.ini"); -var packageINI = path.resolve("./test/jetpack-package.ini"); - -describe("Checking ini files", function () { - - it("Check test/addons/jetpack-addon.ini", function (done) { - - fs.readFile(addonINI, function (err, data) { - if (err) { - throw err; - } - // filter comments - var text = data.toString().split("\n").filter(function(line) { - return !/^\s*#/.test(line); - }).join("\n"); - var expected = ""; - - ini.makeAddonIniContent() - .then(function(contents) { - expected = contents; - - setTimeout(function end() { - expect(text.trim()).to.be.equal(expected.trim()); - done(); - }); - }); - }); - - }); - - it("Check test/jetpack-package.ini", function (done) { - - fs.readFile(packageINI, function (err, data) { - if (err) { - throw err; - } - // filter comments - var text = data.toString().split("\n").filter(function(line) { - return !/^\s*#/.test(line); - }).join("\n"); - var expected = ""; - - ini.makePackageIniContent() - .then(function(contents) { - expected = contents; - - setTimeout(function end() { - expect(text.trim()).to.be.equal(expected.trim()); - done(); - }); - }); - }); - - }); - -}); diff --git a/addon-sdk/source/bin/node-scripts/test.modules.js b/addon-sdk/source/bin/node-scripts/test.modules.js deleted file mode 100644 index eb400a5f3..000000000 --- a/addon-sdk/source/bin/node-scripts/test.modules.js +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var utils = require("./utils"); -var readParam = utils.readParam; -var path = require("path"); -var fs = require("fs"); -var jpm = utils.run; -var sdk = path.join(__dirname, "..", ".."); -var binary = process.env.JPM_FIREFOX_BINARY || "nightly"; - -var filterPattern = readParam("filter"); - -describe("jpm test sdk modules", function () { - it("SDK Modules", function (done) { - process.chdir(sdk); - - var options = { cwd: sdk, env: { JPM_FIREFOX_BINARY: binary } }; - if (process.env.DISPLAY) { - options.env.DISPLAY = process.env.DISPLAY; - } - options.filter = filterPattern; - - jpm("test", options, process).then(done); - }); -}); diff --git a/addon-sdk/source/bin/node-scripts/update-ini.js b/addon-sdk/source/bin/node-scripts/update-ini.js deleted file mode 100644 index 634cbc1de..000000000 --- a/addon-sdk/source/bin/node-scripts/update-ini.js +++ /dev/null @@ -1,141 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var path = require("path"); -var cp = require("child_process"); -var fs = require("fs"); -var Promise = require("promise"); -var parser = require("ini-parser"); - -var addonINI = path.resolve("./test/addons/jetpack-addon.ini"); -var addonsDir = path.resolve("./test/addons/"); -var packageINI = path.resolve("./test/jetpack-package.ini"); -var packageDir = path.resolve("./test/"); -var packageIgnorables = [ "addons", "preferences" ]; -var packageSupportFiles = [ - "fixtures.js", - "test-context-menu.html", - "util.js" -] - -function updateAddonINI() { - return new Promise(function(resolve) { - console.log("Start updating " + addonINI); - - makeAddonIniContent(). - then(function(contents) { - fs.writeFileSync(addonINI, contents, { encoding: "utf8" }); - console.log("Done updating " + addonINI); - resolve(); - }); - }) -} -exports.updateAddonINI = updateAddonINI; - -function makeAddonIniContent() { - return new Promise(function(resolve) { - var data = parser.parse(fs.readFileSync(addonINI, { encoding: "utf8" }).toString()); - var result = {}; - - fs.readdir(addonsDir, function(err, files) { - // get a list of folders - var folders = files.filter(function(file) { - return fs.statSync(path.resolve(addonsDir, file)).isDirectory(); - }).sort(); - - // copy any related data from the existing ini - folders.forEach(function(folder) { - var oldData = data[folder + ".xpi"]; - result[folder] = oldData ? oldData : {}; - }); - - // build a new ini file - var contents = []; - Object.keys(result).sort().forEach(function(key) { - contents.push("[" + key + ".xpi]"); - Object.keys(result[key]).forEach(function(dataKey) { - contents.push(dataKey + " = " + result[key][dataKey]); - }); - }); - contents = contents.join("\n") + "\n"; - - return resolve(contents); - }); - }); -} -exports.makeAddonIniContent = makeAddonIniContent; - -function makePackageIniContent() { - return new Promise(function(resolve) { - var data = parser.parse(fs.readFileSync(packageINI, { encoding: "utf8" }).toString()); - var result = {}; - - fs.readdir(packageDir, function(err, files) { - // get a list of folders - var folders = files.filter(function(file) { - var ignore = (packageIgnorables.indexOf(file) >= 0); - var isDir = fs.statSync(path.resolve(packageDir, file)).isDirectory(); - return (isDir && !ignore); - }).sort(); - - // get a list of "test-"" files - var files = files.filter(function(file) { - var ignore = !/^test\-.*\.js$/i.test(file); - var isDir = fs.statSync(path.resolve(packageDir, file)).isDirectory(); - return (!isDir && !ignore); - }).sort(); - - // get a list of the support files - var support_files = packageSupportFiles.map(function(file) { - return " " + file; - }); - folders.forEach(function(folder) { - support_files.push(" " + folder + "/**"); - }); - support_files = support_files.sort(); - - // copy any related data from the existing ini - files.forEach(function(file) { - var oldData = data[file]; - result[file] = oldData ? oldData : {}; - }); - - // build a new ini file - var contents = [ - "[DEFAULT]", - "support-files =" - ]; - support_files.forEach(function(support_file) { - contents.push(support_file); - }); - contents.push(""); - - Object.keys(result).sort().forEach(function(key) { - contents.push("[" + key + "]"); - Object.keys(result[key]).forEach(function(dataKey) { - contents.push(dataKey + " = " + result[key][dataKey]); - }); - }); - contents = contents.join("\n") + "\n"; - - return resolve(contents); - }); - }); -} -exports.makePackageIniContent = makePackageIniContent; - -function updatePackageINI() { - return new Promise(function(resolve) { - console.log("Start updating " + packageINI); - - makeAddonIniContent(). - then(function(contents) { - fs.writeFileSync(packageINI, contents, { encoding: "utf8" }); - console.log("Done updating " + packageINI); - resolve(); - }); - }) -} -exports.updatePackageINI = updatePackageINI; diff --git a/addon-sdk/source/bin/node-scripts/utils.js b/addon-sdk/source/bin/node-scripts/utils.js deleted file mode 100644 index 1d7f94474..000000000 --- a/addon-sdk/source/bin/node-scripts/utils.js +++ /dev/null @@ -1,104 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var _ = require("lodash"); -var path = require("path"); -var child_process = require("child_process"); -var jpm = require.resolve("../../node_modules/jpm/bin/jpm"); -var Promise = require("promise"); -var chai = require("chai"); -var expect = chai.expect; -var assert = chai.assert; -var DEFAULT_PROCESS = process; - -var sdk = path.join(__dirname, "..", ".."); -var prefsPath = path.join(sdk, "test", "preferences", "test-preferences.js"); -var e10sPrefsPath = path.join(sdk, "test", "preferences", "test-e10s-preferences.js"); - -var OUTPUT_FILTERS = [ - /[^\n\r]+WARNING\: NS_ENSURE_SUCCESS\(rv, rv\) failed[^\n]+\n\r?/ -]; - -var isDebug = (process.env["JPM_FX_DEBUG"] == "1"); -exports.isDebug = isDebug; - -function spawn (cmd, options) { - options = options || {}; - var env = _.extend({}, options.env, process.env); - - if (isDebug) { - env["MOZ_QUIET"] = 1; - } - - var e10s = options.e10s || false; - - return child_process.spawn("node", [ - jpm, cmd, "-v", "--tbpl", - "--prefs", e10s ? e10sPrefsPath : prefsPath, - "-o", sdk, - "-f", options.filter || "" - ], { - cwd: options.cwd || tmpOutputDir, - env: env - }); -} -exports.spawn = spawn; - -function run (cmd, options, p) { - return new Promise(function(resolve) { - var output = []; - - var proc = spawn(cmd, options); - proc.stderr.pipe(process.stderr); - proc.stdout.on("data", function (data) { - for (var i = OUTPUT_FILTERS.length - 1; i >= 0; i--) { - if (OUTPUT_FILTERS[i].test(data)) { - return null; - } - } - output.push(data); - return null; - }); - - if (p) { - proc.stdout.pipe(p.stdout); - } - else if (!isDebug) { - proc.stdout.pipe(DEFAULT_PROCESS.stdout); - } - else { - proc.stdout.on("data", function (data) { - data = (data || "") + ""; - if (/TEST-/.test(data)) { - DEFAULT_PROCESS.stdout.write(data.replace(/[\s\n]+$/, "") + "\n"); - } - }); - } - - proc.on("close", function(code) { - var out = output.join(""); - var buildDisplayed = /Build \d+/.test(out); - var noTests = /No tests were run/.test(out); - var hasSuccess = /All tests passed!/.test(out); - var hasFailure = /There were test failures\.\.\./.test(out); - if (noTests || hasFailure || !hasSuccess || code != 0) { - DEFAULT_PROCESS.stdout.write(out); - } - expect(code).to.equal(hasFailure ? 1 : 0); - expect(buildDisplayed).to.equal(true); - expect(hasFailure).to.equal(false); - expect(hasSuccess).to.equal(true); - expect(noTests).to.equal(false); - resolve(); - }); - }); -} -exports.run = run; - -function readParam(name) { - var index = process.argv.indexOf("--" + name) - return index >= 0 && process.argv[index + 1] -} -exports.readParam = readParam; diff --git a/addon-sdk/source/bin/node-scripts/words.txt b/addon-sdk/source/bin/node-scripts/words.txt deleted file mode 100644 index b5b29f74b..000000000 --- a/addon-sdk/source/bin/node-scripts/words.txt +++ /dev/null @@ -1,11 +0,0 @@ -addon-sdk -github -stackoverflow -bugzilla -irc -jsantell -mossop -gozala -zer0 -autonome -0c0w3 diff --git a/addon-sdk/source/examples/actor-repl/README.md b/addon-sdk/source/examples/actor-repl/README.md deleted file mode 100644 index 5228719e9..000000000 --- a/addon-sdk/source/examples/actor-repl/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Actor REPL - -Simple REPL for a Firefox debugging protocol. diff --git a/addon-sdk/source/examples/actor-repl/data/codemirror-compressed.js b/addon-sdk/source/examples/actor-repl/data/codemirror-compressed.js deleted file mode 100644 index 6d8e4bf71..000000000 --- a/addon-sdk/source/examples/actor-repl/data/codemirror-compressed.js +++ /dev/null @@ -1,5 +0,0 @@ -window.CodeMirror=function(){"use strict";function z(a,c){if(!(this instanceof z))return new z(a,c);this.options=c=c||{};for(var d in fd)!c.hasOwnProperty(d)&&fd.hasOwnProperty(d)&&(c[d]=fd[d]);M(c);var e="string"==typeof c.value?0:c.value.first,f=this.display=A(a,e);f.wrapper.CodeMirror=this,J(this),c.autofocus&&!r&&Qb(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,focused:!1,suppressEdits:!1,pasteIncoming:!1,cutIncoming:!1,draggingText:!1,highlight:new jf},H(this),c.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap");var g=c.value;"string"==typeof g&&(g=new te(c.value,c.mode)),Ib(this,xe)(this,g),b&&setTimeout(tf(Pb,this,!0),20),Tb(this);var h;try{h=document.activeElement==f.input}catch(i){}h||c.autofocus&&!r?setTimeout(tf(rc,this),20):sc(this),Ib(this,function(){for(var a in ed)ed.propertyIsEnumerable(a)&&ed[a](this,c[a],hd);for(var b=0;bb.maxLineLength&&(b.maxLineLength=d,b.maxLine=a)})}function M(a){var b=pf(a.gutters,"CodeMirror-linenumbers");-1==b&&a.lineNumbers?a.gutters=a.gutters.concat(["CodeMirror-linenumbers"]):b>-1&&!a.lineNumbers&&(a.gutters=a.gutters.slice(0),a.gutters.splice(b,1))}function N(a){var b=a.display,c=a.doc.height,d=c+jb(b);b.sizer.style.minHeight=b.heightForcer.style.top=d+"px",b.gutters.style.height=Math.max(d,b.scroller.clientHeight-gf)+"px";var e=Math.max(d,b.scroller.scrollHeight),f=b.scroller.scrollWidth>b.scroller.clientWidth+1,g=e>b.scroller.clientHeight+1;g?(b.scrollbarV.style.display="block",b.scrollbarV.style.bottom=f?Hf(b.measure)+"px":"0",b.scrollbarV.firstChild.style.height=Math.max(0,e-b.scroller.clientHeight+b.scrollbarV.clientHeight)+"px"):(b.scrollbarV.style.display="",b.scrollbarV.firstChild.style.height="0"),f?(b.scrollbarH.style.display="block",b.scrollbarH.style.right=g?Hf(b.measure)+"px":"0",b.scrollbarH.firstChild.style.width=b.scroller.scrollWidth-b.scroller.clientWidth+b.scrollbarH.clientWidth+"px"):(b.scrollbarH.style.display="",b.scrollbarH.firstChild.style.width="0"),f&&g?(b.scrollbarFiller.style.display="block",b.scrollbarFiller.style.height=b.scrollbarFiller.style.width=Hf(b.measure)+"px"):b.scrollbarFiller.style.display="",f&&a.options.coverGutterNextToScrollbar&&a.options.fixedGutter?(b.gutterFiller.style.display="block",b.gutterFiller.style.height=Hf(b.measure)+"px",b.gutterFiller.style.width=b.gutters.offsetWidth+"px"):b.gutterFiller.style.display="",n&&0===Hf(b.measure)&&(b.scrollbarV.style.minWidth=b.scrollbarH.style.minHeight=o?"18px":"12px",b.scrollbarV.style.pointerEvents=b.scrollbarH.style.pointerEvents="none")}function O(a,b,c){var d=a.scroller.scrollTop,e=a.wrapper.clientHeight;"number"==typeof c?d=c:c&&(d=c.top,e=c.bottom-c.top),d=Math.floor(d-ib(a));var f=Math.ceil(d+e);return{from:De(b,d),to:De(b,f)}}function P(a){var b=a.display;if(b.alignWidgets||b.gutters.firstChild&&a.options.fixedGutter){for(var c=S(b)-b.scroller.scrollLeft+a.doc.scrollLeft,d=b.gutters.offsetWidth,e=c+"px",f=b.lineDiv.firstChild;f;f=f.nextSibling)if(f.alignable)for(var g=0,h=f.alignable;g=a.display.showingFrom&&h.to<=a.display.showingTo)break}return g&&(bf(a,"update",a),(a.display.showingFrom!=e||a.display.showingTo!=f)&&bf(a,"viewportChange",a,a.display.showingFrom,a.display.showingTo)),g}function U(a,b,c,d){var e=a.display,f=a.doc;if(!e.wrapper.offsetWidth)return e.showingFrom=e.showingTo=f.first,e.viewOffset=0,void 0;if(!(!d&&0==b.length&&c.from>e.showingFrom&&c.tol&&e.showingTo-l<20&&(l=Math.min(j,e.showingTo)),y)for(k=Ce(Rd(f,ye(f,k)));j>l&&Sd(f,ye(f,l));)++l;var m=[{from:Math.max(e.showingFrom,f.first),to:Math.min(e.showingTo,j)}];if(m=m[0].from>=m[0].to?[]:X(m,b),y)for(var i=0;in.from)){m.splice(i--,1);break}n.to=p}for(var q=0,i=0;il&&(n.to=l),n.from>=n.to?m.splice(i--,1):q+=n.to-n.from}if(!d&&q==l-k&&k==e.showingFrom&&l==e.showingTo)return W(a),void 0;m.sort(function(a,b){return a.from-b.from});try{var r=document.activeElement}catch(s){}.7*(l-k)>q&&(e.lineDiv.style.display="none"),Z(a,k,l,m,h),e.lineDiv.style.display="",r&&document.activeElement!=r&&r.offsetHeight&&r.focus();var t=k!=e.showingFrom||l!=e.showingTo||e.lastSizeC!=e.wrapper.clientHeight;return t&&(e.lastSizeC=e.wrapper.clientHeight,eb(a,400)),e.showingFrom=k,e.showingTo=l,e.gutters.style.height="",V(a),W(a),!0}}function V(a){for(var f,b=a.display,d=b.lineDiv.offsetTop,e=b.lineDiv.firstChild;e;e=e.nextSibling)if(e.lineObj){if(c){var g=e.offsetTop+e.offsetHeight;f=g-d,d=g}else{var h=Df(e);f=h.bottom-h.top}var i=e.lineObj.height-f;if(2>f&&(f=Db(b)),i>.001||-.001>i){Be(e.lineObj,f);var j=e.lineObj.widgets;if(j)for(var k=0;kc;++c){for(var e=b[c],f=[],g=e.diff||0,h=0,i=a.length;i>h;++h){var j=a[h];e.to<=j.from&&e.diff?f.push({from:j.from+g,to:j.to+g}):e.to<=j.from||e.from>=j.to?f.push(j):(e.from>j.from&&f.push({from:j.from,to:e.from}),e.ton){for(;k.lineObj!=b;)k=l(k);i&&n>=e&&k.lineNumber&&Cf(k.lineNumber,R(a.options,n)),k=k.nextSibling}else{if(b.widgets)for(var s,q=0,r=k;r&&20>q;++q,r=r.nextSibling)if(r.lineObj==b&&/div/i.test(r.nodeName)){s=r;break}var t=$(a,b,n,f,s);if(t!=s)j.insertBefore(t,k);else{for(;k!=s;)k=l(k);k=k.nextSibling}t.lineObj=b}++n});k;)k=l(k)}function $(a,b,d,e,f){var k,g=ie(a,b),h=g.pre,i=b.gutterMarkers,j=a.display,l=g.bgClass?g.bgClass+" "+(b.bgClass||""):b.bgClass;if(!(a.options.lineNumbers||i||l||b.wrapClass||b.widgets))return h;if(f){f.alignable=null;for(var q,m=!0,n=0,o=null,p=f.firstChild;p;p=q)if(q=p.nextSibling,/\bCodeMirror-linewidget\b/.test(p.className)){for(var r=0;rb&&(b=0),e.appendChild(zf("div",null,"CodeMirror-selected","position: absolute; left: "+a+"px; top: "+b+"px; width: "+(null==c?h-a:c)+"px; height: "+(d-b)+"px"))}function j(b,d,e){function m(c,d){return xb(a,Gc(b,c),"div",f,d)}var k,l,f=ye(c,b),j=f.text.length;return Of(Fe(f),d||0,null==e?j:e,function(a,b,c){var n,o,p,f=m(a,"left");if(a==b)n=f,o=p=f.left;else{if(n=m(b-1,"right"),"rtl"==c){var q=f;f=n,n=q}o=f.left,p=n.right}null==d&&0==a&&(o=g),n.top-f.top>3&&(i(o,f.top,null,f.bottom),o=g,f.bottoml.bottom||n.bottom==l.bottom&&n.right>l.right)&&(l=n),g+1>o&&(o=g),i(o,n.top,p-o,n.bottom)}),{start:k,end:l}}var b=a.display,c=a.doc,d=a.doc.sel,e=document.createDocumentFragment(),f=kb(a.display),g=f.left,h=b.lineSpace.offsetWidth-f.right;if(d.from.line==d.to.line)j(d.from.line,d.from.ch,d.to.ch);else{var k=ye(c,d.from.line),l=ye(c,d.to.line),m=Rd(c,k)==Rd(c,l),n=j(d.from.line,d.from.ch,m?k.text.length:null).end,o=j(d.to.line,m?0:null,d.to.ch).start;m&&(n.top0&&(b.blinker=setInterval(function(){b.cursor.style.visibility=b.otherCursor.style.visibility=(c=!c)?"":"hidden"},a.options.cursorBlinkRate))}}function eb(a,b){a.doc.mode.startState&&a.doc.frontier=a.display.showingTo)){var f,c=+new Date+a.options.workTime,d=nd(b.mode,hb(a,b.frontier)),e=[];b.iter(b.frontier,Math.min(b.first+b.size,a.display.showingTo+500),function(g){if(b.frontier>=a.display.showingFrom){var h=g.styles;g.styles=ce(a,g,d,!0);for(var i=!h||h.length!=g.styles.length,j=0;!i&&jc?(eb(a,a.options.workDelay),!0):void 0}),e.length&&Ib(a,function(){for(var a=0;ag;--h){if(h<=f.first)return f.first;var i=ye(f,h-1);if(i.stateAfter&&(!c||h<=f.frontier))return h;var j=kf(i.text,null,a.options.tabSize);(null==e||d>j)&&(e=h-1,d=j)}return e}function hb(a,b,c){var d=a.doc,e=a.display;if(!d.mode.startState)return!0;var f=gb(a,b,c),g=f>d.first&&ye(d,f-1).stateAfter;return g=g?nd(d.mode,g):od(d.mode),d.iter(f,b,function(c){ee(a,c.text,g);var h=f==b-1||0==f%5||f>=e.showingFrom&&ff&&0==h&&(f=1)}return e=h>c?"left":c>h?"right":e,"left"==e&&i.leftSide?i=i.leftSide:"right"==e&&i.rightSide&&(i=i.rightSide),{left:c>h?i.right:i.left,right:h>c?i.left:i.right,top:i.top,bottom:i.bottom}}function mb(a,b){for(var c=a.display.measureLineCache,d=0;ds&&(c=s),0>b&&(b=0);for(var d=q.length-2;d>=0;d-=2){var e=q[d],f=q[d+1];if(!(e>c||b>f)&&(b>=e&&f>=c||e>=b&&c>=f||Math.min(c,f)-Math.max(b,e)>=c-b>>1)){q[d]=Math.min(b,e),q[d+1]=Math.max(c,f);break}}return 0>d&&(d=q.length,q.push(b,c)),{left:a.left-p.left,right:a.right-p.left,top:d,bottom:null}}function u(a){a.bottom=q[a.top+1],a.top=q[a.top]}if(!a.options.lineWrapping&&e.text.length>=a.options.crudeMeasuringFrom)return qb(a,e);var f=a.display,g=sf(e.text.length),h=ie(a,e,g,!0).pre;if(b&&!c&&!a.options.lineWrapping&&h.childNodes.length>100){for(var i=document.createDocumentFragment(),j=10,k=h.childNodes.length,l=0,m=Math.ceil(k/j);m>l;++l){for(var n=zf("div",null,null,"display: inline-block"),o=0;j>o&&k;++o)n.appendChild(h.firstChild),--k;i.appendChild(n)}h.appendChild(i)}Bf(f.measure,h);var p=Df(f.lineDiv),q=[],r=sf(e.text.length),s=h.offsetHeight;d&&f.measure.first!=h&&Bf(f.measure,h);for(var v,l=0;l1&&(x=r[l]=t(y[0]),x.rightSide=t(y[y.length-1]))}x||(x=r[l]=t(Df(w))),v.measureRight&&(x.right=Df(v.measureRight).left-p.left),v.leftSide&&(x.leftSide=t(Df(v.leftSide)))}Af(a.display.measure);for(var v,l=0;l=a.options.crudeMeasuringFrom)return lb(a,b,b.text.length,f&&f.measure,"right").right;var g=ie(a,b,null,!0).pre,h=g.appendChild(Jf(a.display.measure));Bf(a.display.measure,g);var i=Df(h);return 0==i.right&&0==i.bottom&&(h=g.appendChild(zf("span","\xa0")),i=Df(h)),i.left-Df(a.display.lineDiv).left}function sb(a){a.display.measureLineCache.length=a.display.measureLineCachePos=0,a.display.cachedCharWidth=a.display.cachedTextHeight=a.display.cachedPaddingH=null,a.options.lineWrapping||(a.display.maxLineChanged=!0),a.display.lineNumChars=null}function tb(){return window.pageXOffset||(document.documentElement||document.body).scrollLeft}function ub(){return window.pageYOffset||(document.documentElement||document.body).scrollTop}function vb(a,b,c,d){if(b.widgets)for(var e=0;ec.from?f(a-1):f(a,d)}d=d||ye(a.doc,b.line),e||(e=ob(a,d));var h=Fe(d),i=b.ch;if(!h)return f(i);var j=Xf(h,i),k=g(i,j);return null!=Wf&&(k.other=g(i,Wf)),k}function zb(a,b,c,d){var e=new Gc(a,b);return e.xRel=d,c&&(e.outside=!0),e}function Ab(a,b,c){var d=a.doc;if(c+=a.display.viewOffset,0>c)return zb(d.first,0,!0,-1);var e=De(d,c),f=d.first+d.size-1;if(e>f)return zb(d.first+d.size-1,ye(d,f).text.length,!0,1);for(0>b&&(b=0);;){var g=ye(d,e),h=Bb(a,g,e,b,c),i=Pd(g),j=i&&i.find();if(!i||!(h.ch>j.from.ch||h.ch==j.from.ch&&h.xRel>0))return h;e=j.to.line}}function Bb(a,b,c,d,e){function j(d){var e=yb(a,Gc(c,d),"line",b,i);return g=!0,f>e.bottom?e.left-h:fq)return zb(c,n,r,1);for(;;){if(k?n==m||n==Zf(b,m,1):1>=n-m){for(var s=o>d||q-d>=d-o?m:n,t=d-(s==m?o:q);yf(b.text.charAt(s));)++s;var u=zb(c,s,s==m?p:r,0>t?-1:t?1:0);return u}var v=Math.ceil(l/2),w=m+v;if(k){w=m;for(var x=0;v>x;++x)w=Zf(b,w,1)}var y=j(w);y>d?(n=w,q=y,(r=g)&&(q+=1e3),l=v):(m=w,o=y,p=g,l-=v)}}function Db(a){if(null!=a.cachedTextHeight)return a.cachedTextHeight;if(null==Cb){Cb=zf("pre");for(var b=0;49>b;++b)Cb.appendChild(document.createTextNode("x")),Cb.appendChild(zf("br"));Cb.appendChild(document.createTextNode("x"))}Bf(a.measure,Cb);var c=Cb.offsetHeight/50;return c>3&&(a.cachedTextHeight=c),Af(a.measure),c||1}function Eb(a){if(null!=a.cachedCharWidth)return a.cachedCharWidth;var b=zf("span","x"),c=zf("pre",[b]);Bf(a.measure,c);var d=b.offsetWidth;return d>2&&(a.cachedCharWidth=d),d||10}function Gb(a){a.curOp={changes:[],forceUpdate:!1,updateInput:null,userSelChange:null,textChanged:null,selectionChanged:!1,cursorActivity:!1,updateMaxLine:!1,updateScrollPos:!1,id:++Fb},af++||(_e=[])}function Hb(a){var b=a.curOp,c=a.doc,d=a.display;if(a.curOp=null,b.updateMaxLine&&L(a),d.maxLineChanged&&!a.options.lineWrapping&&d.maxLine){var e=rb(a,d.maxLine);d.sizer.style.minWidth=Math.max(0,e+3)+"px",d.maxLineChanged=!1;var f=Math.max(0,d.sizer.offsetLeft+d.sizer.offsetWidth-d.scroller.clientWidth);fj&&c.charCodeAt(j)==h.charCodeAt(j);)++j;var l=f.from,m=f.to,n=h.slice(j);j-1){$c(a,f.head.line,"smart");break}}return h.length>1e3||h.indexOf("\n")>-1?b.value=a.display.prevInput="":a.display.prevInput=h,i&&Hb(a),a.state.pasteIncoming=a.state.cutIncoming=!1,!0}function Pb(a,b){var c,e,f=a.doc;if(Hc(f.sel.from,f.sel.to))b&&(a.display.prevInput=a.display.input.value="",g&&!d&&(a.display.inputHasSelection=null));else{a.display.prevInput="",c=Mf&&(f.sel.to.line-f.sel.from.line>100||(e=a.getSelection()).length>1e3);var h=c?"-":e||a.getSelection();a.display.input.value=h,a.state.focused&&of(a.display.input),g&&!d&&(a.display.inputHasSelection=h)}a.display.inaccurateSelection=c}function Qb(a){"nocursor"==a.options.readOnly||r&&document.activeElement==a.display.input||a.display.input.focus()}function Rb(a){a.state.focused||(Qb(a),rc(a))}function Sb(a){return a.options.readOnly||a.doc.cantEdit}function Tb(a){function e(){a.state.focused&&setTimeout(tf(Qb,a),0)}function i(){null==f&&(f=setTimeout(function(){f=null,c.cachedCharWidth=c.cachedTextHeight=c.cachedPaddingH=Gf=null,sb(a),Kb(a,tf(Lb,a))},100))}function j(){for(var a=c.wrapper.parentNode;a&&a!=document.body;a=a.parentNode);a?setTimeout(j,5e3):Ze(window,"resize",i)}function k(b){cf(a,b)||a.options.onDragEvent&&a.options.onDragEvent(a,Re(b))||Ve(b)}function l(b){c.inaccurateSelection&&(c.prevInput="",c.inaccurateSelection=!1,c.input.value=a.getSelection(),of(c.input)),"cut"==b.type&&(a.state.cutIncoming=!0)}var c=a.display;Ye(c.scroller,"mousedown",Ib(a,Yb)),b?Ye(c.scroller,"dblclick",Ib(a,function(b){if(!cf(a,b)){var c=Vb(a,b);if(c&&!_b(a,b)&&!Ub(a.display,b)){Se(b);var d=cd(ye(a.doc,c.line).text,c);Pc(a.doc,d.from,d.to)}}})):Ye(c.scroller,"dblclick",function(b){cf(a,b)||Se(b)}),Ye(c.lineSpace,"selectstart",function(a){Ub(c,a)||Se(a)}),w||Ye(c.scroller,"contextmenu",function(b){uc(a,b)}),Ye(c.scroller,"scroll",function(){c.scroller.clientHeight&&(dc(a,c.scroller.scrollTop),ec(a,c.scroller.scrollLeft,!0),$e(a,"scroll",a))}),Ye(c.scrollbarV,"scroll",function(){c.scroller.clientHeight&&dc(a,c.scrollbarV.scrollTop)}),Ye(c.scrollbarH,"scroll",function(){c.scroller.clientHeight&&ec(a,c.scrollbarH.scrollLeft)}),Ye(c.scroller,"mousewheel",function(b){hc(a,b)}),Ye(c.scroller,"DOMMouseScroll",function(b){hc(a,b)}),Ye(c.scrollbarH,"mousedown",e),Ye(c.scrollbarV,"mousedown",e),Ye(c.wrapper,"scroll",function(){c.wrapper.scrollTop=c.wrapper.scrollLeft=0});var f;Ye(window,"resize",i),setTimeout(j,5e3),Ye(c.input,"keyup",Ib(a,nc)),Ye(c.input,"input",function(){g&&!d&&a.display.inputHasSelection&&(a.display.inputHasSelection=null),Nb(a)}),Ye(c.input,"keydown",Ib(a,pc)),Ye(c.input,"keypress",Ib(a,qc)),Ye(c.input,"focus",tf(rc,a)),Ye(c.input,"blur",tf(sc,a)),a.options.dragDrop&&(Ye(c.scroller,"dragstart",function(b){cc(a,b)}),Ye(c.scroller,"dragenter",k),Ye(c.scroller,"dragover",k),Ye(c.scroller,"drop",Ib(a,bc))),Ye(c.scroller,"paste",function(b){Ub(c,b)||(Qb(a),Nb(a))}),Ye(c.input,"paste",function(){if(h&&!a.state.fakedLastChar&&!(new Date-a.state.lastMiddleDown<200)){var b=c.input.selectionStart,d=c.input.selectionEnd; -c.input.value+="$",c.input.selectionStart=b,c.input.selectionEnd=d,a.state.fakedLastChar=!0}a.state.pasteIncoming=!0,Nb(a)}),Ye(c.input,"cut",l),Ye(c.input,"copy",l),m&&Ye(c.sizer,"mouseup",function(){document.activeElement==c.input&&c.input.blur(),Qb(a)})}function Ub(a,b){for(var c=We(b);c!=a.wrapper;c=c.parentNode)if(!c||c.ignoreEvents||c.parentNode==a.sizer&&c!=a.mover)return!0}function Vb(a,b,c){var d=a.display;if(!c){var e=We(b);if(e==d.scrollbarH||e==d.scrollbarH.firstChild||e==d.scrollbarV||e==d.scrollbarV.firstChild||e==d.scrollbarFiller||e==d.gutterFiller)return null}var f,g,h=Df(d.lineSpace);try{f=b.clientX,g=b.clientY}catch(b){return null}return Ab(a,f-h.left,g-h.top)}function Yb(a){function t(a){if(!Hc(s,a)){if(s=a,"single"==m)return Pc(c.doc,Mc(i,k),a),void 0;if(q=Mc(i,q),r=Mc(i,r),"double"==m){var b=cd(ye(i,a.line).text,a);Ic(a,q)?Pc(c.doc,b.from,r):Pc(c.doc,q,b.to)}else"triple"==m&&(Ic(a,q)?Pc(c.doc,r,Mc(i,Gc(a.line,0))):Pc(c.doc,q,Mc(i,Gc(a.line+1,0))))}}function x(a){var b=++v,d=Vb(c,a,!0);if(d)if(Hc(d,o)){var g=a.clientYu.bottom?20:0;g&&setTimeout(Ib(c,function(){v==b&&(f.scroller.scrollTop+=g,x(a))}),50)}else{Rb(c),o=d,t(d);var e=O(f,i);(d.line>=e.to||d.linel-400&&Hc(Xb.pos,k))m="triple",Se(a),setTimeout(tf(Qb,c),20),dd(c,k.line);else if(Wb&&Wb.time>l-400&&Hc(Wb.pos,k)){m="double",Xb={time:l,pos:k},Se(a);var n=cd(ye(i,k.line).text,k);Pc(c.doc,n.from,n.to)}else Wb={time:l,pos:k};var o=k;if(c.options.dragDrop&&Ef&&!Sb(c)&&!Hc(j.from,j.to)&&!Ic(k,j.from)&&!Ic(j.to,k)&&"single"==m){var p=Ib(c,function(e){h&&(f.scroller.draggable=!1),c.state.draggingText=!1,Ze(document,"mouseup",p),Ze(f.scroller,"drop",p),Math.abs(a.clientX-e.clientX)+Math.abs(a.clientY-e.clientY)<10&&(Se(e),Pc(c.doc,k),Qb(c),b&&!d&&setTimeout(function(){document.body.focus(),Qb(c)},20))});return h&&(f.scroller.draggable=!0),c.state.draggingText=p,f.scroller.dragDrop&&f.scroller.dragDrop(),Ye(document,"mouseup",p),Ye(f.scroller,"drop",p),void 0}Se(a),"single"==m&&Pc(c.doc,Mc(i,k));var q=j.from,r=j.to,s=k,u=Df(f.wrapper),v=0,z=Ib(c,function(a){(g&&!e?a.buttons:Xe(a))?x(a):y(a)}),A=Ib(c,y);Ye(document,"mousemove",z),Ye(document,"mouseup",A)}}}function Zb(a,b,c,d,e){try{var f=b.clientX,g=b.clientY}catch(b){return!1}if(f>=Math.floor(Df(a.display.gutters).right))return!1;d&&Se(b);var h=a.display,i=Df(h.lineDiv);if(g>i.bottom||!ef(a,c))return Ue(b);g-=i.top-h.viewOffset;for(var j=0;j=f){var l=De(a.doc,g),m=a.options.gutters[j];return e(a,c,a,l,m,b),Ue(b)}}}function $b(a,b){return ef(a,"gutterContextMenu")?Zb(a,b,"gutterContextMenu",!1,$e):!1}function _b(a,b){return Zb(a,b,"gutterClick",!0,bf)}function bc(a){var b=this;if(!(cf(b,a)||Ub(b.display,a)||b.options.onDragEvent&&b.options.onDragEvent(b,Re(a)))){Se(a),g&&(ac=+new Date);var c=Vb(b,a,!0),d=a.dataTransfer.files;if(c&&!Sb(b))if(d&&d.length&&window.FileReader&&window.File)for(var e=d.length,f=Array(e),h=0,i=function(a,d){var g=new FileReader;g.onload=function(){f[d]=g.result,++h==e&&(c=Mc(b.doc,c),zc(b.doc,{from:c,to:c,text:Kf(f.join("\n")),origin:"paste"},"around"))},g.readAsText(a)},j=0;e>j;++j)i(d[j],j);else{if(b.state.draggingText&&!Ic(c,b.doc.sel.from)&&!Ic(b.doc.sel.to,c))return b.state.draggingText(a),setTimeout(tf(Qb,b),20),void 0;try{var f=a.dataTransfer.getData("Text");if(f){var k=b.doc.sel.from,l=b.doc.sel.to;Rc(b.doc,c,c),b.state.draggingText&&Fc(b.doc,"",k,l,"paste"),b.replaceSelection(f,null,"paste"),Qb(b)}}catch(a){}}}}function cc(a,b){if(g&&(!a.state.draggingText||+new Date-ac<100))return Ve(b),void 0;if(!cf(a,b)&&!Ub(a.display,b)){var c=a.getSelection();if(b.dataTransfer.setData("Text",c),b.dataTransfer.setDragImage&&!l){var d=zf("img",null,null,"position: fixed; left: 0; top: 0;");d.src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",k&&(d.width=d.height=1,a.display.wrapper.appendChild(d),d._top=d.offsetTop),b.dataTransfer.setDragImage(d,0,0),k&&d.parentNode.removeChild(d)}}}function dc(b,c){Math.abs(b.doc.scrollTop-c)<2||(b.doc.scrollTop=c,a||T(b,[],c),b.display.scroller.scrollTop!=c&&(b.display.scroller.scrollTop=c),b.display.scrollbarV.scrollTop!=c&&(b.display.scrollbarV.scrollTop=c),a&&T(b,[]),eb(b,100))}function ec(a,b,c){(c?b==a.doc.scrollLeft:Math.abs(a.doc.scrollLeft-b)<2)||(b=Math.min(b,a.display.scroller.scrollWidth-a.display.scroller.clientWidth),a.doc.scrollLeft=b,P(a),a.display.scroller.scrollLeft!=b&&(a.display.scroller.scrollLeft=b),a.display.scrollbarH.scrollLeft!=b&&(a.display.scrollbarH.scrollLeft=b))}function hc(b,c){var d=c.wheelDeltaX,e=c.wheelDeltaY;null==d&&c.detail&&c.axis==c.HORIZONTAL_AXIS&&(d=c.detail),null==e&&c.detail&&c.axis==c.VERTICAL_AXIS?e=c.detail:null==e&&(e=c.wheelDelta);var f=b.display,g=f.scroller;if(d&&g.scrollWidth>g.clientWidth||e&&g.scrollHeight>g.clientHeight){if(e&&s&&h)for(var i=c.target;i!=g;i=i.parentNode)if(i.lineObj){b.display.currentWheelTarget=i;break}if(d&&!a&&!k&&null!=gc)return e&&dc(b,Math.max(0,Math.min(g.scrollTop+e*gc,g.scrollHeight-g.clientHeight))),ec(b,Math.max(0,Math.min(g.scrollLeft+d*gc,g.scrollWidth-g.clientWidth))),Se(c),f.wheelStartX=null,void 0;if(e&&null!=gc){var j=e*gc,l=b.doc.scrollTop,m=l+f.wrapper.clientHeight;0>j?l=Math.max(0,l+j-50):m=Math.min(b.doc.height,m+j+50),T(b,[],{top:l,bottom:m})}20>fc&&(null==f.wheelStartX?(f.wheelStartX=g.scrollLeft,f.wheelStartY=g.scrollTop,f.wheelDX=d,f.wheelDY=e,setTimeout(function(){if(null!=f.wheelStartX){var a=g.scrollLeft-f.wheelStartX,b=g.scrollTop-f.wheelStartY,c=b&&f.wheelDY&&b/f.wheelDY||a&&f.wheelDX&&a/f.wheelDX;f.wheelStartX=f.wheelStartY=null,c&&(gc=(gc*fc+c)/(fc+1),++fc)}},200)):(f.wheelDX+=d,f.wheelDY+=e))}}function ic(a,b,c){if("string"==typeof b&&(b=pd[b],!b))return!1;a.display.pollingFast&&Ob(a)&&(a.display.pollingFast=!1);var d=a.doc,e=d.sel.shift,f=!1;try{Sb(a)&&(a.state.suppressEdits=!0),c&&(d.sel.shift=!1),f=b(a)!=hf}finally{d.sel.shift=e,a.state.suppressEdits=!1}return f}function jc(a){var b=a.state.keyMaps.slice(0);return a.options.extraKeys&&b.push(a.options.extraKeys),b.push(a.options.keyMap),b}function lc(a,b){var c=rd(a.options.keyMap),e=c.auto;clearTimeout(kc),e&&!td(b)&&(kc=setTimeout(function(){rd(a.options.keyMap)==c&&(a.options.keyMap=e.call?e.call(null,a):e,G(a))},50));var f=ud(b,!0),g=!1;if(!f)return!1;var h=jc(a);return g=b.shiftKey?sd("Shift-"+f,h,function(b){return ic(a,b,!0)})||sd(f,h,function(b){return("string"==typeof b?/^go[A-Z]/.test(b):b.motion)?ic(a,b):void 0}):sd(f,h,function(b){return ic(a,b)}),g&&(Se(b),db(a),d&&(b.oldKeyCode=b.keyCode,b.keyCode=0),bf(a,"keyHandled",a,f,b)),g}function mc(a,b,c){var d=sd("'"+c+"'",jc(a),function(b){return ic(a,b,!0)});return d&&(Se(b),db(a),bf(a,"keyHandled",a,"'"+c+"'",b)),d}function nc(a){var b=this;cf(b,a)||b.options.onKeyEvent&&b.options.onKeyEvent(b,Re(a))||16==a.keyCode&&(b.doc.sel.shift=!1)}function pc(a){var c=this;if(Rb(c),!(cf(c,a)||c.options.onKeyEvent&&c.options.onKeyEvent(c,Re(a)))){b&&27==a.keyCode&&(a.returnValue=!1);var d=a.keyCode;c.doc.sel.shift=16==d||a.shiftKey;var e=lc(c,a);k&&(oc=e?d:null,!e&&88==d&&!Mf&&(s?a.metaKey:a.ctrlKey)&&c.replaceSelection(""))}}function qc(a){var b=this;if(!(cf(b,a)||b.options.onKeyEvent&&b.options.onKeyEvent(b,Re(a)))){var c=a.keyCode,e=a.charCode;if(k&&c==oc)return oc=null,Se(a),void 0;if(!(k&&(!a.which||a.which<10)||m)||!lc(b,a)){var f=String.fromCharCode(null==e?c:e);mc(b,a,f)||(g&&!d&&(b.display.inputHasSelection=null),Nb(b))}}}function rc(a){"nocursor"!=a.options.readOnly&&(a.state.focused||($e(a,"focus",a),a.state.focused=!0,-1==a.display.wrapper.className.search(/\bCodeMirror-focused\b/)&&(a.display.wrapper.className+=" CodeMirror-focused"),a.curOp||(Pb(a,!0),h&&setTimeout(tf(Pb,a,!0),0))),Mb(a),db(a))}function sc(a){a.state.focused&&($e(a,"blur",a),a.state.focused=!1,a.display.wrapper.className=a.display.wrapper.className.replace(" CodeMirror-focused","")),clearInterval(a.display.blinker),setTimeout(function(){a.state.focused||(a.doc.sel.shift=!1)},150)}function uc(a,b){function l(){if(null!=c.input.selectionStart){var a=c.input.value="\u200b"+(Hc(e.from,e.to)?"":c.input.value);c.prevInput="\u200b",c.input.selectionStart=1,c.input.selectionEnd=a.length}}function m(){if(c.inputDiv.style.position="relative",c.input.style.cssText=j,d&&(c.scrollbarV.scrollTop=c.scroller.scrollTop=h),Mb(a),null!=c.input.selectionStart){(!g||d)&&l(),clearTimeout(tc);var b=0,e=function(){"\u200b"==c.prevInput&&0==c.input.selectionStart?Ib(a,pd.selectAll)(a):b++<10?tc=setTimeout(e,500):Pb(a)};tc=setTimeout(e,200)}}if(!cf(a,b,"contextmenu")){var c=a.display,e=a.doc.sel;if(!Ub(c,b)&&!$b(a,b)){var f=Vb(a,b),h=c.scroller.scrollTop;if(f&&!k){var i=a.options.resetSelectionOnContextMenu;i&&(Hc(e.from,e.to)||Ic(f,e.from)||!Ic(f,e.to))&&Ib(a,Rc)(a.doc,f,f);var j=c.input.style.cssText;if(c.inputDiv.style.position="absolute",c.input.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(b.clientY-5)+"px; left: "+(b.clientX-5)+"px; z-index: 1000; background: transparent; outline: none;"+"border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);",Qb(a),Pb(a,!0),Hc(e.from,e.to)&&(c.input.value=c.prevInput=" "),g&&!d&&l(),w){Ve(b);var n=function(){Ze(window,"mouseup",n),setTimeout(m,20)};Ye(window,"mouseup",n)}else setTimeout(m,50)}}}}function wc(a,b,c){if(!Ic(b.from,c))return Mc(a,c);var d=b.text.length-1-(b.to.line-b.from.line);if(c.line>b.to.line+d){var e=c.line-d,f=a.first+a.size-1;return e>f?Gc(f,ye(a,f).text.length):Nc(c,ye(a,e).text.length)}if(c.line==b.to.line+d)return Nc(c,nf(b.text).length+(1==b.text.length?b.from.ch:0)+ye(a,b.to.line).text.length-b.to.ch);var g=c.line-b.from.line;return Nc(c,b.text[g].length+(g?0:b.from.ch))}function xc(a,b,c){if(c&&"object"==typeof c)return{anchor:wc(a,b,c.anchor),head:wc(a,b,c.head)};if("start"==c)return{anchor:b.from,head:b.from};var d=vc(b);if("around"==c)return{anchor:b.from,head:d};if("end"==c)return{anchor:d,head:d};var e=function(a){if(Ic(a,b.from))return a;if(!Ic(b.to,a))return d;var c=a.line+b.text.length-(b.to.line-b.from.line)-1,e=a.ch;return a.line==b.to.line&&(e+=d.ch-b.to.ch),Gc(c,e)};return{anchor:e(a.sel.anchor),head:e(a.sel.head)}}function yc(a,b,c){var d={canceled:!1,from:b.from,to:b.to,text:b.text,origin:b.origin,cancel:function(){this.canceled=!0}};return c&&(d.update=function(b,c,d,e){b&&(this.from=Mc(a,b)),c&&(this.to=Mc(a,c)),d&&(this.text=d),void 0!==e&&(this.origin=e)}),$e(a,"beforeChange",a,d),a.cm&&$e(a.cm,"beforeChange",a.cm,d),d.canceled?null:{from:d.from,to:d.to,text:d.text,origin:d.origin}}function zc(a,b,c,d){if(a.cm){if(!a.cm.curOp)return Ib(a.cm,zc)(a,b,c,d);if(a.cm.state.suppressEdits)return}if(!(ef(a,"beforeChange")||a.cm&&ef(a.cm,"beforeChange"))||(b=yc(a,b,!0))){var e=x&&!d&&Jd(a,b.from,b.to);if(e){for(var f=e.length-1;f>=1;--f)Ac(a,{from:e[f].from,to:e[f].to,text:[""]});e.length&&Ac(a,{from:e[0].from,to:e[0].to,text:b.text},c)}else Ac(a,b,c)}}function Ac(a,b,c){if(1!=b.text.length||""!=b.text[0]||!Hc(b.from,b.to)){var d=xc(a,b,c);Je(a,b,d,a.cm?a.cm.curOp.id:0/0),Dc(a,b,d,Gd(a,b));var e=[];we(a,function(a,c){c||-1!=pf(e,a.history)||(Pe(a.history,b),e.push(a.history)),Dc(a,b,null,Gd(a,b))})}}function Bc(a,b){if(!a.cm||!a.cm.state.suppressEdits){var c=a.history,d=("undo"==b?c.done:c.undone).pop();if(d){var e={changes:[],anchorBefore:d.anchorAfter,headBefore:d.headAfter,anchorAfter:d.anchorBefore,headAfter:d.headBefore,generation:c.generation};("undo"==b?c.undone:c.done).push(e),c.generation=d.generation||++c.maxGeneration;for(var f=ef(a,"beforeChange")||a.cm&&ef(a.cm,"beforeChange"),g=d.changes.length-1;g>=0;--g){var h=d.changes[g];if(h.origin=b,f&&!yc(a,h,!1))return("undo"==b?c.done:c.undone).length=0,void 0;e.changes.push(Ie(a,h));var i=g?xc(a,h,null):{anchor:d.anchorBefore,head:d.headBefore};Dc(a,h,i,Id(a,h));var j=[];we(a,function(a,b){b||-1!=pf(j,a.history)||(Pe(a.history,h),j.push(a.history)),Dc(a,h,null,Id(a,h))})}}}}function Cc(a,b){function c(a){return Gc(a.line+b,a.ch)}a.first+=b,a.cm&&Lb(a.cm,a.first,a.first,b),a.sel.head=c(a.sel.head),a.sel.anchor=c(a.sel.anchor),a.sel.from=c(a.sel.from),a.sel.to=c(a.sel.to)}function Dc(a,b,c,d){if(a.cm&&!a.cm.curOp)return Ib(a.cm,Dc)(a,b,c,d);if(b.to.linea.lastLine())){if(b.from.linef&&(b={from:b.from,to:Gc(f,ye(a,f).text.length),text:[b.text[0]],origin:b.origin}),b.removed=ze(a,b.from,b.to),c||(c=xc(a,b,null)),a.cm?Ec(a.cm,b,d,c):pe(a,b,d,c)}}function Ec(a,b,c,d){var e=a.doc,f=a.display,g=b.from,h=b.to,i=!1,j=g.line;a.options.lineWrapping||(j=Ce(Rd(e,ye(e,g.line))),e.iter(j,h.line+1,function(a){return a==f.maxLine?(i=!0,!0):void 0})),Ic(e.sel.head,b.from)||Ic(b.to,e.sel.head)||(a.curOp.cursorActivity=!0),pe(e,b,c,d,E(a)),a.options.lineWrapping||(e.iter(j,g.line+b.text.length,function(a){var b=K(e,a);b>f.maxLineLength&&(f.maxLine=a,f.maxLineLength=b,f.maxLineChanged=!0,i=!1)}),i&&(a.curOp.updateMaxLine=!0)),e.frontier=Math.min(e.frontier,g.line),eb(a,400);var k=b.text.length-(h.line-g.line)-1;if(Lb(a,g.line,h.line+1,k),ef(a,"change")){var l={from:g,to:h,text:b.text,removed:b.removed,origin:b.origin};if(a.curOp.textChanged){for(var m=a.curOp.textChanged;m.next;m=m.next);m.next=l}else a.curOp.textChanged=l}}function Fc(a,b,c,d,e){if(d||(d=c),Ic(d,c)){var f=d;d=c,c=f}"string"==typeof b&&(b=Kf(b)),zc(a,{from:c,to:d,text:b,origin:e},null)}function Gc(a,b){return this instanceof Gc?(this.line=a,this.ch=b,void 0):new Gc(a,b)}function Hc(a,b){return a.line==b.line&&a.ch==b.ch}function Ic(a,b){return a.linec?Gc(c,ye(a,c).text.length):Nc(b,ye(a,b.line).text.length)}function Nc(a,b){var c=a.ch;return null==c||c>b?Gc(a.line,b):0>c?Gc(a.line,0):a}function Oc(a,b){return b>=a.first&&b=f.ch:j.to>f.ch))){if(d&&($e(k,"beforeCursorEnter"),k.explicitlyCleared)){if(h.markedSpans){--i;continue}break}if(!k.atomic)continue;var l=k.find()[0>g?"from":"to"];if(Hc(l,f)&&(l.ch+=g,l.ch<0?l=l.line>a.first?Mc(a,Gc(l.line-1)):null:l.ch>h.text.length&&(l=l.line(window.innerHeight||document.documentElement.clientHeight)&&(e=!1),null!=e&&!p){var f=zf("div","\u200b",null,"position: absolute; top: "+(b.top-c.viewOffset)+"px; height: "+(b.bottom-b.top+gf)+"px; left: "+b.left+"px; width: 2px;");a.display.lineSpace.appendChild(f),f.scrollIntoView(e),a.display.lineSpace.removeChild(f)}}}function Vc(a,b,c,d){for(null==d&&(d=0);;){var e=!1,f=yb(a,b),g=c&&c!=b?yb(a,c):f,h=Xc(a,Math.min(f.left,g.left),Math.min(f.top,g.top)-d,Math.max(f.left,g.left),Math.max(f.bottom,g.bottom)+d),i=a.doc.scrollTop,j=a.doc.scrollLeft;if(null!=h.scrollTop&&(dc(a,h.scrollTop),Math.abs(a.doc.scrollTop-i)>1&&(e=!0)),null!=h.scrollLeft&&(ec(a,h.scrollLeft),Math.abs(a.doc.scrollLeft-j)>1&&(e=!0)),!e)return f}}function Wc(a,b,c,d,e){var f=Xc(a,b,c,d,e);null!=f.scrollTop&&dc(a,f.scrollTop),null!=f.scrollLeft&&ec(a,f.scrollLeft)}function Xc(a,b,c,d,e){var f=a.display,g=Db(a.display);0>c&&(c=0);var h=f.scroller.clientHeight-gf,i=f.scroller.scrollTop,j={},k=a.doc.height+jb(f),l=g>c,m=e>k-g;if(i>c)j.scrollTop=l?0:c;else if(e>i+h){var n=Math.min(c,(m?k:e)-h);n!=i&&(j.scrollTop=n)}var o=f.scroller.clientWidth-gf,p=f.scroller.scrollLeft;b+=f.gutters.offsetWidth,d+=f.gutters.offsetWidth;var q=f.gutters.offsetWidth,r=q+10>b;return p+q>b||r?(r&&(b=0),j.scrollLeft=Math.max(0,b-10-q)):d>o+p-3&&(j.scrollLeft=d+10-o),j}function Yc(a,b,c){a.curOp.updateScrollPos={scrollLeft:null==b?a.doc.scrollLeft:b,scrollTop:null==c?a.doc.scrollTop:c}}function Zc(a,b,c){var d=a.curOp.updateScrollPos||(a.curOp.updateScrollPos={scrollLeft:a.doc.scrollLeft,scrollTop:a.doc.scrollTop}),e=a.display.scroller;d.scrollTop=Math.max(0,Math.min(e.scrollHeight-e.clientHeight,d.scrollTop+c)),d.scrollLeft=Math.max(0,Math.min(e.scrollWidth-e.clientWidth,d.scrollLeft+b))}function $c(a,b,c,d){var f,e=a.doc;null==c&&(c="add"),"smart"==c&&(a.doc.mode.indent?f=hb(a,b):c="prev");var g=a.options.tabSize,h=ye(e,b),i=kf(h.text,null,g);h.stateAfter&&(h.stateAfter=null);var k,j=h.text.match(/^\s*/)[0];if(d||/\S/.test(h.text)){if("smart"==c&&(k=a.doc.mode.indent(f,h.text.slice(j.length),h.text),k==hf)){if(!d)return;c="prev"}}else k=0,c="not";"prev"==c?k=b>e.first?kf(ye(e,b-1).text,null,g):0:"add"==c?k=i+a.options.indentUnit:"subtract"==c?k=i-a.options.indentUnit:"number"==typeof c&&(k=i+c),k=Math.max(0,k);var l="",m=0;if(a.options.indentWithTabs)for(var n=Math.floor(k/g);n;--n)m+=g,l+=" ";k>m&&(l+=mf(k-m)),l!=j?Fc(a.doc,l,Gc(b,0),Gc(b,j.length),"+input"):e.sel.head.line==b&&e.sel.head.ch=a.first+a.size?j=!1:(f=b,i=ye(a,b))}function l(a){var b=(e?Zf:$f)(i,g,c,!0);if(null==b){if(a||!k())return j=!1;g=e?(0>c?Sf:Rf)(i):0>c?i.text.length:0}else g=b;return!0}var f=b.line,g=b.ch,h=c,i=ye(a,f),j=!0;if("char"==d)l();else if("column"==d)l(!0);else if("word"==d||"group"==d)for(var m=null,n="group"==d,o=!0;!(0>c)||l(!o);o=!1){var p=i.text.charAt(g)||"\n",q=vf(p)?"w":n&&"\n"==p?"n":!n||/\s/.test(p)?null:"p";if(!n||o||q||(q="s"),m&&m!=q){0>c&&(c=1,l());break}if(q&&(m=q),c>0&&!l(!o))break}var r=Tc(a,Gc(f,g),h,!0);return j||(r.hitSide=!0),r}function bd(a,b,c,d){var g,e=a.doc,f=b.left;if("page"==d){var h=Math.min(a.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);g=b.top+c*(h-(0>c?1.5:.5)*Db(a.display))}else"line"==d&&(g=c>0?b.bottom+3:b.top-3);for(;;){var i=Ab(a,f,g);if(!i.outside)break;if(0>c?0>=g:g>=e.height){i.hitSide=!0;break}g+=5*c}return i}function cd(a,b){var c=b.ch,d=b.ch;if(a){(b.xRel<0||d==a.length)&&c?--c:++d;for(var e=a.charAt(c),f=vf(e)?vf:/\s/.test(e)?function(a){return/\s/.test(a)}:function(a){return!/\s/.test(a)&&!vf(a)};c>0&&f(a.charAt(c-1));)--c;for(;dg;++g){var i=d(f[g]);if(i)return i}return!1}for(var e=0;e=b:f.to>b);(e||(e=[])).push({from:f.from,to:i?null:f.to,marker:g})}}return e}function Fd(a,b,c){if(a)for(var e,d=0;d=b:f.to>b);if(h||f.from==b&&"bookmark"==g.type&&(!c||f.marker.insertLeft)){var i=null==f.from||(g.inclusiveLeft?f.from<=b:f.from0&&h)for(var l=0;ll;++l)o.push(q);o.push(i)}return o}function Hd(a){for(var b=0;b=0&&0>=l||0>=k&&l>=0)&&(0>=k&&(Jc(j.to,c)||Ld(i.marker)-Kd(e))>0||k>=0&&(Jc(j.from,d)||Kd(i.marker)-Ld(e))<0))return!0}}}function Rd(a,b){for(var c;c=Od(b);)b=ye(a,c.find().from.line);return b}function Sd(a,b){var c=y&&b.markedSpans;if(c)for(var d,e=0;ea.options.maxHighlightLength?(g=!1,f&&ee(a,b,d,j.pos),j.pos=b.length,k=null):k=c.token(j,d),a.options.addModeClass){var l=z.innerMode(c,d).mode.name;l&&(k="m-"+(k?l+" "+k:l))}g&&i==k||(hi;){var d=e[h];d>a&&e.splice(h,1,a,e[h+1],d),h+=2,i=Math.min(a,d)}if(b)if(g.opaque)e.splice(c,h-c,a,b),h=c+2;else for(;h>c;c+=2){var f=e[c+1];e[c+1]=f?f+" "+b:b}})}return e}function de(a,b){return b.styles&&b.styles[0]==a.state.modeGen||(b.styles=ce(a,b,b.stateAfter=hb(a,Ce(b)))),b.styles}function ee(a,b,c,d){var e=a.doc.mode,f=new vd(b,a.options.tabSize);for(f.start=f.pos=d||0,""==b&&e.blankLine&&e.blankLine(c);!f.eol()&&f.pos<=a.options.maxHighlightLength;)e.token(f,c),f.start=f.pos}function he(a,b){if(!a)return null;for(;;){var c=a.match(/(?:^|\s+)line-(background-)?(\S+)/);if(!c)break;a=a.slice(0,c.index)+a.slice(c.index+c[0].length);var d=c[1]?"bgClass":"textClass";null==b[d]?b[d]=c[2]:new RegExp("(?:^|s)"+c[2]+"(?:$|s)").test(b[d])||(b[d]+=" "+c[2])}if(/^\s*$/.test(a))return null;var e=b.cm.options.addModeClass?ge:fe;return e[a]||(e[a]=a.replace(/\S+/g,"cm-$&"))}function ie(a,b,c,d){for(var e,f=b,i=!0;e=Od(f);)f=ye(a.doc,e.find().from.line);var j={pre:zf("pre"),col:0,pos:0,measure:null,measuredSomething:!1,cm:a,copyWidgets:d};do{f.text&&(i=!1),j.measure=f==b&&c,j.pos=0,j.addToken=j.measure?le:ke,(g||h)&&a.getOption("lineWrapping")&&(j.addToken=me(j.addToken));var k=oe(f,j,de(a,f));c&&f==b&&!j.measuredSomething&&(c[0]=j.pre.appendChild(Jf(a.display.measure)),j.measuredSomething=!0),k&&(f=ye(a.doc,k.to.line))}while(k);!c||j.measuredSomething||c[0]||(c[0]=j.pre.appendChild(i?zf("span","\xa0"):Jf(a.display.measure))),j.pre.firstChild||Sd(a.doc,b)||j.pre.appendChild(document.createTextNode("\xa0"));var l;if(c&&g&&(l=Fe(f))){var m=l.length-1;l[m].from==l[m].to&&--m;var n=l[m],o=l[m-1];if(n.from+1==n.to&&o&&n.leveli)?(null!=t.to&&l>t.to&&(l=t.to,n=""),u.className&&(m+=" "+u.className),u.startStyle&&t.from==i&&(o+=" "+u.startStyle),u.endStyle&&t.to==l&&(n+=" "+u.endStyle),u.title&&!p&&(p=u.title),u.collapsed&&(!q||Md(q.marker,u)<0)&&(q=t)):t.from>i&&l>t.from&&(l=t.from),"bookmark"==u.type&&t.from==i&&u.replacedWith&&r.push(u)}if(q&&(q.from||0)==i&&(ne(b,(null==q.to?h:q.to)-i,q.marker,null==q.from),null==q.to))return q.marker.find();if(!q&&r.length)for(var s=0;s=h)break;for(var v=Math.min(h,l);;){if(j){var w=i+j.length;if(!q){var x=w>v?j.slice(0,v-i):j;b.addToken(b,x,k?k+m:m,o,i+x.length==l?n:"",p)}if(w>=v){j=j.slice(v-i),i=v;break}i=w,o=""}j=e.slice(f,f=c[g++]),k=he(c[g++],b)}}else for(var g=1;gp;++p)r.push(new $d(j[p],f(p),e));r.push(new $d(m+k.text.slice(i.ch),n,e)),g(k,k.text.slice(0,h.ch)+j[0],f(0)),a.insert(h.line+1,r)}else if(1==j.length)g(k,k.text.slice(0,h.ch)+j[0]+l.text.slice(i.ch),f(0)),a.remove(h.line+1,o);else{g(k,k.text.slice(0,h.ch)+j[0],f(0)),g(l,m+l.text.slice(i.ch),n);for(var p=1,q=j.length-1,r=[];q>p;++p)r.push(new $d(j[p],f(p),e));o>1&&a.remove(h.line+1,o-1),a.insert(h.line+1,r)}else{for(var p=0,q=j.length-1,r=[];q>p;++p)r.push(new $d(j[p],f(p),e));g(l,l.text,n),o&&a.remove(h.line,o),r.length&&a.insert(h.line,r)}bf(a,"change",a,b),Rc(a,d.anchor,d.head,null,!0)}function qe(a){this.lines=a,this.parent=null;for(var b=0,c=a.length,d=0;c>b;++b)a[b].parent=this,d+=a[b].height;this.height=d}function re(a){this.children=a;for(var b=0,c=0,d=0,e=a.length;e>d;++d){var f=a[d];b+=f.chunkSize(),c+=f.height,f.parent=this}this.size=b,this.height=c,this.parent=null}function we(a,b,c){function d(a,e,f){if(a.linked)for(var g=0;gb){a=d;break}b-=e}return a.lines[b]}function ze(a,b,c){var d=[],e=b.line;return a.iter(b.line,c.line+1,function(a){var f=a.text;e==c.line&&(f=f.slice(0,c.ch)),e==b.line&&(f=f.slice(b.ch)),d.push(f),++e}),d}function Ae(a,b,c){var d=[];return a.iter(b,c,function(a){d.push(a.text)}),d}function Be(a,b){for(var c=b-a.height,d=a;d;d=d.parent)d.height+=c}function Ce(a){if(null==a.parent)return null;for(var b=a.parent,c=pf(b.lines,a),d=b.parent;d;b=d,d=d.parent)for(var e=0;d.children[e]!=b;++e)c+=d.children[e].chunkSize();return c+b.first}function De(a,b){var c=a.first;a:do{for(var d=0,e=a.children.length;e>d;++d){var f=a.children[d],g=f.height;if(g>b){a=f;continue a}b-=g,c+=f.chunkSize()}return c}while(!a.lines);for(var d=0,e=a.lines.length;e>d;++d){var h=a.lines[d],i=h.height;if(i>b)break;b-=i}return c+d}function Ee(a,b){b=Rd(a.doc,b);for(var c=0,d=b.parent,e=0;ef-a.cm.options.historyEventDelay||"*"==b.origin.charAt(0)))){var h=nf(g.changes);Hc(b.from,b.to)&&Hc(b.from,h.to)?h.to=vc(b):g.changes.push(Ie(a,b)),g.anchorAfter=c.anchor,g.headAfter=c.head}else for(g={changes:[Ie(a,b)],generation:e.generation,anchorBefore:a.sel.anchor,headBefore:a.sel.head,anchorAfter:c.anchor,headAfter:c.head},e.done.push(g);e.done.length>e.undoDepth;)e.done.shift();e.generation=++e.maxGeneration,e.lastTime=f,e.lastOp=d,e.lastOrigin=b.origin,h||$e(a,"historyAdded")}function Ke(a){if(!a)return null;for(var c,b=0;b-1&&(nf(g)[k]=i[k],delete i[k])}}return d}function Ne(a,b,c,d){c0}function ff(a){a.prototype.on=function(a,b){Ye(this,a,b)},a.prototype.off=function(a,b){Ze(this,a,b)}}function jf(){this.id=null}function kf(a,b,c,d,e){null==b&&(b=a.search(/[^\s\u00a0]/),-1==b&&(b=a.length));for(var f=d||0,g=e||0;b>f;++f)" "==a.charAt(f)?g+=c-g%c:++g;return g}function mf(a){for(;lf.length<=a;)lf.push(nf(lf)+" ");return lf[a]}function nf(a){return a[a.length-1]}function of(a){if(q)a.selectionStart=0,a.selectionEnd=a.value.length;else try{a.select()}catch(b){}}function pf(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;++c)if(a[c]==b)return c;return-1}function qf(a,b){function c(){}c.prototype=a;var d=new c;return b&&rf(b,d),d}function rf(a,b){b||(b={});for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}function sf(a){for(var b=[],c=0;a>c;++c)b.push(void 0);return b}function tf(a){var b=Array.prototype.slice.call(arguments,1);return function(){return a.apply(null,b)}}function vf(a){return/\w/.test(a)||a>"\x80"&&(a.toUpperCase()!=a.toLowerCase()||uf.test(a))}function wf(a){for(var b in a)if(a.hasOwnProperty(b)&&a[b])return!1;return!0}function yf(a){return a.charCodeAt(0)>=768&&xf.test(a)}function zf(a,b,c,d){var e=document.createElement(a);if(c&&(e.className=c),d&&(e.style.cssText=d),"string"==typeof b)Cf(e,b);else if(b)for(var f=0;f0;--b)a.removeChild(a.firstChild);return a}function Bf(a,b){return Af(a).appendChild(b)}function Cf(a,b){d?(a.innerHTML="",a.appendChild(document.createTextNode(b))):a.textContent=b}function Df(a){return a.getBoundingClientRect()}function Ff(){return!1}function Hf(a){if(null!=Gf)return Gf;var b=zf("div",null,null,"width: 50px; height: 50px; overflow-x: scroll");return Bf(a,b),b.offsetWidth&&(Gf=b.offsetHeight-b.clientHeight),Gf||0}function Jf(a){if(null==If){var b=zf("span","\u200b");Bf(a,zf("span",[b,document.createTextNode("x")])),0!=a.firstChild.offsetHeight&&(If=b.offsetWidth<=1&&b.offsetHeight>2&&!c)}return If?zf("span","\u200b"):zf("span","\xa0",null,"display: inline-block; width: 1px; margin-right: -1px")}function Of(a,b,c,d){if(!a)return d(b,c,"ltr");for(var e=!1,f=0;fb||b==c&&g.to==b)&&(d(Math.max(g.from,b),Math.min(g.to,c),1==g.level?"rtl":"ltr"),e=!0)}e||d(b,c,"ltr")}function Pf(a){return a.level%2?a.to:a.from}function Qf(a){return a.level%2?a.from:a.to}function Rf(a){var b=Fe(a);return b?Pf(b[0]):0}function Sf(a){var b=Fe(a);return b?Qf(nf(b)):a.text.length}function Tf(a,b){var c=ye(a.doc,b),d=Rd(a.doc,c);d!=c&&(b=Ce(d));var e=Fe(d),f=e?e[0].level%2?Sf(d):Rf(d):0;return Gc(b,f)}function Uf(a,b){for(var c,d;c=Pd(d=ye(a.doc,b));)b=c.find().to.line;var e=Fe(d),f=e?e[0].level%2?Rf(d):Sf(d):d.text.length;return Gc(b,f)}function Vf(a,b,c){var d=a[0].level;return b==d?!0:c==d?!1:c>b}function Xf(a,b){Wf=null;for(var d,c=0;cb)return c;if(e.from==b||e.to==b){if(null!=d)return Vf(a,e.level,a[d].level)?(e.from!=e.to&&(Wf=d),c):(e.from!=e.to&&(Wf=c),d);d=c}}return d}function Yf(a,b,c,d){if(!d)return b+c;do b+=c;while(b>0&&yf(a.text.charAt(b)));return b}function Zf(a,b,c,d){var e=Fe(a);if(!e)return $f(a,b,c,d);for(var f=Xf(e,b),g=e[f],h=Yf(a,b,g.level%2?-c:c,d);;){if(h>g.from&&h0==g.level%2?g.to:g.from);if(g=e[f+=c],!g)return null;h=c>0==g.level%2?Yf(a,g.to,-1,d):Yf(a,g.from,1,d)}}function $f(a,b,c,d){var e=b+c;if(d)for(;e>0&&yf(a.text.charAt(e));)e+=c;return 0>e||e>a.text.length?null:e}var a=/gecko\/\d/i.test(navigator.userAgent),b=/MSIE \d/.test(navigator.userAgent),c=b&&(null==document.documentMode||document.documentMode<8),d=b&&(null==document.documentMode||document.documentMode<9),e=b&&(null==document.documentMode||document.documentMode<10),f=/Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent),g=b||f,h=/WebKit\//.test(navigator.userAgent),i=h&&/Qt\/\d+\.\d+/.test(navigator.userAgent),j=/Chrome\//.test(navigator.userAgent),k=/Opera\//.test(navigator.userAgent),l=/Apple Computer/.test(navigator.vendor),m=/KHTML\//.test(navigator.userAgent),n=/Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent),o=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent),p=/PhantomJS/.test(navigator.userAgent),q=/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent),r=q||/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent),s=q||/Mac/.test(navigator.platform),t=/win/i.test(navigator.platform),u=k&&navigator.userAgent.match(/Version\/(\d*\.\d*)/);u&&(u=Number(u[1])),u&&u>=15&&(k=!1,h=!0);var Cb,Wb,Xb,v=s&&(i||k&&(null==u||12.11>u)),w=a||g&&!d,x=!1,y=!1,Fb=0,ac=0,fc=0,gc=null;g?gc=-.53:a?gc=15:j?gc=-.7:l&&(gc=-1/3);var kc,tc,oc=null,vc=z.changeEnd=function(a){return a.text?Gc(a.from.line+a.text.length-1,nf(a.text).length+(1==a.text.length?a.from.ch:0)):a.to};z.Pos=Gc,z.prototype={constructor:z,focus:function(){window.focus(),Qb(this),Nb(this)},setOption:function(a,b){var c=this.options,d=c[a];(c[a]!=b||"mode"==a)&&(c[a]=b,ed.hasOwnProperty(a)&&Ib(this,ed[a])(this,b,d))},getOption:function(a){return this.options[a]},getDoc:function(){return this.doc},addKeyMap:function(a,b){this.state.keyMaps[b?"push":"unshift"](a)},removeKeyMap:function(a){for(var b=this.state.keyMaps,c=0;c=d;++d)$c(this,d,a)}),getTokenAt:function(a,b){var c=this.doc;a=Mc(c,a);for(var d=hb(this,a.line,b),e=this.doc.mode,f=ye(c,a.line),g=new vd(f.text,this.options.tabSize);g.pos>1;if((f?b[2*f-1]:0)>=e)d=f;else{if(!(b[2*f+1]d&&(a=d,c=!0);var e=ye(this.doc,a);return vb(this,ye(this.doc,a),{top:0,left:0},b||"page").top+(c?e.height:0)},defaultTextHeight:function(){return Db(this.display)},defaultCharWidth:function(){return Eb(this.display)},setGutterMarker:Ib(null,function(a,b,c){return _c(this,a,function(a){var d=a.gutterMarkers||(a.gutterMarkers={});return d[b]=c,!c&&wf(d)&&(a.gutterMarkers=null),!0})}),clearGutter:Ib(null,function(a){var b=this,c=b.doc,d=c.first;c.iter(function(c){c.gutterMarkers&&c.gutterMarkers[a]&&(c.gutterMarkers[a]=null,Lb(b,d,d+1),wf(c.gutterMarkers)&&(c.gutterMarkers=null)),++d})}),addLineClass:Ib(null,function(a,b,c){return _c(this,a,function(a){var d="text"==b?"textClass":"background"==b?"bgClass":"wrapClass";if(a[d]){if(new RegExp("(?:^|\\s)"+c+"(?:$|\\s)").test(a[d]))return!1;a[d]+=" "+c}else a[d]=c;return!0})}),removeLineClass:Ib(null,function(a,b,c){return _c(this,a,function(a){var d="text"==b?"textClass":"background"==b?"bgClass":"wrapClass",e=a[d];if(!e)return!1;if(null==c)a[d]=null;else{var f=e.match(new RegExp("(?:^|\\s+)"+c+"(?:$|\\s+)"));if(!f)return!1;var g=f.index+f[0].length;a[d]=e.slice(0,f.index)+(f.index&&g!=e.length?" ":"")+e.slice(g)||null}return!0})}),addLineWidget:Ib(null,function(a,b,c){return Zd(this,a,b,c)}),removeLineWidget:function(a){a.clear()},lineInfo:function(a){if("number"==typeof a){if(!Oc(this.doc,a))return null;var b=a;if(a=ye(this.doc,a),!a)return null}else{var b=Ce(a);if(null==b)return null}return{line:b,handle:a,text:a.text,gutterMarkers:a.gutterMarkers,textClass:a.textClass,bgClass:a.bgClass,wrapClass:a.wrapClass,widgets:a.widgets}},getViewport:function(){return{from:this.display.showingFrom,to:this.display.showingTo}},addWidget:function(a,b,c,d,e){var f=this.display;a=yb(this,Mc(this.doc,a));var g=a.bottom,h=a.left;if(b.style.position="absolute",f.sizer.appendChild(b),"over"==d)g=a.top;else if("above"==d||"near"==d){var i=Math.max(f.wrapper.clientHeight,this.doc.height),j=Math.max(f.sizer.clientWidth,f.lineSpace.clientWidth);("above"==d||a.bottom+b.offsetHeight>i)&&a.top>b.offsetHeight?g=a.top-b.offsetHeight:a.bottom+b.offsetHeight<=i&&(g=a.bottom),h+b.offsetWidth>j&&(h=j-b.offsetWidth)}b.style.top=g+"px",b.style.left=b.style.right="","right"==e?(h=f.sizer.clientWidth-b.offsetWidth,b.style.right="0px"):("left"==e?h=0:"middle"==e&&(h=(f.sizer.clientWidth-b.offsetWidth)/2),b.style.left=h+"px"),c&&Wc(this,h,g,h+b.offsetWidth,g+b.offsetHeight)},triggerOnKeyDown:Ib(null,pc),triggerOnKeyPress:Ib(null,qc),triggerOnKeyUp:Ib(null,nc),execCommand:function(a){return pd.hasOwnProperty(a)?pd[a](this):void 0},findPosH:function(a,b,c,d){var e=1;0>b&&(e=-1,b=-b);for(var f=0,g=Mc(this.doc,a);b>f&&(g=ad(this.doc,g,e,c,d),!g.hitSide);++f);return g},moveH:Ib(null,function(a,b){var d,c=this.doc.sel;d=c.shift||c.extend||Hc(c.from,c.to)?ad(this.doc,c.head,a,b,this.options.rtlMoveVisually):0>a?c.from:c.to,Pc(this.doc,d,d,a)}),deleteH:Ib(null,function(a,b){var c=this.doc.sel;Hc(c.from,c.to)?Fc(this.doc,"",c.from,ad(this.doc,c.head,a,b,!1),"+delete"):Fc(this.doc,"",c.from,c.to,"+delete"),this.curOp.userSelChange=!0}),findPosV:function(a,b,c,d){var e=1,f=d;0>b&&(e=-1,b=-b);for(var g=0,h=Mc(this.doc,a);b>g;++g){var i=yb(this,h,"div");if(null==f?f=i.left:i.left=f,h=bd(this,i,e,c),h.hitSide)break}return h},moveV:Ib(null,function(a,b){var d,e,c=this.doc.sel;if(c.shift||c.extend||Hc(c.from,c.to)){var f=yb(this,c.head,"div");null!=c.goalColumn&&(f.left=c.goalColumn),d=bd(this,f,a,b),"page"==b&&Zc(this,0,xb(this,d,"div").top-f.top),e=f.left}else d=0>a?c.from:c.to;Pc(this.doc,d,d,a),null!=e&&(c.goalColumn=e)}),toggleOverwrite:function(a){(null==a||a!=this.state.overwrite)&&((this.state.overwrite=!this.state.overwrite)?this.display.cursor.className+=" CodeMirror-overwrite":this.display.cursor.className=this.display.cursor.className.replace(" CodeMirror-overwrite",""),$e(this,"overwriteToggle",this,this.state.overwrite))},hasFocus:function(){return document.activeElement==this.display.input},scrollTo:Ib(null,function(a,b){Yc(this,a,b)}),getScrollInfo:function(){var a=this.display.scroller,b=gf;return{left:a.scrollLeft,top:a.scrollTop,height:a.scrollHeight-b,width:a.scrollWidth-b,clientHeight:a.clientHeight-b,clientWidth:a.clientWidth-b}},scrollIntoView:Ib(null,function(a,b){null==a?a={from:this.doc.sel.head,to:null}:"number"==typeof a?a={from:Gc(a,0),to:null}:null==a.from&&(a={from:a,to:null}),a.to||(a.to=a.from),b||(b=0);var c=a;null!=a.from.line&&(this.curOp.scrollToPos={from:a.from,to:a.to,margin:b},c={from:yb(this,a.from),to:yb(this,a.to)});var d=Xc(this,Math.min(c.from.left,c.to.left),Math.min(c.from.top,c.to.top)-b,Math.max(c.from.right,c.to.right),Math.max(c.from.bottom,c.to.bottom)+b);Yc(this,d.scrollLeft,d.scrollTop)}),setSize:Ib(null,function(a,b){function c(a){return"number"==typeof a||/^\d+$/.test(String(a))?a+"px":a}null!=a&&(this.display.wrapper.style.width=c(a)),null!=b&&(this.display.wrapper.style.height=c(b)),this.options.lineWrapping&&(this.display.measureLineCache.length=this.display.measureLineCachePos=0),this.curOp.forceUpdate=!0,$e(this,"refresh",this)}),operation:function(a){return Kb(this,a)},refresh:Ib(null,function(){var a=this.display.cachedTextHeight;sb(this),Yc(this,this.doc.scrollLeft,this.doc.scrollTop),Lb(this),(null==a||Math.abs(a-Db(this.display))>.5)&&F(this),$e(this,"refresh",this)}),swapDoc:Ib(null,function(a){var b=this.doc;return b.cm=null,xe(this,a),sb(this),Pb(this,!0),Yc(this,a.scrollLeft,a.scrollTop),bf(this,"swapDoc",this,b),b}),getInputField:function(){return this.display.input},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},ff(z);var ed=z.optionHandlers={},fd=z.defaults={},hd=z.Init={toString:function(){return"CodeMirror.Init"}};gd("value","",function(a,b){a.setValue(b)},!0),gd("mode",null,function(a,b){a.doc.modeOption=b,B(a)},!0),gd("indentUnit",2,B,!0),gd("indentWithTabs",!1),gd("smartIndent",!0),gd("tabSize",4,function(a){C(a),sb(a),Lb(a)},!0),gd("specialChars",/[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g,function(a,b){a.options.specialChars=new RegExp(b.source+(b.test(" ")?"":"| "),"g"),a.refresh()},!0),gd("specialCharPlaceholder",je,function(a){a.refresh()},!0),gd("electricChars",!0),gd("rtlMoveVisually",!t),gd("wholeLineUpdateBefore",!0),gd("theme","default",function(a){H(a),I(a)},!0),gd("keyMap","default",G),gd("extraKeys",null),gd("onKeyEvent",null),gd("onDragEvent",null),gd("lineWrapping",!1,D,!0),gd("gutters",[],function(a){M(a.options),I(a)},!0),gd("fixedGutter",!0,function(a,b){a.display.gutters.style.left=b?S(a.display)+"px":"0",a.refresh()},!0),gd("coverGutterNextToScrollbar",!1,N,!0),gd("lineNumbers",!1,function(a){M(a.options),I(a)},!0),gd("firstLineNumber",1,I,!0),gd("lineNumberFormatter",function(a){return a},I,!0),gd("showCursorWhenSelecting",!1,ab,!0),gd("resetSelectionOnContextMenu",!0),gd("readOnly",!1,function(a,b){"nocursor"==b?(sc(a),a.display.input.blur(),a.display.disabled=!0):(a.display.disabled=!1,b||Pb(a,!0))}),gd("disableInput",!1,function(a,b){b||Pb(a,!0)},!0),gd("dragDrop",!0),gd("cursorBlinkRate",530),gd("cursorScrollMargin",0),gd("cursorHeight",1),gd("workTime",100),gd("workDelay",100),gd("flattenSpans",!0,C,!0),gd("addModeClass",!1,C,!0),gd("pollInterval",100),gd("undoDepth",40,function(a,b){a.doc.history.undoDepth=b}),gd("historyEventDelay",500),gd("viewportMargin",10,function(a){a.refresh()},!0),gd("maxHighlightLength",1e4,C,!0),gd("crudeMeasuringFrom",1e4),gd("moveInputWithCursor",!0,function(a,b){b||(a.display.inputDiv.style.top=a.display.inputDiv.style.left=0)}),gd("tabindex",null,function(a,b){a.display.input.tabIndex=b||""}),gd("autofocus",null);var id=z.modes={},jd=z.mimeModes={};z.defineMode=function(a,b){if(z.defaults.mode||"null"==a||(z.defaults.mode=a),arguments.length>2){b.dependencies=[];for(var c=2;c0&&b.ch=this.string.length},sol:function(){return this.pos==this.lineStart},peek:function(){return this.string.charAt(this.pos)||void 0},next:function(){return this.posb},eatSpace:function(){for(var a=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>a},skipToEnd:function(){this.pos=this.string.length},skipTo:function(a){var b=this.string.indexOf(a,this.pos);return b>-1?(this.pos=b,!0):void 0},backUp:function(a){this.pos-=a},column:function(){return this.lastColumnPos0?null:(f&&b!==!1&&(this.pos+=f[0].length),f)}var d=function(a){return c?a.toLowerCase():a},e=this.string.substr(this.pos,a.length);return d(e)==d(a)?(b!==!1&&(this.pos+=a.length),!0):void 0},current:function(){return this.string.slice(this.start,this.pos)},hideFirstChars:function(a,b){this.lineStart+=a;try{return b()}finally{this.lineStart-=a}}},z.StringStream=vd,z.TextMarker=wd,ff(wd),wd.prototype.clear=function(){if(!this.explicitlyCleared){var a=this.doc.cm,b=a&&!a.curOp;if(b&&Gb(a),ef(this,"clear")){var c=this.find();c&&bf(this,"clear",c.from,c.to)}for(var d=null,e=null,f=0;fa.display.maxLineLength&&(a.display.maxLine=i,a.display.maxLineLength=j,a.display.maxLineChanged=!0)}null!=d&&a&&Lb(a,d,e+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,a&&Sc(a)),bf(a,"markerCleared",a,this),b&&Hb(a)}},wd.prototype.find=function(a){for(var b,c,d=0;d=b.display.showingFrom&&a.linec;++c){var e=this.lines[c];this.height-=e.height,ae(e),bf(e,"delete")}this.lines.splice(a,b)},collapse:function(a){a.splice.apply(a,[a.length,0].concat(this.lines))},insertInner:function(a,b,c){this.height+=c,this.lines=this.lines.slice(0,a).concat(b).concat(this.lines.slice(a));for(var d=0,e=b.length;e>d;++d)b[d].parent=this},iterN:function(a,b,c){for(var d=a+b;d>a;++a)if(c(this.lines[a]))return!0}},re.prototype={chunkSize:function(){return this.size},removeInner:function(a,b){this.size-=b;for(var c=0;ca){var f=Math.min(b,e-a),g=d.height;if(d.removeInner(a,f),this.height-=g-d.height,e==f&&(this.children.splice(c--,1),d.parent=null),0==(b-=f))break;a=0}else a-=e}if(this.size-b<25){var h=[];this.collapse(h),this.children=[new qe(h)],this.children[0].parent=this}},collapse:function(a){for(var b=0,c=this.children.length;c>b;++b)this.children[b].collapse(a)},insertInner:function(a,b,c){this.size+=b.length,this.height+=c;for(var d=0,e=this.children.length;e>d;++d){var f=this.children[d],g=f.chunkSize();if(g>=a){if(f.insertInner(a,b,c),f.lines&&f.lines.length>50){for(;f.lines.length>50;){var h=f.lines.splice(f.lines.length-25,25),i=new qe(h);f.height-=i.height,this.children.splice(d+1,0,i),i.parent=this}this.maybeSpill()}break}a-=g}},maybeSpill:function(){if(!(this.children.length<=10)){var a=this;do{var b=a.children.splice(a.children.length-5,5),c=new re(b);if(a.parent){a.size-=c.size,a.height-=c.height;var e=pf(a.parent.children,a);a.parent.children.splice(e+1,0,c)}else{var d=new re(a.children);d.parent=a,a.children=[d,c],a=d}c.parent=a.parent}while(a.children.length>10);a.parent.maybeSpill()}},iterN:function(a,b,c){for(var d=0,e=this.children.length;e>d;++d){var f=this.children[d],g=f.chunkSize();if(g>a){var h=Math.min(b,g-a);if(f.iterN(a,h,c))return!0;if(0==(b-=h))break;a=0}else a-=g}}};var se=0,te=z.Doc=function(a,b,c){if(!(this instanceof te))return new te(a,b,c);null==c&&(c=0),re.call(this,[new qe([new $d("",null)])]),this.first=c,this.scrollTop=this.scrollLeft=0,this.cantEdit=!1,this.history=Ge(),this.cleanGeneration=1,this.frontier=c;var d=Gc(c,0);this.sel={from:d,to:d,head:d,anchor:d,shift:!1,extend:!1,goalColumn:null},this.id=++se,this.modeOption=b,"string"==typeof a&&(a=Kf(a)),pe(this,{from:d,to:d,text:a},null,{head:d,anchor:d})};te.prototype=qf(re.prototype,{constructor:te,iter:function(a,b,c){c?this.iterN(a-this.first,b-a,c):this.iterN(this.first,this.first+this.size,a)},insert:function(a,b){for(var c=0,d=0,e=b.length;e>d;++d)c+=b[d].height;this.insertInner(a-this.first,b,c)},remove:function(a,b){this.removeInner(a-this.first,b)},getValue:function(a){var b=Ae(this,this.first,this.first+this.size);return a===!1?b:b.join(a||"\n")},setValue:function(a){var b=Gc(this.first,0),c=this.first+this.size-1;zc(this,{from:b,to:Gc(c,ye(this,c).text.length),text:Kf(a),origin:"setValue"},{head:b,anchor:b},!0)},replaceRange:function(a,b,c,d){b=Mc(this,b),c=c?Mc(this,c):b,Fc(this,a,b,c,d)},getRange:function(a,b,c){var d=ze(this,Mc(this,a),Mc(this,b));return c===!1?d:d.join(c||"\n")},getLine:function(a){var b=this.getLineHandle(a);return b&&b.text},setLine:function(a,b){Oc(this,a)&&Fc(this,b,Gc(a,0),Mc(this,Gc(a)))},removeLine:function(a){a?Fc(this,"",Mc(this,Gc(a-1)),Mc(this,Gc(a))):Fc(this,"",Gc(0,0),Mc(this,Gc(1,0)))},getLineHandle:function(a){return Oc(this,a)?ye(this,a):void 0},getLineNumber:function(a){return Ce(a)},getLineHandleVisualStart:function(a){return"number"==typeof a&&(a=ye(this,a)),Rd(this,a)},lineCount:function(){return this.size},firstLine:function(){return this.first},lastLine:function(){return this.first+this.size-1},clipPos:function(a){return Mc(this,a)},getCursor:function(a){var c,b=this.sel;return c=null==a||"head"==a?b.head:"anchor"==a?b.anchor:"end"==a||a===!1?b.to:b.from,Kc(c)},somethingSelected:function(){return!Hc(this.sel.head,this.sel.anchor)},setCursor:Jb(function(a,b,c){var d=Mc(this,"number"==typeof a?Gc(a,b||0):a);c?Pc(this,d):Rc(this,d,d)}),setSelection:Jb(function(a,b,c){Rc(this,Mc(this,a),Mc(this,b||a),c)}),extendSelection:Jb(function(a,b,c){Pc(this,Mc(this,a),b&&Mc(this,b),c)}),getSelection:function(a){return this.getRange(this.sel.from,this.sel.to,a)},replaceSelection:function(a,b,c){zc(this,{from:this.sel.from,to:this.sel.to,text:Kf(a),origin:c},b||"around")},undo:Jb(function(){Bc(this,"undo")}),redo:Jb(function(){Bc(this,"redo")}),setExtending:function(a){this.sel.extend=a},historySize:function(){var a=this.history;return{undo:a.done.length,redo:a.undone.length}},clearHistory:function(){this.history=Ge(this.history.maxGeneration)},markClean:function(){this.cleanGeneration=this.changeGeneration(!0)},changeGeneration:function(a){return a&&(this.history.lastOp=this.history.lastOrigin=null),this.history.generation},isClean:function(a){return this.history.generation==(a||this.cleanGeneration)},getHistory:function(){return{done:Me(this.history.done),undone:Me(this.history.undone)}},setHistory:function(a){var b=this.history=Ge(this.history.maxGeneration);b.done=a.done.slice(0),b.undone=a.undone.slice(0)},markText:function(a,b,c){return yd(this,Mc(this,a),Mc(this,b),c,"range")},setBookmark:function(a,b){var c={replacedWith:b&&(null==b.nodeType?b.widget:b),insertLeft:b&&b.insertLeft,clearWhenEmpty:!1};return a=Mc(this,a),yd(this,a,a,c,"bookmark")},findMarksAt:function(a){a=Mc(this,a);var b=[],c=ye(this,a.line).markedSpans;if(c)for(var d=0;d=a.ch)&&b.push(e.marker.parent||e.marker)}return b},findMarks:function(a,b){a=Mc(this,a),b=Mc(this,b);var c=[],d=a.line;return this.iter(a.line,b.line+1,function(e){var f=e.markedSpans;if(f)for(var g=0;gh.to||null==h.from&&d!=a.line||d==b.line&&h.from>b.ch||c.push(h.marker.parent||h.marker)}++d}),c},getAllMarks:function(){var a=[];return this.iter(function(b){var c=b.markedSpans;if(c)for(var d=0;da?(b=a,!0):(a-=e,++c,void 0)}),Mc(this,Gc(c,b))},indexFromPos:function(a){a=Mc(this,a);var b=a.ch;return a.lineb&&(b=a.from),null!=a.to&&a.to=8208&&8212>=c}:h&&(Ff=function(a,b){if(b>1&&45==a.charCodeAt(b-1)){if(/\w/.test(a.charAt(b-2))&&/[^\-?\.]/.test(a.charAt(b)))return!0;if(b>2&&/[\d\.,]/.test(a.charAt(b-2))&&/[\d\.,]/.test(a.charAt(b)))return!1}return/[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(a.slice(b-1,b+1))});var Gf,If,Kf=3!="\n\nb".split(/\n/).length?function(a){for(var b=0,c=[],d=a.length;d>=b;){var e=a.indexOf("\n",b);-1==e&&(e=a.length);var f=a.slice(b,"\r"==a.charAt(e-1)?e-1:e),g=f.indexOf("\r");-1!=g?(c.push(f.slice(0,g)),b+=g+1):(c.push(f),b=e+1)}return c}:function(a){return a.split(/\r\n?|\n/)};z.splitLines=Kf;var Lf=window.getSelection?function(a){try{return a.selectionStart!=a.selectionEnd}catch(b){return!1}}:function(a){try{var b=a.ownerDocument.selection.createRange()}catch(c){}return b&&b.parentElement()==a?0!=b.compareEndPoints("StartToEnd",b):!1},Mf=function(){var a=zf("div");return"oncopy"in a?!0:(a.setAttribute("oncopy","return;"),"function"==typeof a.oncopy)}(),Nf={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",61:"=",91:"Mod",92:"Mod",93:"Mod",107:"=",109:"-",127:"Delete",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63232:"Up",63233:"Down",63234:"Left",63235:"Right",63272:"Delete",63273:"Home",63275:"End",63276:"PageUp",63277:"PageDown",63302:"Insert"};z.keyNames=Nf,function(){for(var a=0;10>a;a++)Nf[a+48]=Nf[a+96]=String(a);for(var a=65;90>=a;a++)Nf[a]=String.fromCharCode(a);for(var a=1;12>=a;a++)Nf[a+111]=Nf[a+63235]="F"+a}();var Wf,_f=function(){function c(c){return 255>=c?a.charAt(c):c>=1424&&1524>=c?"R":c>=1536&&1791>=c?b.charAt(c-1536):c>=1792&&2220>=c?"r":"L"}var a="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL",b="rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr",d=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,e=/[stwN]/,f=/[LRr]/,g=/[Lb1n]/,h=/[1n]/,i="L";return function(a){if(!d.test(a))return!1;for(var l,b=a.length,j=[],k=0;b>k;++k)j.push(l=c(a.charCodeAt(k)));for(var k=0,m=i;b>k;++k){var l=j[k];"m"==l?j[k]=m:m=l}for(var k=0,n=i;b>k;++k){var l=j[k];"1"==l&&"r"==n?j[k]="n":f.test(l)&&(n=l,"r"==l&&(j[k]="R"))}for(var k=1,m=j[0];b-1>k;++k){var l=j[k];"+"==l&&"1"==m&&"1"==j[k+1]?j[k]="1":","!=l||m!=j[k+1]||"1"!=m&&"n"!=m||(j[k]=m),m=l}for(var k=0;b>k;++k){var l=j[k];if(","==l)j[k]="N";else if("%"==l){for(var o=k+1;b>o&&"%"==j[o];++o);for(var p=k&&"!"==j[k-1]||b>o&&"1"==j[o]?"1":"N",q=k;o>q;++q)j[q]=p;k=o-1}}for(var k=0,n=i;b>k;++k){var l=j[k];"L"==n&&"1"==l?j[k]="L":f.test(l)&&(n=l)}for(var k=0;b>k;++k)if(e.test(j[k])){for(var o=k+1;b>o&&e.test(j[o]);++o);for(var r="L"==(k?j[k-1]:i),s="L"==(b>o?j[o]:i),p=r||s?"L":"R",q=k;o>q;++q)j[q]=p;k=o-1}for(var u,t=[],k=0;b>k;)if(g.test(j[k])){var v=k;for(++k;b>k&&g.test(j[k]);++k);t.push({from:v,to:k,level:0})}else{var w=k,x=t.length;for(++k;b>k&&"L"!=j[k];++k);for(var q=w;k>q;)if(h.test(j[q])){q>w&&t.splice(x,0,{from:w,to:q,level:1});var y=q;for(++q;k>q&&h.test(j[q]);++q);t.splice(x,0,{from:y,to:q,level:2}),w=q}else++q;k>w&&t.splice(x,0,{from:w,to:k,level:1})}return 1==t[0].level&&(u=a.match(/^\s+/))&&(t[0].from=u[0].length,t.unshift({from:0,to:u[0].length,level:0})),1==nf(t).level&&(u=a.match(/\s+$/))&&(nf(t).to-=u[0].length,t.push({from:b-u[0].length,to:b,level:0})),t[0].level!=nf(t).level&&t.push({from:b,to:b,level:t[0].level}),t}}();return z.version="3.22.1",z}(),CodeMirror.defineMode("javascript",function(a,b){function k(a){for(var c,b=!1,d=!1;null!=(c=a.next());){if(!b){if("/"==c&&!d)return;"["==c?d=!0:d&&"]"==c&&(d=!1)}b=!b&&"\\"==c}}function n(a,b,c){return l=a,m=c,b}function o(a,b){var c=a.next();if('"'==c||"'"==c)return b.tokenize=p(c),b.tokenize(a,b);if("."==c&&a.match(/^\d+(?:[eE][+\-]?\d+)?/))return n("number","number");if("."==c&&a.match(".."))return n("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(c))return n(c);if("="==c&&a.eat(">"))return n("=>","operator");if("0"==c&&a.eat(/x/i))return a.eatWhile(/[\da-f]/i),n("number","number");if(/\d/.test(c))return a.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/),n("number","number");if("/"==c)return a.eat("*")?(b.tokenize=q,q(a,b)):a.eat("/")?(a.skipToEnd(),n("comment","comment")):"operator"==b.lastType||"keyword c"==b.lastType||"sof"==b.lastType||/^[\[{}\(,;:]$/.test(b.lastType)?(k(a),a.eatWhile(/[gimy]/),n("regexp","string-2")):(a.eatWhile(i),n("operator","operator",a.current()));if("`"==c)return b.tokenize=r,r(a,b);if("#"==c)return a.skipToEnd(),n("error","error");if(i.test(c))return a.eatWhile(i),n("operator","operator",a.current());a.eatWhile(/[\w\$_]/);var d=a.current(),e=h.propertyIsEnumerable(d)&&h[d];return e&&"."!=b.lastType?n(e.type,e.style,d):n("variable","variable",d)}function p(a){return function(b,c){var f,d=!1;if(e&&"@"==b.peek()&&b.match(j))return c.tokenize=o,n("jsonld-keyword","meta");for(;null!=(f=b.next())&&(f!=a||d);)d=!d&&"\\"==f;return d||(c.tokenize=o),n("string","string")}}function q(a,b){for(var d,c=!1;d=a.next();){if("/"==d&&c){b.tokenize=o;break}c="*"==d}return n("comment","comment")}function r(a,b){for(var d,c=!1;null!=(d=a.next());){if(!c&&("`"==d||"$"==d&&a.eat("{"))){b.tokenize=o;break}c=!c&&"\\"==d}return n("quasi","string-2",a.current())}function t(a,b){b.fatArrowAt&&(b.fatArrowAt=null);var c=a.string.indexOf("=>",a.start);if(!(0>c)){for(var d=0,e=!1,f=c-1;f>=0;--f){var g=a.string.charAt(f),h=s.indexOf(g);if(h>=0&&3>h){if(!d){++f;break}if(0==--d)break}else if(h>=3&&6>h)++d;else if(/[$\w]/.test(g))e=!0;else if(e&&!d){++f;break}}e&&!d&&(b.fatArrowAt=f)}}function v(a,b,c,d,e,f){this.indented=a,this.column=b,this.type=c,this.prev=e,this.info=f,null!=d&&(this.align=d)}function w(a,b){for(var c=a.localVars;c;c=c.next)if(c.name==b)return!0;for(var d=a.context;d;d=d.prev)for(var c=d.vars;c;c=c.next)if(c.name==b)return!0}function x(a,b,c,d,e){var g=a.cc;for(y.state=a,y.stream=e,y.marked=null,y.cc=g,a.lexical.hasOwnProperty("align")||(a.lexical.align=!0);;){var h=g.length?g.pop():f?J:I;if(h(c,d)){for(;g.length&&g[g.length-1].lex;)g.pop()();return y.marked?y.marked:"variable"==c&&w(a,d)?"variable-2":b}}}function z(){for(var a=arguments.length-1;a>=0;a--)y.cc.push(arguments[a])}function A(){return z.apply(null,arguments),!0}function B(a){function c(b){for(var c=b;c;c=c.next)if(c.name==a)return!0;return!1}var d=y.state;if(d.context){if(y.marked="def",c(d.localVars))return;d.localVars={name:a,next:d.localVars}}else{if(c(d.globalVars))return;b.globalVars&&(d.globalVars={name:a,next:d.globalVars})}}function D(){y.state.context={prev:y.state.context,vars:y.state.localVars},y.state.localVars=C}function E(){y.state.localVars=y.state.context.vars,y.state.context=y.state.context.prev}function F(a,b){var c=function(){var c=y.state,d=c.indented;"stat"==c.lexical.type&&(d=c.lexical.indented),c.lexical=new v(d,y.stream.column(),a,null,c.lexical,b)};return c.lex=!0,c}function G(){var a=y.state;a.lexical.prev&&(")"==a.lexical.type&&(a.indented=a.lexical.indented),a.lexical=a.lexical.prev)}function H(a){return function(b){return b==a?A():";"==a?z():A(arguments.callee)}}function I(a,b){return"var"==a?A(F("vardef",b.length),cb,H(";"),G):"keyword a"==a?A(F("form"),J,I,G):"keyword b"==a?A(F("form"),I,G):"{"==a?A(F("}"),_,G):";"==a?A():"if"==a?A(F("form"),J,I,G,hb):"function"==a?A(nb):"for"==a?A(F("form"),ib,I,G):"variable"==a?A(F("stat"),U):"switch"==a?A(F("form"),J,F("}","switch"),H("{"),_,G,G):"case"==a?A(J,H(":")):"default"==a?A(H(":")):"catch"==a?A(F("form"),D,H("("),ob,H(")"),I,G,E):"module"==a?A(F("form"),D,sb,E,G):"class"==a?A(F("form"),pb,rb,G):"export"==a?A(F("form"),tb,G):"import"==a?A(F("form"),ub,G):z(F("stat"),J,H(";"),G)}function J(a){return L(a,!1)}function K(a){return L(a,!0)}function L(a,b){if(y.state.fatArrowAt==y.stream.start){var c=b?T:S;if("("==a)return A(D,F(")"),Z(db,")"),G,H("=>"),c,E);if("variable"==a)return z(D,db,H("=>"),c,E)}var d=b?P:O;return u.hasOwnProperty(a)?A(d):"function"==a?A(nb):"keyword c"==a?A(b?N:M):"("==a?A(F(")"),M,zb,H(")"),G,d):"operator"==a||"spread"==a?A(b?K:J):"["==a?A(F("]"),xb,G,d):"{"==a?$(W,"}",null,d):A()}function M(a){return a.match(/[;\}\)\],]/)?z():z(J)}function N(a){return a.match(/[;\}\)\],]/)?z():z(K)}function O(a,b){return","==a?A(J):P(a,b,!1)}function P(a,b,c){var d=0==c?O:P,e=0==c?J:K;return"=>"==b?A(D,c?T:S,E):"operator"==a?/\+\+|--/.test(b)?A(d):"?"==b?A(J,H(":"),e):A(e):"quasi"==a?(y.cc.push(d),Q(b)):";"!=a?"("==a?$(K,")","call",d):"."==a?A(V,d):"["==a?A(F("]"),M,H("]"),G,d):void 0:void 0}function Q(a){return"${"!=a.slice(a.length-2)?A():A(J,R)}function R(a){return"}"==a?(y.marked="string-2",y.state.tokenize=r,A()):void 0}function S(a){return t(y.stream,y.state),"{"==a?z(I):z(J)}function T(a){return t(y.stream,y.state),"{"==a?z(I):z(K)}function U(a){return":"==a?A(G,I):z(O,H(";"),G)}function V(a){return"variable"==a?(y.marked="property",A()):void 0}function W(a,b){if("variable"==a){if(y.marked="property","get"==b||"set"==b)return A(X)}else if("number"==a||"string"==a)y.marked=e?"property":a+" property";else if("["==a)return A(J,H("]"),Y);return u.hasOwnProperty(a)?A(Y):void 0}function X(a){return"variable"!=a?z(Y):(y.marked="property",A(nb))}function Y(a){return":"==a?A(K):"("==a?z(nb):void 0}function Z(a,b){function c(d){if(","==d){var e=y.state.lexical;return"call"==e.info&&(e.pos=(e.pos||0)+1),A(a,c)}return d==b?A():A(H(b))}return function(d){return d==b?A():z(a,c)}}function $(a,b,c){for(var d=3;d!?|~^]/,j=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/,s="([{}])",u={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,"this":!0,"jsonld-keyword":!0},y={state:null,column:null,marked:null,cc:null},C={name:"this",next:{name:"arguments"}};return G.lex=!0,{startState:function(a){var d={tokenize:o,lastType:"sof",cc:[],lexical:new v((a||0)-c,0,"block",!1),localVars:b.localVars,context:b.localVars&&{vars:b.localVars},indented:0};return b.globalVars&&(d.globalVars=b.globalVars),d},token:function(a,b){if(a.sol()&&(b.lexical.hasOwnProperty("align")||(b.lexical.align=!1),b.indented=a.indentation(),t(a,b)),b.tokenize!=q&&a.eatSpace())return null;var c=b.tokenize(a,b);return"comment"==l?c:(b.lastType="operator"!=l||"++"!=m&&"--"!=m?l:"incdec",x(b,c,l,m,a))},indent:function(a,e){if(a.tokenize==q)return CodeMirror.Pass;if(a.tokenize!=o)return 0;for(var f=e&&e.charAt(0),g=a.lexical,h=a.cc.length-1;h>=0;--h){var i=a.cc[h];if(i==G)g=g.prev;else if(i!=hb)break}"stat"==g.type&&"}"==f&&(g=g.prev),d&&")"==g.type&&"stat"==g.prev.type&&(g=g.prev);var j=g.type,k=f==j;return"vardef"==j?g.indented+("operator"==a.lastType||","==a.lastType?g.info+1:0):"form"==j&&"{"==f?g.indented:"form"==j?g.indented+c:"stat"==j?g.indented+("operator"==a.lastType||","==a.lastType?d||c:0):"switch"!=g.info||k||0==b.doubleIndentSwitch?g.align?g.column+(k?0:1):g.indented+(k?0:c):g.indented+(/^(?:case|default)\b/.test(e)?c:2*c)},electricChars:":{}",blockCommentStart:f?null:"/*",blockCommentEnd:f?null:"*/",lineComment:f?null:"//",fold:"brace",helperType:f?"json":"javascript",jsonldMode:e,jsonMode:f}}),CodeMirror.defineMIME("text/javascript","javascript"),CodeMirror.defineMIME("text/ecmascript","javascript"),CodeMirror.defineMIME("application/javascript","javascript"),CodeMirror.defineMIME("application/ecmascript","javascript"),CodeMirror.defineMIME("application/json",{name:"javascript",json:!0}),CodeMirror.defineMIME("application/x-json",{name:"javascript",json:!0}),CodeMirror.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),CodeMirror.defineMIME("text/typescript",{name:"javascript",typescript:!0}),CodeMirror.defineMIME("application/typescript",{name:"javascript",typescript:!0}),function(){function d(a,d,e){function r(d,e,f){if(d.text){var h=m?0:d.text.length-1,i=m?d.text.length:-1;if(d.text.length>g)return null;for(null!=f&&(h=f+n);h!=i;h+=n){var j=d.text.charAt(h);if(q.test(j)&&a.getTokenTypeAt(b(e,h+1))==o){var k=c[j];if(">"==k.charAt(1)==m)p.push(j);else{if(p.pop()!=k.charAt(0))return{pos:h,match:!1};if(!p.length)return{pos:h,match:!0}}}}}}var f=a.state.matchBrackets,g=f&&f.maxScanLineLength||1e4,h=f&&f.maxScanLines||100,i=d||a.getCursor(),j=a.getLineHandle(i.line),k=i.ch-1,l=k>=0&&c[j.text.charAt(k)]||c[j.text.charAt(++k)];if(!l)return null;var m=">"==l.charAt(1),n=m?1:-1;if(e&&m!=(k==i.ch))return null;for(var t,o=a.getTokenTypeAt(b(i.line,k+1)),p=[j.text.charAt(k)],q=/[(){}[\]]/,s=i.line,u=m?Math.min(s+h,a.lineCount()):Math.max(-1,s-h);s!=u&&!(t=s==i.line?r(j,s,k):r(a.getLineHandle(s),s));s+=n);return{from:b(i.line,k),to:t&&b(s,t.pos),match:t&&t.match,forward:m}}function e(c,e){var f=c.state.matchBrackets.maxHighlightLineLength||1e3,g=d(c);if(!(!g||c.getLine(g.from.line).length>f||g.to&&c.getLine(g.to.line).length>f)){var h=g.match?"CodeMirror-matchingbracket":"CodeMirror-nonmatchingbracket",i=c.markText(g.from,b(g.from.line,g.from.ch+1),{className:h}),j=g.to&&c.markText(g.to,b(g.to.line,g.to.ch+1),{className:h});a&&c.state.focused&&c.display.input.focus();var k=function(){c.operation(function(){i.clear(),j&&j.clear()})};return e?(setTimeout(k,800),void 0):k -}}function g(a){a.operation(function(){f&&(f(),f=null),a.somethingSelected()||(f=e(a,!1))})}var a=/MSIE \d/.test(navigator.userAgent)&&(null==document.documentMode||document.documentMode<8),b=CodeMirror.Pos,c={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"},f=null;CodeMirror.defineOption("matchBrackets",!1,function(a,b,c){c&&c!=CodeMirror.Init&&a.off("cursorActivity",g),b&&(a.state.matchBrackets="object"==typeof b?b:{},a.on("cursorActivity",g))}),CodeMirror.defineExtension("matchBrackets",function(){e(this,!0)}),CodeMirror.defineExtension("findMatchingBracket",function(a,b){return d(this,a,b)})}(),CodeMirror.runMode=function(a,b,c,d){var e=CodeMirror.getMode(CodeMirror.defaults,b),f=/MSIE \d/.test(navigator.userAgent),g=f&&(null==document.documentMode||document.documentMode<9);if(1==c.nodeType){var h=d&&d.tabSize||CodeMirror.defaults.tabSize,i=c,j=0;i.innerHTML="",c=function(a,b){if("\n"==a)return i.appendChild(document.createTextNode(g?"\r":a)),j=0,void 0;for(var c="",d=0;;){var e=a.indexOf(" ",d);if(-1==e){c+=a.slice(d),j+=a.length-d;break}j+=e-d,c+=a.slice(d,e);var f=h-j%h;j+=f;for(var k=0;f>k;++k)c+=" ";d=e+1}if(b){var l=i.appendChild(document.createElement("span"));l.className="cm-"+b.replace(/ +/g," cm-"),l.appendChild(document.createTextNode(c))}else i.appendChild(document.createTextNode(c))}}for(var k=CodeMirror.splitLines(a),l=d&&d.state||CodeMirror.startState(e),m=0,n=k.length;n>m;++m){m&&c("\n");for(var o=new CodeMirror.StringStream(k[m]);!o.eol();){var p=e.token(o,l);c(o.current(),p,m,o.start,l),o.start=o.pos}}}; \ No newline at end of file diff --git a/addon-sdk/source/examples/actor-repl/data/codemirror.css b/addon-sdk/source/examples/actor-repl/data/codemirror.css deleted file mode 100644 index 2b050e19f..000000000 --- a/addon-sdk/source/examples/actor-repl/data/codemirror.css +++ /dev/null @@ -1,264 +0,0 @@ -/* BASICS */ - -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - font-family: monospace; - height: 300px; -} -.CodeMirror-scroll { - /* Set scrolling behaviour here */ - overflow: auto; -} - -/* PADDING */ - -.CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ -} -.CodeMirror pre { - padding: 0 4px; /* Horizontal padding of content */ -} - -.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; -} -.CodeMirror-linenumbers {} -.CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -/* CURSOR */ - -.CodeMirror div.CodeMirror-cursor { - border-left: 1px solid black; - z-index: 3; -} -/* Shown when moving in bi-directional text */ -.CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} -.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { - width: auto; - border: 0; - background: #7e7; - z-index: 1; -} -/* Can style cursor different in overwrite (non-insert) mode */ -.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} - -.cm-tab { display: inline-block; } - -.CodeMirror-ruler { - border-left: 1px solid #ccc; - position: absolute; -} - -/* DEFAULT THEME */ - -.cm-s-default .cm-keyword {color: #708;} -.cm-s-default .cm-atom {color: #219;} -.cm-s-default .cm-number {color: #164;} -.cm-s-default .cm-def {color: #00f;} -.cm-s-default .cm-variable {color: black;} -.cm-s-default .cm-variable-2 {color: #05a;} -.cm-s-default .cm-variable-3 {color: #085;} -.cm-s-default .cm-property {color: black;} -.cm-s-default .cm-operator {color: black;} -.cm-s-default .cm-comment {color: #a50;} -.cm-s-default .cm-string {color: #a11;} -.cm-s-default .cm-string-2 {color: #f50;} -.cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-qualifier {color: #555;} -.cm-s-default .cm-builtin {color: #30a;} -.cm-s-default .cm-bracket {color: #997;} -.cm-s-default .cm-tag {color: #170;} -.cm-s-default .cm-attribute {color: #00c;} -.cm-s-default .cm-header {color: blue;} -.cm-s-default .cm-quote {color: #090;} -.cm-s-default .cm-hr {color: #999;} -.cm-s-default .cm-link {color: #00c;} - -.cm-negative {color: #d44;} -.cm-positive {color: #292;} -.cm-header, .cm-strong {font-weight: bold;} -.cm-em {font-style: italic;} -.cm-link {text-decoration: underline;} - -.cm-s-default .cm-error {color: #f00;} -.cm-invalidchar {color: #f00;} - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} -.CodeMirror-activeline-background {background: #e8f2ff;} - -/* STOP */ - -/* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - -.CodeMirror { - line-height: 1; - position: relative; - overflow: hidden; - background: white; - color: black; -} - -.CodeMirror-scroll { - /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -.CodeMirror-sizer { - position: relative; - border-right: 30px solid transparent; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -/* The fake, visible scrollbars. Used to force redraw during scrolling - before actuall scrolling happens, thus preventing shaking and - flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - position: absolute; - z-index: 6; - display: none; -} -.CodeMirror-vscrollbar { - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; -} -.CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-y: hidden; - overflow-x: scroll; -} -.CodeMirror-scrollbar-filler { - right: 0; bottom: 0; -} -.CodeMirror-gutter-filler { - left: 0; bottom: 0; -} - -.CodeMirror-gutters { - position: absolute; left: 0; top: 0; - padding-bottom: 30px; - z-index: 3; -} -.CodeMirror-gutter { - white-space: normal; - height: 100%; - -moz-box-sizing: content-box; - box-sizing: content-box; - padding-bottom: 30px; - margin-bottom: -32px; - display: inline-block; - /* Hack to make IE7 behave */ - *zoom:1; - *display:inline; -} -.CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; -} - -.CodeMirror-lines { - cursor: text; -} -.CodeMirror pre { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; -} -.CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} - -.CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; -} - -.CodeMirror-linewidget { - position: relative; - z-index: 2; - overflow: auto; -} - -.CodeMirror-widget {} - -.CodeMirror-wrap .CodeMirror-scroll { - overflow-x: hidden; -} - -.CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; -} -.CodeMirror-measure pre { position: static; } - -.CodeMirror div.CodeMirror-cursor { - position: absolute; - visibility: hidden; - border-right: none; - width: 0; -} -.CodeMirror-focused div.CodeMirror-cursor { - visibility: visible; -} - -.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } - -.cm-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); -} - -/* IE7 hack to prevent it from returning funny offsetTops on the spans */ -.CodeMirror span { *vertical-align: text-bottom; } - -@media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursor { - visibility: hidden; - } -} diff --git a/addon-sdk/source/examples/actor-repl/data/index.html b/addon-sdk/source/examples/actor-repl/data/index.html deleted file mode 100644 index 250ece249..000000000 --- a/addon-sdk/source/examples/actor-repl/data/index.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - -
-

-            
-
-
- - -

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

Hello World

- diff --git a/addon-sdk/source/examples/reading-data/lib/main.js b/addon-sdk/source/examples/reading-data/lib/main.js deleted file mode 100644 index 468a497b1..000000000 --- a/addon-sdk/source/examples/reading-data/lib/main.js +++ /dev/null @@ -1,53 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var self = require("sdk/self"); -var { Panel } = require("sdk/panel"); -var { ToggleButton } = require("sdk/ui"); - -function replaceMom(html) { - return html.replace("World", "Mom"); -} -exports.replaceMom = replaceMom; - -exports.main = function(options, callbacks) { - console.log("My ID is " + self.id); - - // Load the sample HTML into a string. - var helloHTML = self.data.load("sample.html"); - - // Let's now modify it... - helloHTML = replaceMom(helloHTML); - - // ... and then create a panel that displays it. - var myPanel = Panel({ - contentURL: "data:text/html," + helloHTML, - onHide: handleHide - }); - - // Create a widget that displays the image. We'll attach the panel to it. - // When you click the widget, the panel will pop up. - var button = ToggleButton({ - id: "test-widget", - label: "Mom", - icon: './mom.png', - onChange: handleChange - }); - - // If you run cfx with --static-args='{"quitWhenDone":true}' this program - // will automatically quit Firefox when it's done. - if (options.staticArgs.quitWhenDone) - callbacks.quit(); -} - -function handleChange(state) { - if (state.checked) { - myPanel.show({ position: button }); - } -} - -function handleHide() { - button.state('window', { checked: false }); -} diff --git a/addon-sdk/source/examples/reading-data/package.json b/addon-sdk/source/examples/reading-data/package.json deleted file mode 100644 index 8bdce6423..000000000 --- a/addon-sdk/source/examples/reading-data/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "reading-data", - "description": "A demonstration of reading bundled data.", - "keywords": [], - "author": "Brian Warner", - "contributors": [], - "license": "MPL-2.0", - "id": "reading-data-example@jetpack.mozillalabs.com" -} diff --git a/addon-sdk/source/examples/reading-data/tests/test-main.js b/addon-sdk/source/examples/reading-data/tests/test-main.js deleted file mode 100644 index 4e85f49de..000000000 --- a/addon-sdk/source/examples/reading-data/tests/test-main.js +++ /dev/null @@ -1,25 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var m = require("main"); -var self = require("sdk/self"); - -exports.testReplace = function(test) { - var input = "Hello World"; - var output = m.replaceMom(input); - test.assertEqual(output, "Hello Mom"); - var callbacks = { quit: function() {} }; - - // Make sure it doesn't crash... - m.main({ staticArgs: {} }, callbacks); -}; - -exports.testID = function(test) { - // The ID is randomly generated during tests, so we cannot compare it against - // anything in particular. Just assert that it is not empty. - test.assert(self.id.length > 0); - test.assertEqual(self.data.url("sample.html"), - "resource://reading-data-example-at-jetpack-dot-mozillalabs-dot-com/reading-data/data/sample.html"); -}; diff --git a/addon-sdk/source/examples/theme/data/icon-16.png b/addon-sdk/source/examples/theme/data/icon-16.png deleted file mode 100644 index 72327f77b..000000000 Binary files a/addon-sdk/source/examples/theme/data/icon-16.png and /dev/null differ diff --git a/addon-sdk/source/examples/theme/data/index.html b/addon-sdk/source/examples/theme/data/index.html deleted file mode 100644 index 24ffc1a04..000000000 --- a/addon-sdk/source/examples/theme/data/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/addon-sdk/source/examples/theme/data/theme.css b/addon-sdk/source/examples/theme/data/theme.css deleted file mode 100644 index c837960d9..000000000 --- a/addon-sdk/source/examples/theme/data/theme.css +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#devtools-theme-box { - background-color: red !important; -} diff --git a/addon-sdk/source/examples/theme/lib/main.js b/addon-sdk/source/examples/theme/lib/main.js deleted file mode 100644 index 3b71376a5..000000000 --- a/addon-sdk/source/examples/theme/lib/main.js +++ /dev/null @@ -1,37 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Tool } = require("dev/toolbox"); -const { Class } = require("sdk/core/heritage"); -const { onEnable, onDisable } = require("dev/theme/hooks"); -const { Theme, LightTheme } = require("dev/theme"); - -/** - * This object represents a new theme registered within the Toolbox. - * You can activate it by clicking on "My Light Theme" theme option - * in the Options panel. - * Note that the new theme derives styles from built-in Light theme. - */ -const MyTheme = Theme({ - name: "mytheme", - label: "My Light Theme", - styles: [LightTheme, "./theme.css"], - - onEnable: function(window, oldTheme) { - console.log("myTheme.onEnable; method override " + - window.location.href); - }, - onDisable: function(window, newTheme) { - console.log("myTheme.onDisable; method override " + - window.location.href); - }, -}); - -// Registration - -const mytheme = new Tool({ - name: "My Tool", - themes: { mytheme: MyTheme } -}); diff --git a/addon-sdk/source/examples/theme/package.json b/addon-sdk/source/examples/theme/package.json deleted file mode 100644 index bb18a78ae..000000000 --- a/addon-sdk/source/examples/theme/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "theme", - "title": "theme", - "id": "theme@jetpack", - "description": "How to create new theme for devtools", - "author": "Jan Odvarko", - "license": "MPL-2.0", - "version": "0.1.0", - "main": "lib/main" -} diff --git a/addon-sdk/source/examples/theme/test/test-main.js b/addon-sdk/source/examples/theme/test/test-main.js deleted file mode 100644 index 8a5e101d3..000000000 --- a/addon-sdk/source/examples/theme/test/test-main.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - -exports.testMain = function(assert) { - assert.pass("TODO: Write some tests."); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/examples/toolbar-api/data/favicon.ico b/addon-sdk/source/examples/toolbar-api/data/favicon.ico deleted file mode 100644 index ae5084bc0..000000000 Binary files a/addon-sdk/source/examples/toolbar-api/data/favicon.ico and /dev/null differ diff --git a/addon-sdk/source/examples/toolbar-api/data/index.html b/addon-sdk/source/examples/toolbar-api/data/index.html deleted file mode 100644 index 4c91a63ae..000000000 --- a/addon-sdk/source/examples/toolbar-api/data/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - diff --git a/addon-sdk/source/examples/toolbar-api/lib/main.js b/addon-sdk/source/examples/toolbar-api/lib/main.js deleted file mode 100644 index a538c8cd6..000000000 --- a/addon-sdk/source/examples/toolbar-api/lib/main.js +++ /dev/null @@ -1,48 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Toolbar } = require("sdk/ui/toolbar"); -const { Frame } = require("sdk/ui/frame"); -const { ActionButton } = require("sdk/ui/button/action"); - -var button = new ActionButton({ - id: "button", - label: "send!", - icon: "./favicon.ico", - onClick: () => { - frame.postMessage({ - hello: "content" - }); - } -}); - -var frame = new Frame({ - url: "./index.html", - onAttach: () => { - console.log("frame was attached"); - }, - onReady: () => { - console.log("frame document was loaded"); - }, - onLoad: () => { - console.log("frame load complete"); - }, - onMessage: (event) => { - console.log("got message from frame content", event); - if (event.data === "ping!") - event.source.postMessage("pong!", event.source.origin); - } -}); -var toolbar = new Toolbar({ - items: [frame], - title: "Addon Demo", - hidden: false, - onShow: () => { - console.log("toolbar was shown"); - }, - onHide: () => { - console.log("toolbar was hidden"); - } -}); diff --git a/addon-sdk/source/examples/toolbar-api/package.json b/addon-sdk/source/examples/toolbar-api/package.json deleted file mode 100644 index 62af7f7ff..000000000 --- a/addon-sdk/source/examples/toolbar-api/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "toolbar-api", - "title": "Toolbar API", - "main": "./lib/main.js", - "description": "a toolbar api example", - "author": "", - "license": "MPL-2.0", - "version": "0.1.1", - "engines": { - "firefox": ">=27.0 <=30.0" - } -} diff --git a/addon-sdk/source/examples/toolbar-api/test/test-main.js b/addon-sdk/source/examples/toolbar-api/test/test-main.js deleted file mode 100644 index 9862fc20b..000000000 --- a/addon-sdk/source/examples/toolbar-api/test/test-main.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - -exports.testMain = function(assert) { - assert.pass("TODO: Write some tests."); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/examples/ui-button-apis/lib/main.js b/addon-sdk/source/examples/ui-button-apis/lib/main.js deleted file mode 100644 index f0ae3dd6c..000000000 --- a/addon-sdk/source/examples/ui-button-apis/lib/main.js +++ /dev/null @@ -1,39 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var data = require('sdk/self').data; -var tabs = require('sdk/tabs'); -var { notify } = require('sdk/notifications'); -var { ActionButton, ToggleButton } = require('sdk/ui'); - -var icon = 'chrome://mozapps/skin/extensions/extensionGeneric.svg'; -exports.icon = icon; - -// your basic action button -var action = ActionButton({ - id: 'test-action-button', - label: 'Action Button', - icon: icon, - onClick: function (state) { - notify({ - title: "Action!", - text: "This notification was triggered from an action button!", - }); - } -}); -exports.actionButton = action; - -var toggle = ToggleButton({ - id: 'test-toggle-button', - label: 'Toggle Button', - icon: icon, - onClick: function (state) { - notify({ - title: "Toggled!", - text: "The current state of the button is " + state.checked, - }); - } -}); -exports.toggleButton = toggle; diff --git a/addon-sdk/source/examples/ui-button-apis/package.json b/addon-sdk/source/examples/ui-button-apis/package.json deleted file mode 100644 index 83cf7f6ff..000000000 --- a/addon-sdk/source/examples/ui-button-apis/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "ui-button-apis", - "title": "Australis Button API Examples", - "id": "ui-button-apis@mozilla.org", - "description": "A Button API example", - "author": "jeff@canuckistani.ca (Jeff Griffiths | @canuckistani)", - "license": "MPL-2.0", - "version": "0.1.1", - "main": "./lib/main.js" -} diff --git a/addon-sdk/source/examples/ui-button-apis/tests/test-main.js b/addon-sdk/source/examples/ui-button-apis/tests/test-main.js deleted file mode 100644 index 49bdc863a..000000000 --- a/addon-sdk/source/examples/ui-button-apis/tests/test-main.js +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -try { - // CFX use case.. - var { actionButton, toggleButton, icon } = require("main"); -} -catch (e) { - // JPM use case.. - let mainURI = "../lib/main"; - var { actionButton, toggleButton, icon } = require(mainURI); -} -var self = require("sdk/self"); - -exports.testActionButton = function(assert) { - assert.equal(actionButton.id, "test-action-button", "action button id is correct"); - assert.equal(actionButton.label, "Action Button", "action button label is correct"); - assert.equal(actionButton.icon, icon, "action button icon is correct"); -} - -exports.testToggleButton = function(assert) { - assert.equal(toggleButton.id, "test-toggle-button", "toggle button id is correct"); - assert.equal(toggleButton.label, "Toggle Button", "toggle button label is correct"); - assert.equal(toggleButton.icon, icon, "toggle button icon is correct"); -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/gulpfile.js b/addon-sdk/source/gulpfile.js deleted file mode 100644 index 4020dd9d4..000000000 --- a/addon-sdk/source/gulpfile.js +++ /dev/null @@ -1,44 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var gulp = require('gulp'); -var patch = require("./bin/node-scripts/apply-patch"); -var ini = require("./bin/node-scripts/update-ini"); - -gulp.task('test', function(done) { - require("./bin/jpm-test").run().then(done); -}); - -gulp.task('test:addons', function(done) { - require("./bin/jpm-test").run("addons").catch(console.error).then(done); -}); - -gulp.task('test:docs', function(done) { - require("./bin/jpm-test").run("docs").catch(console.error).then(done); -}); - -gulp.task('test:examples', function(done) { - require("./bin/jpm-test").run("examples").catch(console.error).then(done); -}); - -gulp.task('test:modules', function(done) { - require("./bin/jpm-test").run("modules").catch(console.error).then(done); -}); - -gulp.task('test:ini', function(done) { - require("./bin/jpm-test").run("ini").catch(console.error).then(done); -}); - -gulp.task('test:firefox-bin', function(done) { - require("./bin/jpm-test").run("firefox-bin").catch(console.error).then(done); -}); - -gulp.task('patch:clean', function(done) { - patch.clean().catch(console.error).then(done); -}); - -gulp.task('patch:apply', function(done) { - patch.apply().catch(console.error).then(done); -}); diff --git a/addon-sdk/source/lib/dev/debuggee.js b/addon-sdk/source/lib/dev/debuggee.js deleted file mode 100644 index 0ca0bd37a..000000000 --- a/addon-sdk/source/lib/dev/debuggee.js +++ /dev/null @@ -1,95 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { require: devtoolsRequire } = Cu.import("resource://devtools/shared/Loader.jsm", {}); -const { DebuggerServer } = devtoolsRequire("devtools/server/main"); - -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. - if (!DebuggerServer.initialized) { - DebuggerServer.init(); - DebuggerServer.addBrowserActors(); - } - - transports.set(this, DebuggerServer.connectPipe()); - } - // 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; diff --git a/addon-sdk/source/lib/dev/frame-script.js b/addon-sdk/source/lib/dev/frame-script.js deleted file mode 100644 index 33a197419..000000000 --- a/addon-sdk/source/lib/dev/frame-script.js +++ /dev/null @@ -1,120 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; -(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", false); -observerService.addObserver(observer, "content-document-loaded", false); -observerService.addObserver(observer, "chrome-document-interactive", false); -observerService.addObserver(observer, "chrome-document-loaded", false); -addEventListener("unload", observer, false); - -})(this); diff --git a/addon-sdk/source/lib/dev/panel.js b/addon-sdk/source/lib/dev/panel.js deleted file mode 100644 index 1ef6a303a..000000000 --- a/addon-sdk/source/lib/dev/panel.js +++ /dev/null @@ -1,259 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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); diff --git a/addon-sdk/source/lib/dev/panel/view.js b/addon-sdk/source/lib/dev/panel/view.js deleted file mode 100644 index 41cf9c221..000000000 --- a/addon-sdk/source/lib/dev/panel/view.js +++ /dev/null @@ -1,14 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -const { method } = require("method/core"); - -const createView = method("dev/panel/view#createView"); -exports.createView = createView; diff --git a/addon-sdk/source/lib/dev/ports.js b/addon-sdk/source/lib/dev/ports.js deleted file mode 100644 index a41f59eb7..000000000 --- a/addon-sdk/source/lib/dev/ports.js +++ /dev/null @@ -1,64 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -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; diff --git a/addon-sdk/source/lib/dev/theme.js b/addon-sdk/source/lib/dev/theme.js deleted file mode 100644 index 05930a502..000000000 --- a/addon-sdk/source/lib/dev/theme.js +++ /dev/null @@ -1,135 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; diff --git a/addon-sdk/source/lib/dev/theme/hooks.js b/addon-sdk/source/lib/dev/theme/hooks.js deleted file mode 100644 index 9987f853b..000000000 --- a/addon-sdk/source/lib/dev/theme/hooks.js +++ /dev/null @@ -1,17 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; diff --git a/addon-sdk/source/lib/dev/toolbox.js b/addon-sdk/source/lib/dev/toolbox.js deleted file mode 100644 index 43f37759f..000000000 --- a/addon-sdk/source/lib/dev/toolbox.js +++ /dev/null @@ -1,107 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"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 { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.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, null, null)); -}; - -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; - - gDevTools.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); - - gDevTools.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 => gDevTools.unregisterTool(Panel.prototype.id), - values(this.panels)); - - each(Theme => gDevTools.unregisterTheme(Theme.prototype.id), - values(this.themes)); - } -}); - -validate.define(Tool, contract({ - panels: { - is: ["object", "undefined"] - }, - themes: { - is: ["object", "undefined"] - } -})); - -exports.Tool = Tool; diff --git a/addon-sdk/source/lib/dev/utils.js b/addon-sdk/source/lib/dev/utils.js deleted file mode 100644 index 48db39e04..000000000 --- a/addon-sdk/source/lib/dev/utils.js +++ /dev/null @@ -1,40 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {}); -const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {}); - -const { getActiveTab } = require("../sdk/tabs/utils"); -const { getMostRecentBrowserWindow } = require("../sdk/window/utils"); - -const targetFor = target => { - target = target || getActiveTab(getMostRecentBrowserWindow()); - return devtools.TargetFactory.forTab(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 gDevTools.showToolbox(targetFor(tab), id); -}; -exports.openToolbox = openToolbox; - -const closeToolbox = tab => gDevTools.closeToolbox(targetFor(tab)); -exports.closeToolbox = closeToolbox; - -const getToolbox = tab => gDevTools.getToolbox(targetFor(tab)); -exports.getToolbox = getToolbox; - -const openToolboxPanel = (id, tab) => { - id = getId(id); - return gDevTools.showToolbox(targetFor(tab), id).then(getCurrentPanel); -}; -exports.openToolboxPanel = openToolboxPanel; diff --git a/addon-sdk/source/lib/dev/volcan.js b/addon-sdk/source/lib/dev/volcan.js deleted file mode 100644 index 6a68ed12d..000000000 --- a/addon-sdk/source/lib/dev/volcan.js +++ /dev/null @@ -1,3848 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.volcan=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0 ? fields.constructor : - function() {}; - var ancestor = fields.extends || Object; - - var descriptor = names.reduce(function(descriptor, key) { - descriptor[key] = describe(fields, key); - return descriptor; - }, {}); - - var prototype = Object.create(ancestor.prototype, descriptor); - - constructor.prototype = prototype; - prototype.constructor = constructor; - - return constructor; -}; -exports.Class = Class; - -},{}],4:[function(_dereq_,module,exports){ -"use strict"; - -var Class = _dereq_("./class").Class; -var TypeSystem = _dereq_("./type-system").TypeSystem; -var values = _dereq_("./util").values; -var Promise = _dereq_("es6-promise").Promise; -var MessageEvent = _dereq_("./event").MessageEvent; - -var specification = _dereq_("./specification/core.json"); - -function recoverActorDescriptions(error) { - console.warn("Failed to fetch protocol specification (see reason below). " + - "Using a fallback protocal specification!", - error); - return _dereq_("./specification/protocol.json"); -} - -// Type to represent superviser actor relations to actors they supervise -// in terms of lifetime management. -var Supervisor = Class({ - constructor: function(id) { - this.id = id; - this.workers = []; - } -}); - -var Telemetry = Class({ - add: function(id, ms) { - console.log("telemetry::", id, ms) - } -}); - -// Consider making client a root actor. - -var Client = Class({ - constructor: function() { - this.root = null; - this.telemetry = new Telemetry(); - - this.setupConnection(); - this.setupLifeManagement(); - this.setupTypeSystem(); - }, - - setupConnection: function() { - this.requests = []; - }, - setupLifeManagement: function() { - this.cache = Object.create(null); - this.graph = Object.create(null); - this.get = this.get.bind(this); - this.release = this.release.bind(this); - }, - setupTypeSystem: function() { - this.typeSystem = new TypeSystem(this); - this.typeSystem.registerTypes(specification); - }, - - connect: function(port) { - var client = this; - return new Promise(function(resolve, reject) { - client.port = port; - port.onmessage = client.receive.bind(client); - client.onReady = resolve; - client.onFail = reject; - - port.start(); - }); - }, - send: function(packet) { - this.port.postMessage(packet); - }, - request: function(packet) { - var client = this; - return new Promise(function(resolve, reject) { - client.requests.push(packet.to, { resolve: resolve, reject: reject }); - client.send(packet); - }); - }, - - receive: function(event) { - var packet = event.data; - if (!this.root) { - if (packet.from !== "root") - throw Error("Initial packet must be from root"); - if (!("applicationType" in packet)) - throw Error("Initial packet must contain applicationType field"); - - this.root = this.typeSystem.read("root", null, "root"); - this.root - .protocolDescription() - .catch(recoverActorDescriptions) - .then(this.typeSystem.registerTypes.bind(this.typeSystem)) - .then(this.onReady.bind(this, this.root), this.onFail); - } else { - var actor = this.get(packet.from) || this.root; - var event = actor.events[packet.type]; - if (event) { - var message = new MessageEvent(packet.type, { - data: event.read(packet) - }); - actor.dispatchEvent(message); - } else { - var index = this.requests.indexOf(actor.id); - if (index >= 0) { - var request = this.requests.splice(index, 2).pop(); - if (packet.error) - request.reject(packet); - else - request.resolve(packet); - } else { - console.error(Error("Unexpected packet " + JSON.stringify(packet, 2, 2)), - packet, - this.requests.slice(0)); - } - } - } - }, - - get: function(id) { - return this.cache[id]; - }, - supervisorOf: function(actor) { - for (var id in this.graph) { - if (this.graph[id].indexOf(actor.id) >= 0) { - return id; - } - } - }, - workersOf: function(actor) { - return this.graph[actor.id]; - }, - supervise: function(actor, worker) { - var workers = this.workersOf(actor) - if (workers.indexOf(worker.id) < 0) { - workers.push(worker.id); - } - }, - unsupervise: function(actor, worker) { - var workers = this.workersOf(actor); - var index = workers.indexOf(worker.id) - if (index >= 0) { - workers.splice(index, 1) - } - }, - - register: function(actor) { - var registered = this.get(actor.id); - if (!registered) { - this.cache[actor.id] = actor; - this.graph[actor.id] = []; - } else if (registered !== actor) { - throw new Error("Different actor with same id is already registered"); - } - }, - unregister: function(actor) { - if (this.get(actor.id)) { - delete this.cache[actor.id]; - delete this.graph[actor.id]; - } - }, - - release: function(actor) { - var supervisor = this.supervisorOf(actor); - if (supervisor) - this.unsupervise(supervisor, actor); - - var workers = this.workersOf(actor) - - if (workers) { - workers.map(this.get).forEach(this.release) - } - this.unregister(actor); - } -}); -exports.Client = Client; - -},{"./class":3,"./event":5,"./specification/core.json":23,"./specification/protocol.json":24,"./type-system":25,"./util":26,"es6-promise":2}],5:[function(_dereq_,module,exports){ -"use strict"; - -var Symbol = _dereq_("es6-symbol") -var EventEmitter = _dereq_("events").EventEmitter; -var Class = _dereq_("./class").Class; - -var $bound = Symbol("EventTarget/handleEvent"); -var $emitter = Symbol("EventTarget/emitter"); - -function makeHandler(handler) { - return function(event) { - handler.handleEvent(event); - } -} - -var EventTarget = Class({ - constructor: function() { - Object.defineProperty(this, $emitter, { - enumerable: false, - configurable: true, - writable: true, - value: new EventEmitter() - }); - }, - addEventListener: function(type, handler) { - if (typeof(handler) === "function") { - this[$emitter].on(type, handler); - } - else if (handler && typeof(handler) === "object") { - if (!handler[$bound]) handler[$bound] = makeHandler(handler); - this[$emitter].on(type, handler[$bound]); - } - }, - removeEventListener: function(type, handler) { - if (typeof(handler) === "function") - this[$emitter].removeListener(type, handler); - else if (handler && handler[$bound]) - this[$emitter].removeListener(type, handler[$bound]); - }, - dispatchEvent: function(event) { - event.target = this; - this[$emitter].emit(event.type, event); - } -}); -exports.EventTarget = EventTarget; - -var MessageEvent = Class({ - constructor: function(type, options) { - options = options || {}; - this.type = type; - this.data = options.data === void(0) ? null : options.data; - - this.lastEventId = options.lastEventId || ""; - this.origin = options.origin || ""; - this.bubbles = options.bubbles || false; - this.cancelable = options.cancelable || false; - }, - source: null, - ports: null, - preventDefault: function() { - }, - stopPropagation: function() { - }, - stopImmediatePropagation: function() { - } -}); -exports.MessageEvent = MessageEvent; - -},{"./class":3,"es6-symbol":7,"events":6}],6:[function(_dereq_,module,exports){ -// 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. - -function EventEmitter() { - this._events = this._events || {}; - this._maxListeners = this._maxListeners || undefined; -} -module.exports = EventEmitter; - -// Backwards-compat with node 0.10.x -EventEmitter.EventEmitter = EventEmitter; - -EventEmitter.prototype._events = undefined; -EventEmitter.prototype._maxListeners = undefined; - -// By default EventEmitters will print a warning if more than 10 listeners are -// added to it. This is a useful default which helps finding memory leaks. -EventEmitter.defaultMaxListeners = 10; - -// Obviously not all Emitters should be limited to 10. This function allows -// that to be increased. Set to zero for unlimited. -EventEmitter.prototype.setMaxListeners = function(n) { - if (!isNumber(n) || n < 0 || isNaN(n)) - throw TypeError('n must be a positive number'); - this._maxListeners = n; - return this; -}; - -EventEmitter.prototype.emit = function(type) { - var er, handler, len, args, i, listeners; - - if (!this._events) - this._events = {}; - - // If there is no 'error' event listener then throw. - if (type === 'error') { - if (!this._events.error || - (isObject(this._events.error) && !this._events.error.length)) { - er = arguments[1]; - if (er instanceof Error) { - throw er; // Unhandled 'error' event - } else { - throw TypeError('Uncaught, unspecified "error" event.'); - } - return false; - } - } - - handler = this._events[type]; - - if (isUndefined(handler)) - return false; - - if (isFunction(handler)) { - switch (arguments.length) { - // fast cases - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - // slower - default: - len = arguments.length; - args = new Array(len - 1); - for (i = 1; i < len; i++) - args[i - 1] = arguments[i]; - handler.apply(this, args); - } - } else if (isObject(handler)) { - len = arguments.length; - args = new Array(len - 1); - for (i = 1; i < len; i++) - args[i - 1] = arguments[i]; - - listeners = handler.slice(); - len = listeners.length; - for (i = 0; i < len; i++) - listeners[i].apply(this, args); - } - - return true; -}; - -EventEmitter.prototype.addListener = function(type, listener) { - var m; - - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - if (!this._events) - this._events = {}; - - // To avoid recursion in the case that type === "newListener"! Before - // adding it to the listeners, first emit "newListener". - if (this._events.newListener) - this.emit('newListener', type, - isFunction(listener.listener) ? - listener.listener : listener); - - if (!this._events[type]) - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener; - else if (isObject(this._events[type])) - // If we've already got an array, just append. - this._events[type].push(listener); - else - // Adding the second element, need to change to array. - this._events[type] = [this._events[type], listener]; - - // Check for listener leak - if (isObject(this._events[type]) && !this._events[type].warned) { - var m; - if (!isUndefined(this._maxListeners)) { - m = this._maxListeners; - } else { - m = EventEmitter.defaultMaxListeners; - } - - if (m && m > 0 && this._events[type].length > m) { - this._events[type].warned = true; - console.error('(node) warning: possible EventEmitter memory ' + - 'leak detected. %d listeners added. ' + - 'Use emitter.setMaxListeners() to increase limit.', - this._events[type].length); - if (typeof console.trace === 'function') { - // not supported in IE 10 - console.trace(); - } - } - } - - return this; -}; - -EventEmitter.prototype.on = EventEmitter.prototype.addListener; - -EventEmitter.prototype.once = function(type, listener) { - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - var fired = false; - - function g() { - this.removeListener(type, g); - - if (!fired) { - fired = true; - listener.apply(this, arguments); - } - } - - g.listener = listener; - this.on(type, g); - - return this; -}; - -// emits a 'removeListener' event iff the listener was removed -EventEmitter.prototype.removeListener = function(type, listener) { - var list, position, length, i; - - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - if (!this._events || !this._events[type]) - return this; - - list = this._events[type]; - length = list.length; - position = -1; - - if (list === listener || - (isFunction(list.listener) && list.listener === listener)) { - delete this._events[type]; - if (this._events.removeListener) - this.emit('removeListener', type, listener); - - } else if (isObject(list)) { - for (i = length; i-- > 0;) { - if (list[i] === listener || - (list[i].listener && list[i].listener === listener)) { - position = i; - break; - } - } - - if (position < 0) - return this; - - if (list.length === 1) { - list.length = 0; - delete this._events[type]; - } else { - list.splice(position, 1); - } - - if (this._events.removeListener) - this.emit('removeListener', type, listener); - } - - return this; -}; - -EventEmitter.prototype.removeAllListeners = function(type) { - var key, listeners; - - if (!this._events) - return this; - - // not listening for removeListener, no need to emit - if (!this._events.removeListener) { - if (arguments.length === 0) - this._events = {}; - else if (this._events[type]) - delete this._events[type]; - return this; - } - - // emit removeListener for all listeners on all events - if (arguments.length === 0) { - for (key in this._events) { - if (key === 'removeListener') continue; - this.removeAllListeners(key); - } - this.removeAllListeners('removeListener'); - this._events = {}; - return this; - } - - listeners = this._events[type]; - - if (isFunction(listeners)) { - this.removeListener(type, listeners); - } else { - // LIFO order - while (listeners.length) - this.removeListener(type, listeners[listeners.length - 1]); - } - delete this._events[type]; - - return this; -}; - -EventEmitter.prototype.listeners = function(type) { - var ret; - if (!this._events || !this._events[type]) - ret = []; - else if (isFunction(this._events[type])) - ret = [this._events[type]]; - else - ret = this._events[type].slice(); - return ret; -}; - -EventEmitter.listenerCount = function(emitter, type) { - var ret; - if (!emitter._events || !emitter._events[type]) - ret = 0; - else if (isFunction(emitter._events[type])) - ret = 1; - else - ret = emitter._events[type].length; - return ret; -}; - -function isFunction(arg) { - return typeof arg === 'function'; -} - -function isNumber(arg) { - return typeof arg === 'number'; -} - -function isObject(arg) { - return typeof arg === 'object' && arg !== null; -} - -function isUndefined(arg) { - return arg === void 0; -} - -},{}],7:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = _dereq_('./is-implemented')() ? Symbol : _dereq_('./polyfill'); - -},{"./is-implemented":8,"./polyfill":22}],8:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = function () { - var symbol; - if (typeof Symbol !== 'function') return false; - symbol = Symbol('test symbol'); - try { - if (String(symbol) !== 'Symbol (test symbol)') return false; - } catch (e) { return false; } - if (typeof Symbol.iterator === 'symbol') return true; - - // Return 'true' for polyfills - if (typeof Symbol.isConcatSpreadable !== 'object') return false; - if (typeof Symbol.isRegExp !== 'object') return false; - if (typeof Symbol.iterator !== 'object') return false; - if (typeof Symbol.toPrimitive !== 'object') return false; - if (typeof Symbol.toStringTag !== 'object') return false; - if (typeof Symbol.unscopables !== 'object') return false; - - return true; -}; - -},{}],9:[function(_dereq_,module,exports){ -'use strict'; - -var assign = _dereq_('es5-ext/object/assign') - , normalizeOpts = _dereq_('es5-ext/object/normalize-options') - , isCallable = _dereq_('es5-ext/object/is-callable') - , contains = _dereq_('es5-ext/string/#/contains') - - , d; - -d = module.exports = function (dscr, value/*, options*/) { - var c, e, w, options, desc; - if ((arguments.length < 2) || (typeof dscr !== 'string')) { - options = value; - value = dscr; - dscr = null; - } else { - options = arguments[2]; - } - if (dscr == null) { - c = w = true; - e = false; - } else { - c = contains.call(dscr, 'c'); - e = contains.call(dscr, 'e'); - w = contains.call(dscr, 'w'); - } - - desc = { value: value, configurable: c, enumerable: e, writable: w }; - return !options ? desc : assign(normalizeOpts(options), desc); -}; - -d.gs = function (dscr, get, set/*, options*/) { - var c, e, options, desc; - if (typeof dscr !== 'string') { - options = set; - set = get; - get = dscr; - dscr = null; - } else { - options = arguments[3]; - } - if (get == null) { - get = undefined; - } else if (!isCallable(get)) { - options = get; - get = set = undefined; - } else if (set == null) { - set = undefined; - } else if (!isCallable(set)) { - options = set; - set = undefined; - } - if (dscr == null) { - c = true; - e = false; - } else { - c = contains.call(dscr, 'c'); - e = contains.call(dscr, 'e'); - } - - desc = { get: get, set: set, configurable: c, enumerable: e }; - return !options ? desc : assign(normalizeOpts(options), desc); -}; - -},{"es5-ext/object/assign":10,"es5-ext/object/is-callable":13,"es5-ext/object/normalize-options":17,"es5-ext/string/#/contains":19}],10:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = _dereq_('./is-implemented')() - ? Object.assign - : _dereq_('./shim'); - -},{"./is-implemented":11,"./shim":12}],11:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = function () { - var assign = Object.assign, obj; - if (typeof assign !== 'function') return false; - obj = { foo: 'raz' }; - assign(obj, { bar: 'dwa' }, { trzy: 'trzy' }); - return (obj.foo + obj.bar + obj.trzy) === 'razdwatrzy'; -}; - -},{}],12:[function(_dereq_,module,exports){ -'use strict'; - -var keys = _dereq_('../keys') - , value = _dereq_('../valid-value') - - , max = Math.max; - -module.exports = function (dest, src/*, …srcn*/) { - var error, i, l = max(arguments.length, 2), assign; - dest = Object(value(dest)); - assign = function (key) { - try { dest[key] = src[key]; } catch (e) { - if (!error) error = e; - } - }; - for (i = 1; i < l; ++i) { - src = arguments[i]; - keys(src).forEach(assign); - } - if (error !== undefined) throw error; - return dest; -}; - -},{"../keys":14,"../valid-value":18}],13:[function(_dereq_,module,exports){ -// Deprecated - -'use strict'; - -module.exports = function (obj) { return typeof obj === 'function'; }; - -},{}],14:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = _dereq_('./is-implemented')() - ? Object.keys - : _dereq_('./shim'); - -},{"./is-implemented":15,"./shim":16}],15:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = function () { - try { - Object.keys('primitive'); - return true; - } catch (e) { return false; } -}; - -},{}],16:[function(_dereq_,module,exports){ -'use strict'; - -var keys = Object.keys; - -module.exports = function (object) { - return keys(object == null ? object : Object(object)); -}; - -},{}],17:[function(_dereq_,module,exports){ -'use strict'; - -var assign = _dereq_('./assign') - - , forEach = Array.prototype.forEach - , create = Object.create, getPrototypeOf = Object.getPrototypeOf - - , process; - -process = function (src, obj) { - var proto = getPrototypeOf(src); - return assign(proto ? process(proto, obj) : obj, src); -}; - -module.exports = function (options/*, …options*/) { - var result = create(null); - forEach.call(arguments, function (options) { - if (options == null) return; - process(Object(options), result); - }); - return result; -}; - -},{"./assign":10}],18:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = function (value) { - if (value == null) throw new TypeError("Cannot use null or undefined"); - return value; -}; - -},{}],19:[function(_dereq_,module,exports){ -'use strict'; - -module.exports = _dereq_('./is-implemented')() - ? String.prototype.contains - : _dereq_('./shim'); - -},{"./is-implemented":20,"./shim":21}],20:[function(_dereq_,module,exports){ -'use strict'; - -var str = 'razdwatrzy'; - -module.exports = function () { - if (typeof str.contains !== 'function') return false; - return ((str.contains('dwa') === true) && (str.contains('foo') === false)); -}; - -},{}],21:[function(_dereq_,module,exports){ -'use strict'; - -var indexOf = String.prototype.indexOf; - -module.exports = function (searchString/*, position*/) { - return indexOf.call(this, searchString, arguments[1]) > -1; -}; - -},{}],22:[function(_dereq_,module,exports){ -'use strict'; - -var d = _dereq_('d') - - , create = Object.create, defineProperties = Object.defineProperties - , generateName, Symbol; - -generateName = (function () { - var created = create(null); - return function (desc) { - var postfix = 0; - while (created[desc + (postfix || '')]) ++postfix; - desc += (postfix || ''); - created[desc] = true; - return '@@' + desc; - }; -}()); - -module.exports = Symbol = function (description) { - var symbol; - if (this instanceof Symbol) { - throw new TypeError('TypeError: Symbol is not a constructor'); - } - symbol = create(Symbol.prototype); - description = (description === undefined ? '' : String(description)); - return defineProperties(symbol, { - __description__: d('', description), - __name__: d('', generateName(description)) - }); -}; - -Object.defineProperties(Symbol, { - create: d('', Symbol('create')), - hasInstance: d('', Symbol('hasInstance')), - isConcatSpreadable: d('', Symbol('isConcatSpreadable')), - isRegExp: d('', Symbol('isRegExp')), - iterator: d('', Symbol('iterator')), - toPrimitive: d('', Symbol('toPrimitive')), - toStringTag: d('', Symbol('toStringTag')), - unscopables: d('', Symbol('unscopables')) -}); - -defineProperties(Symbol.prototype, { - properToString: d(function () { - return 'Symbol (' + this.__description__ + ')'; - }), - toString: d('', function () { return this.__name__; }) -}); -Object.defineProperty(Symbol.prototype, Symbol.toPrimitive, d('', - function (hint) { - throw new TypeError("Conversion of symbol objects is not allowed"); - })); -Object.defineProperty(Symbol.prototype, Symbol.toStringTag, d('c', 'Symbol')); - -},{"d":9}],23:[function(_dereq_,module,exports){ -module.exports={ - "types": { - "root": { - "category": "actor", - "typeName": "root", - "methods": [ - { - "name": "echo", - "request": { - "string": { "_arg": 0, "type": "string" } - }, - "response": { - "string": { "_retval": "string" } - } - }, - { - "name": "listTabs", - "request": {}, - "response": { "_retval": "tablist" } - }, - { - "name": "protocolDescription", - "request": {}, - "response": { "_retval": "json" } - } - ], - "events": { - "tabListChanged": {} - } - }, - "tablist": { - "category": "dict", - "typeName": "tablist", - "specializations": { - "selected": "number", - "tabs": "array:tab", - "url": "string", - "consoleActor": "console", - "inspectorActor": "inspector", - "styleSheetsActor": "stylesheets", - "styleEditorActor": "styleeditor", - "memoryActor": "memory", - "eventLoopLagActor": "eventLoopLag", - "preferenceActor": "preference", - "deviceActor": "device", - - "profilerActor": "profiler", - "chromeDebugger": "chromeDebugger", - "webappsActor": "webapps" - } - }, - "tab": { - "category": "actor", - "typeName": "tab", - "fields": { - "title": "string", - "url": "string", - "outerWindowID": "number", - "inspectorActor": "inspector", - "callWatcherActor": "call-watcher", - "canvasActor": "canvas", - "webglActor": "webgl", - "webaudioActor": "webaudio", - "storageActor": "storage", - "gcliActor": "gcli", - "memoryActor": "memory", - "eventLoopLag": "eventLoopLag", - "styleSheetsActor": "stylesheets", - "styleEditorActor": "styleeditor", - - "consoleActor": "console", - "traceActor": "trace" - }, - "methods": [ - { - "name": "attach", - "request": {}, - "response": { "_retval": "json" } - } - ], - "events": { - "tabNavigated": { - "typeName": "tabNavigated" - } - } - }, - "console": { - "category": "actor", - "typeName": "console", - "methods": [ - { - "name": "evaluateJS", - "request": { - "text": { - "_option": 0, - "type": "string" - }, - "url": { - "_option": 1, - "type": "string" - }, - "bindObjectActor": { - "_option": 2, - "type": "nullable:string" - }, - "frameActor": { - "_option": 2, - "type": "nullable:string" - }, - "selectedNodeActor": { - "_option": 2, - "type": "nullable:string" - } - }, - "response": { - "_retval": "evaluatejsresponse" - } - } - ], - "events": {} - }, - "evaluatejsresponse": { - "category": "dict", - "typeName": "evaluatejsresponse", - "specializations": { - "result": "object", - "exception": "object", - "exceptionMessage": "string", - "input": "string" - } - }, - "object": { - "category": "actor", - "typeName": "object", - "methods": [ - { - "name": "property", - "request": { - "name": { - "_arg": 0, - "type": "string" - } - }, - "response": { - "descriptor": { - "_retval": "json" - } - } - } - ] - } - } -} - -},{}],24:[function(_dereq_,module,exports){ -module.exports={ - "types": { - "longstractor": { - "category": "actor", - "typeName": "longstractor", - "methods": [ - { - "name": "substring", - "request": { - "type": "substring", - "start": { - "_arg": 0, - "type": "primitive" - }, - "end": { - "_arg": 1, - "type": "primitive" - } - }, - "response": { - "substring": { - "_retval": "primitive" - } - } - }, - { - "name": "release", - "release": true, - "request": { - "type": "release" - }, - "response": {} - } - ], - "events": {} - }, - "stylesheet": { - "category": "actor", - "typeName": "stylesheet", - "methods": [ - { - "name": "toggleDisabled", - "request": { - "type": "toggleDisabled" - }, - "response": { - "disabled": { - "_retval": "boolean" - } - } - }, - { - "name": "getText", - "request": { - "type": "getText" - }, - "response": { - "text": { - "_retval": "longstring" - } - } - }, - { - "name": "getOriginalSources", - "request": { - "type": "getOriginalSources" - }, - "response": { - "originalSources": { - "_retval": "nullable:array:originalsource" - } - } - }, - { - "name": "getOriginalLocation", - "request": { - "type": "getOriginalLocation", - "line": { - "_arg": 0, - "type": "number" - }, - "column": { - "_arg": 1, - "type": "number" - } - }, - "response": { - "_retval": "originallocationresponse" - } - }, - { - "name": "update", - "request": { - "type": "update", - "text": { - "_arg": 0, - "type": "string" - }, - "transition": { - "_arg": 1, - "type": "boolean" - } - }, - "response": {} - } - ], - "events": { - "property-change": { - "type": "propertyChange", - "property": { - "_arg": 0, - "type": "string" - }, - "value": { - "_arg": 1, - "type": "json" - } - }, - "style-applied": { - "type": "styleApplied" - } - } - }, - "originalsource": { - "category": "actor", - "typeName": "originalsource", - "methods": [ - { - "name": "getText", - "request": { - "type": "getText" - }, - "response": { - "text": { - "_retval": "longstring" - } - } - } - ], - "events": {} - }, - "stylesheets": { - "category": "actor", - "typeName": "stylesheets", - "methods": [ - { - "name": "getStyleSheets", - "request": { - "type": "getStyleSheets" - }, - "response": { - "styleSheets": { - "_retval": "array:stylesheet" - } - } - }, - { - "name": "addStyleSheet", - "request": { - "type": "addStyleSheet", - "text": { - "_arg": 0, - "type": "string" - } - }, - "response": { - "styleSheet": { - "_retval": "stylesheet" - } - } - } - ], - "events": {} - }, - "originallocationresponse": { - "category": "dict", - "typeName": "originallocationresponse", - "specializations": { - "source": "string", - "line": "number", - "column": "number" - } - }, - "domnode": { - "category": "actor", - "typeName": "domnode", - "methods": [ - { - "name": "getNodeValue", - "request": { - "type": "getNodeValue" - }, - "response": { - "value": { - "_retval": "longstring" - } - } - }, - { - "name": "setNodeValue", - "request": { - "type": "setNodeValue", - "value": { - "_arg": 0, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "getImageData", - "request": { - "type": "getImageData", - "maxDim": { - "_arg": 0, - "type": "nullable:number" - } - }, - "response": { - "_retval": "imageData" - } - }, - { - "name": "modifyAttributes", - "request": { - "type": "modifyAttributes", - "modifications": { - "_arg": 0, - "type": "array:json" - } - }, - "response": {} - } - ], - "events": {} - }, - "appliedstyle": { - "category": "dict", - "typeName": "appliedstyle", - "specializations": { - "rule": "domstylerule#actorid", - "inherited": "nullable:domnode#actorid" - } - }, - "matchedselector": { - "category": "dict", - "typeName": "matchedselector", - "specializations": { - "rule": "domstylerule#actorid", - "selector": "string", - "value": "string", - "status": "number" - } - }, - "matchedselectorresponse": { - "category": "dict", - "typeName": "matchedselectorresponse", - "specializations": { - "rules": "array:domstylerule", - "sheets": "array:stylesheet", - "matched": "array:matchedselector" - } - }, - "appliedStylesReturn": { - "category": "dict", - "typeName": "appliedStylesReturn", - "specializations": { - "entries": "array:appliedstyle", - "rules": "array:domstylerule", - "sheets": "array:stylesheet" - } - }, - "pagestyle": { - "category": "actor", - "typeName": "pagestyle", - "methods": [ - { - "name": "getComputed", - "request": { - "type": "getComputed", - "node": { - "_arg": 0, - "type": "domnode" - }, - "markMatched": { - "_option": 1, - "type": "boolean" - }, - "onlyMatched": { - "_option": 1, - "type": "boolean" - }, - "filter": { - "_option": 1, - "type": "string" - } - }, - "response": { - "computed": { - "_retval": "json" - } - } - }, - { - "name": "getMatchedSelectors", - "request": { - "type": "getMatchedSelectors", - "node": { - "_arg": 0, - "type": "domnode" - }, - "property": { - "_arg": 1, - "type": "string" - }, - "filter": { - "_option": 2, - "type": "string" - } - }, - "response": { - "_retval": "matchedselectorresponse" - } - }, - { - "name": "getApplied", - "request": { - "type": "getApplied", - "node": { - "_arg": 0, - "type": "domnode" - }, - "inherited": { - "_option": 1, - "type": "boolean" - }, - "matchedSelectors": { - "_option": 1, - "type": "boolean" - }, - "filter": { - "_option": 1, - "type": "string" - } - }, - "response": { - "_retval": "appliedStylesReturn" - } - }, - { - "name": "getLayout", - "request": { - "type": "getLayout", - "node": { - "_arg": 0, - "type": "domnode" - }, - "autoMargins": { - "_option": 1, - "type": "boolean" - } - }, - "response": { - "_retval": "json" - } - } - ], - "events": {} - }, - "domstylerule": { - "category": "actor", - "typeName": "domstylerule", - "methods": [ - { - "name": "modifyProperties", - "request": { - "type": "modifyProperties", - "modifications": { - "_arg": 0, - "type": "array:json" - } - }, - "response": { - "rule": { - "_retval": "domstylerule" - } - } - } - ], - "events": {} - }, - "highlighter": { - "category": "actor", - "typeName": "highlighter", - "methods": [ - { - "name": "showBoxModel", - "request": { - "type": "showBoxModel", - "node": { - "_arg": 0, - "type": "domnode" - }, - "region": { - "_option": 1, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "hideBoxModel", - "request": { - "type": "hideBoxModel" - }, - "response": {} - }, - { - "name": "pick", - "request": { - "type": "pick" - }, - "response": {} - }, - { - "name": "cancelPick", - "request": { - "type": "cancelPick" - }, - "response": {} - } - ], - "events": {} - }, - "imageData": { - "category": "dict", - "typeName": "imageData", - "specializations": { - "data": "nullable:longstring", - "size": "json" - } - }, - "disconnectedNode": { - "category": "dict", - "typeName": "disconnectedNode", - "specializations": { - "node": "domnode", - "newParents": "array:domnode" - } - }, - "disconnectedNodeArray": { - "category": "dict", - "typeName": "disconnectedNodeArray", - "specializations": { - "nodes": "array:domnode", - "newParents": "array:domnode" - } - }, - "dommutation": { - "category": "dict", - "typeName": "dommutation", - "specializations": {} - }, - "domnodelist": { - "category": "actor", - "typeName": "domnodelist", - "methods": [ - { - "name": "item", - "request": { - "type": "item", - "item": { - "_arg": 0, - "type": "primitive" - } - }, - "response": { - "_retval": "disconnectedNode" - } - }, - { - "name": "items", - "request": { - "type": "items", - "start": { - "_arg": 0, - "type": "nullable:number" - }, - "end": { - "_arg": 1, - "type": "nullable:number" - } - }, - "response": { - "_retval": "disconnectedNodeArray" - } - }, - { - "name": "release", - "release": true, - "request": { - "type": "release" - }, - "response": {} - } - ], - "events": {} - }, - "domtraversalarray": { - "category": "dict", - "typeName": "domtraversalarray", - "specializations": { - "nodes": "array:domnode" - } - }, - "domwalker": { - "category": "actor", - "typeName": "domwalker", - "methods": [ - { - "name": "release", - "release": true, - "request": { - "type": "release" - }, - "response": {} - }, - { - "name": "pick", - "request": { - "type": "pick" - }, - "response": { - "_retval": "disconnectedNode" - } - }, - { - "name": "cancelPick", - "request": { - "type": "cancelPick" - }, - "response": {} - }, - { - "name": "highlight", - "request": { - "type": "highlight", - "node": { - "_arg": 0, - "type": "nullable:domnode" - } - }, - "response": {} - }, - { - "name": "document", - "request": { - "type": "document", - "node": { - "_arg": 0, - "type": "nullable:domnode" - } - }, - "response": { - "node": { - "_retval": "domnode" - } - } - }, - { - "name": "documentElement", - "request": { - "type": "documentElement", - "node": { - "_arg": 0, - "type": "nullable:domnode" - } - }, - "response": { - "node": { - "_retval": "domnode" - } - } - }, - { - "name": "parents", - "request": { - "type": "parents", - "node": { - "_arg": 0, - "type": "domnode" - }, - "sameDocument": { - "_option": 1, - "type": "primitive" - } - }, - "response": { - "nodes": { - "_retval": "array:domnode" - } - } - }, - { - "name": "retainNode", - "request": { - "type": "retainNode", - "node": { - "_arg": 0, - "type": "domnode" - } - }, - "response": {} - }, - { - "name": "unretainNode", - "request": { - "type": "unretainNode", - "node": { - "_arg": 0, - "type": "domnode" - } - }, - "response": {} - }, - { - "name": "releaseNode", - "request": { - "type": "releaseNode", - "node": { - "_arg": 0, - "type": "domnode" - }, - "force": { - "_option": 1, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "children", - "request": { - "type": "children", - "node": { - "_arg": 0, - "type": "domnode" - }, - "maxNodes": { - "_option": 1, - "type": "primitive" - }, - "center": { - "_option": 1, - "type": "domnode" - }, - "start": { - "_option": 1, - "type": "domnode" - }, - "whatToShow": { - "_option": 1, - "type": "primitive" - } - }, - "response": { - "_retval": "domtraversalarray" - } - }, - { - "name": "siblings", - "request": { - "type": "siblings", - "node": { - "_arg": 0, - "type": "domnode" - }, - "maxNodes": { - "_option": 1, - "type": "primitive" - }, - "center": { - "_option": 1, - "type": "domnode" - }, - "start": { - "_option": 1, - "type": "domnode" - }, - "whatToShow": { - "_option": 1, - "type": "primitive" - } - }, - "response": { - "_retval": "domtraversalarray" - } - }, - { - "name": "nextSibling", - "request": { - "type": "nextSibling", - "node": { - "_arg": 0, - "type": "domnode" - }, - "whatToShow": { - "_option": 1, - "type": "primitive" - } - }, - "response": { - "node": { - "_retval": "nullable:domnode" - } - } - }, - { - "name": "previousSibling", - "request": { - "type": "previousSibling", - "node": { - "_arg": 0, - "type": "domnode" - }, - "whatToShow": { - "_option": 1, - "type": "primitive" - } - }, - "response": { - "node": { - "_retval": "nullable:domnode" - } - } - }, - { - "name": "querySelector", - "request": { - "type": "querySelector", - "node": { - "_arg": 0, - "type": "domnode" - }, - "selector": { - "_arg": 1, - "type": "primitive" - } - }, - "response": { - "_retval": "disconnectedNode" - } - }, - { - "name": "querySelectorAll", - "request": { - "type": "querySelectorAll", - "node": { - "_arg": 0, - "type": "domnode" - }, - "selector": { - "_arg": 1, - "type": "primitive" - } - }, - "response": { - "list": { - "_retval": "domnodelist" - } - } - }, - { - "name": "getSuggestionsForQuery", - "request": { - "type": "getSuggestionsForQuery", - "query": { - "_arg": 0, - "type": "primitive" - }, - "completing": { - "_arg": 1, - "type": "primitive" - }, - "selectorState": { - "_arg": 2, - "type": "primitive" - } - }, - "response": { - "list": { - "_retval": "array:array:string" - } - } - }, - { - "name": "addPseudoClassLock", - "request": { - "type": "addPseudoClassLock", - "node": { - "_arg": 0, - "type": "domnode" - }, - "pseudoClass": { - "_arg": 1, - "type": "primitive" - }, - "parents": { - "_option": 2, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "hideNode", - "request": { - "type": "hideNode", - "node": { - "_arg": 0, - "type": "domnode" - } - }, - "response": {} - }, - { - "name": "unhideNode", - "request": { - "type": "unhideNode", - "node": { - "_arg": 0, - "type": "domnode" - } - }, - "response": {} - }, - { - "name": "removePseudoClassLock", - "request": { - "type": "removePseudoClassLock", - "node": { - "_arg": 0, - "type": "domnode" - }, - "pseudoClass": { - "_arg": 1, - "type": "primitive" - }, - "parents": { - "_option": 2, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "clearPseudoClassLocks", - "request": { - "type": "clearPseudoClassLocks", - "node": { - "_arg": 0, - "type": "nullable:domnode" - } - }, - "response": {} - }, - { - "name": "innerHTML", - "request": { - "type": "innerHTML", - "node": { - "_arg": 0, - "type": "domnode" - } - }, - "response": { - "value": { - "_retval": "longstring" - } - } - }, - { - "name": "outerHTML", - "request": { - "type": "outerHTML", - "node": { - "_arg": 0, - "type": "domnode" - } - }, - "response": { - "value": { - "_retval": "longstring" - } - } - }, - { - "name": "setOuterHTML", - "request": { - "type": "setOuterHTML", - "node": { - "_arg": 0, - "type": "domnode" - }, - "value": { - "_arg": 1, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "removeNode", - "request": { - "type": "removeNode", - "node": { - "_arg": 0, - "type": "domnode" - } - }, - "response": { - "nextSibling": { - "_retval": "nullable:domnode" - } - } - }, - { - "name": "insertBefore", - "request": { - "type": "insertBefore", - "node": { - "_arg": 0, - "type": "domnode" - }, - "parent": { - "_arg": 1, - "type": "domnode" - }, - "sibling": { - "_arg": 2, - "type": "nullable:domnode" - } - }, - "response": {} - }, - { - "name": "getMutations", - "request": { - "type": "getMutations", - "cleanup": { - "_option": 0, - "type": "primitive" - } - }, - "response": { - "mutations": { - "_retval": "array:dommutation" - } - } - }, - { - "name": "isInDOMTree", - "request": { - "type": "isInDOMTree", - "node": { - "_arg": 0, - "type": "domnode" - } - }, - "response": { - "attached": { - "_retval": "boolean" - } - } - }, - { - "name": "getNodeActorFromObjectActor", - "request": { - "type": "getNodeActorFromObjectActor", - "objectActorID": { - "_arg": 0, - "type": "string" - } - }, - "response": { - "nodeFront": { - "_retval": "nullable:disconnectedNode" - } - } - } - ], - "events": { - "new-mutations": { - "type": "newMutations" - }, - "picker-node-picked": { - "type": "pickerNodePicked", - "node": { - "_arg": 0, - "type": "disconnectedNode" - } - }, - "picker-node-hovered": { - "type": "pickerNodeHovered", - "node": { - "_arg": 0, - "type": "disconnectedNode" - } - }, - "highlighter-ready": { - "type": "highlighter-ready" - }, - "highlighter-hide": { - "type": "highlighter-hide" - } - } - }, - "inspector": { - "category": "actor", - "typeName": "inspector", - "methods": [ - { - "name": "getWalker", - "request": { - "type": "getWalker" - }, - "response": { - "walker": { - "_retval": "domwalker" - } - } - }, - { - "name": "getPageStyle", - "request": { - "type": "getPageStyle" - }, - "response": { - "pageStyle": { - "_retval": "pagestyle" - } - } - }, - { - "name": "getHighlighter", - "request": { - "type": "getHighlighter", - "autohide": { - "_arg": 0, - "type": "boolean" - } - }, - "response": { - "highligter": { - "_retval": "highlighter" - } - } - }, - { - "name": "getImageDataFromURL", - "request": { - "type": "getImageDataFromURL", - "url": { - "_arg": 0, - "type": "primitive" - }, - "maxDim": { - "_arg": 1, - "type": "nullable:number" - } - }, - "response": { - "_retval": "imageData" - } - } - ], - "events": {} - }, - "call-stack-item": { - "category": "dict", - "typeName": "call-stack-item", - "specializations": { - "name": "string", - "file": "string", - "line": "number" - } - }, - "call-details": { - "category": "dict", - "typeName": "call-details", - "specializations": { - "type": "number", - "name": "string", - "stack": "array:call-stack-item" - } - }, - "function-call": { - "category": "actor", - "typeName": "function-call", - "methods": [ - { - "name": "getDetails", - "request": { - "type": "getDetails" - }, - "response": { - "info": { - "_retval": "call-details" - } - } - } - ], - "events": {} - }, - "call-watcher": { - "category": "actor", - "typeName": "call-watcher", - "methods": [ - { - "name": "setup", - "oneway": true, - "request": { - "type": "setup", - "tracedGlobals": { - "_option": 0, - "type": "nullable:array:string" - }, - "tracedFunctions": { - "_option": 0, - "type": "nullable:array:string" - }, - "startRecording": { - "_option": 0, - "type": "boolean" - }, - "performReload": { - "_option": 0, - "type": "boolean" - } - }, - "response": {} - }, - { - "name": "finalize", - "oneway": true, - "request": { - "type": "finalize" - }, - "response": {} - }, - { - "name": "isRecording", - "request": { - "type": "isRecording" - }, - "response": { - "_retval": "boolean" - } - }, - { - "name": "resumeRecording", - "request": { - "type": "resumeRecording" - }, - "response": {} - }, - { - "name": "pauseRecording", - "request": { - "type": "pauseRecording" - }, - "response": { - "calls": { - "_retval": "array:function-call" - } - } - }, - { - "name": "eraseRecording", - "request": { - "type": "eraseRecording" - }, - "response": {} - } - ], - "events": {} - }, - "snapshot-image": { - "category": "dict", - "typeName": "snapshot-image", - "specializations": { - "index": "number", - "width": "number", - "height": "number", - "flipped": "boolean", - "pixels": "uint32-array" - } - }, - "snapshot-overview": { - "category": "dict", - "typeName": "snapshot-overview", - "specializations": { - "calls": "array:function-call", - "thumbnails": "array:snapshot-image", - "screenshot": "snapshot-image" - } - }, - "frame-snapshot": { - "category": "actor", - "typeName": "frame-snapshot", - "methods": [ - { - "name": "getOverview", - "request": { - "type": "getOverview" - }, - "response": { - "overview": { - "_retval": "snapshot-overview" - } - } - }, - { - "name": "generateScreenshotFor", - "request": { - "type": "generateScreenshotFor", - "call": { - "_arg": 0, - "type": "function-call" - } - }, - "response": { - "screenshot": { - "_retval": "snapshot-image" - } - } - } - ], - "events": {} - }, - "canvas": { - "category": "actor", - "typeName": "canvas", - "methods": [ - { - "name": "setup", - "oneway": true, - "request": { - "type": "setup", - "reload": { - "_option": 0, - "type": "boolean" - } - }, - "response": {} - }, - { - "name": "finalize", - "oneway": true, - "request": { - "type": "finalize" - }, - "response": {} - }, - { - "name": "isInitialized", - "request": { - "type": "isInitialized" - }, - "response": { - "initialized": { - "_retval": "boolean" - } - } - }, - { - "name": "recordAnimationFrame", - "request": { - "type": "recordAnimationFrame" - }, - "response": { - "snapshot": { - "_retval": "frame-snapshot" - } - } - } - ], - "events": {} - }, - "gl-shader": { - "category": "actor", - "typeName": "gl-shader", - "methods": [ - { - "name": "getText", - "request": { - "type": "getText" - }, - "response": { - "text": { - "_retval": "string" - } - } - }, - { - "name": "compile", - "request": { - "type": "compile", - "text": { - "_arg": 0, - "type": "string" - } - }, - "response": { - "error": { - "_retval": "nullable:json" - } - } - } - ], - "events": {} - }, - "gl-program": { - "category": "actor", - "typeName": "gl-program", - "methods": [ - { - "name": "getVertexShader", - "request": { - "type": "getVertexShader" - }, - "response": { - "shader": { - "_retval": "gl-shader" - } - } - }, - { - "name": "getFragmentShader", - "request": { - "type": "getFragmentShader" - }, - "response": { - "shader": { - "_retval": "gl-shader" - } - } - }, - { - "name": "highlight", - "oneway": true, - "request": { - "type": "highlight", - "tint": { - "_arg": 0, - "type": "array:number" - } - }, - "response": {} - }, - { - "name": "unhighlight", - "oneway": true, - "request": { - "type": "unhighlight" - }, - "response": {} - }, - { - "name": "blackbox", - "oneway": true, - "request": { - "type": "blackbox" - }, - "response": {} - }, - { - "name": "unblackbox", - "oneway": true, - "request": { - "type": "unblackbox" - }, - "response": {} - } - ], - "events": {} - }, - "webgl": { - "category": "actor", - "typeName": "webgl", - "methods": [ - { - "name": "setup", - "oneway": true, - "request": { - "type": "setup", - "reload": { - "_option": 0, - "type": "boolean" - } - }, - "response": {} - }, - { - "name": "finalize", - "oneway": true, - "request": { - "type": "finalize" - }, - "response": {} - }, - { - "name": "getPrograms", - "request": { - "type": "getPrograms" - }, - "response": { - "programs": { - "_retval": "array:gl-program" - } - } - } - ], - "events": { - "program-linked": { - "type": "programLinked", - "program": { - "_arg": 0, - "type": "gl-program" - } - } - } - }, - "audionode": { - "category": "actor", - "typeName": "audionode", - "methods": [ - { - "name": "getType", - "request": { - "type": "getType" - }, - "response": { - "type": { - "_retval": "string" - } - } - }, - { - "name": "isSource", - "request": { - "type": "isSource" - }, - "response": { - "source": { - "_retval": "boolean" - } - } - }, - { - "name": "setParam", - "request": { - "type": "setParam", - "param": { - "_arg": 0, - "type": "string" - }, - "value": { - "_arg": 1, - "type": "nullable:primitive" - } - }, - "response": { - "error": { - "_retval": "nullable:json" - } - } - }, - { - "name": "getParam", - "request": { - "type": "getParam", - "param": { - "_arg": 0, - "type": "string" - } - }, - "response": { - "text": { - "_retval": "nullable:primitive" - } - } - }, - { - "name": "getParamFlags", - "request": { - "type": "getParamFlags", - "param": { - "_arg": 0, - "type": "string" - } - }, - "response": { - "flags": { - "_retval": "nullable:primitive" - } - } - }, - { - "name": "getParams", - "request": { - "type": "getParams" - }, - "response": { - "params": { - "_retval": "json" - } - } - } - ], - "events": {} - }, - "webaudio": { - "category": "actor", - "typeName": "webaudio", - "methods": [ - { - "name": "setup", - "oneway": true, - "request": { - "type": "setup", - "reload": { - "_option": 0, - "type": "boolean" - } - }, - "response": {} - }, - { - "name": "finalize", - "oneway": true, - "request": { - "type": "finalize" - }, - "response": {} - } - ], - "events": { - "start-context": { - "type": "startContext" - }, - "connect-node": { - "type": "connectNode", - "source": { - "_option": 0, - "type": "audionode" - }, - "dest": { - "_option": 0, - "type": "audionode" - } - }, - "disconnect-node": { - "type": "disconnectNode", - "source": { - "_arg": 0, - "type": "audionode" - } - }, - "connect-param": { - "type": "connectParam", - "source": { - "_arg": 0, - "type": "audionode" - }, - "param": { - "_arg": 1, - "type": "string" - } - }, - "change-param": { - "type": "changeParam", - "source": { - "_option": 0, - "type": "audionode" - }, - "param": { - "_option": 0, - "type": "string" - }, - "value": { - "_option": 0, - "type": "string" - } - }, - "create-node": { - "type": "createNode", - "source": { - "_arg": 0, - "type": "audionode" - } - } - } - }, - "old-stylesheet": { - "category": "actor", - "typeName": "old-stylesheet", - "methods": [ - { - "name": "toggleDisabled", - "request": { - "type": "toggleDisabled" - }, - "response": { - "disabled": { - "_retval": "boolean" - } - } - }, - { - "name": "fetchSource", - "request": { - "type": "fetchSource" - }, - "response": {} - }, - { - "name": "update", - "request": { - "type": "update", - "text": { - "_arg": 0, - "type": "string" - }, - "transition": { - "_arg": 1, - "type": "boolean" - } - }, - "response": {} - } - ], - "events": { - "property-change": { - "type": "propertyChange", - "property": { - "_arg": 0, - "type": "string" - }, - "value": { - "_arg": 1, - "type": "json" - } - }, - "source-load": { - "type": "sourceLoad", - "source": { - "_arg": 0, - "type": "string" - } - }, - "style-applied": { - "type": "styleApplied" - } - } - }, - "styleeditor": { - "category": "actor", - "typeName": "styleeditor", - "methods": [ - { - "name": "newDocument", - "request": { - "type": "newDocument" - }, - "response": {} - }, - { - "name": "newStyleSheet", - "request": { - "type": "newStyleSheet", - "text": { - "_arg": 0, - "type": "string" - } - }, - "response": { - "styleSheet": { - "_retval": "old-stylesheet" - } - } - } - ], - "events": { - "document-load": { - "type": "documentLoad", - "styleSheets": { - "_arg": 0, - "type": "array:old-stylesheet" - } - } - } - }, - "cookieobject": { - "category": "dict", - "typeName": "cookieobject", - "specializations": { - "name": "string", - "value": "longstring", - "path": "nullable:string", - "host": "string", - "isDomain": "boolean", - "isSecure": "boolean", - "isHttpOnly": "boolean", - "creationTime": "number", - "lastAccessed": "number", - "expires": "number" - } - }, - "cookiestoreobject": { - "category": "dict", - "typeName": "cookiestoreobject", - "specializations": { - "total": "number", - "offset": "number", - "data": "array:nullable:cookieobject" - } - }, - "storageobject": { - "category": "dict", - "typeName": "storageobject", - "specializations": { - "name": "string", - "value": "longstring" - } - }, - "storagestoreobject": { - "category": "dict", - "typeName": "storagestoreobject", - "specializations": { - "total": "number", - "offset": "number", - "data": "array:nullable:storageobject" - } - }, - "idbobject": { - "category": "dict", - "typeName": "idbobject", - "specializations": { - "name": "nullable:string", - "db": "nullable:string", - "objectStore": "nullable:string", - "origin": "nullable:string", - "version": "nullable:number", - "objectStores": "nullable:number", - "keyPath": "nullable:string", - "autoIncrement": "nullable:boolean", - "indexes": "nullable:string", - "value": "nullable:longstring" - } - }, - "idbstoreobject": { - "category": "dict", - "typeName": "idbstoreobject", - "specializations": { - "total": "number", - "offset": "number", - "data": "array:nullable:idbobject" - } - }, - "storeUpdateObject": { - "category": "dict", - "typeName": "storeUpdateObject", - "specializations": { - "changed": "nullable:json", - "deleted": "nullable:json", - "added": "nullable:json" - } - }, - "cookies": { - "category": "actor", - "typeName": "cookies", - "methods": [ - { - "name": "getStoreObjects", - "request": { - "type": "getStoreObjects", - "host": { - "_arg": 0, - "type": "primitive" - }, - "names": { - "_arg": 1, - "type": "nullable:array:string" - }, - "options": { - "_arg": 2, - "type": "nullable:json" - } - }, - "response": { - "_retval": "cookiestoreobject" - } - } - ], - "events": {} - }, - "localStorage": { - "category": "actor", - "typeName": "localStorage", - "methods": [ - { - "name": "getStoreObjects", - "request": { - "type": "getStoreObjects", - "host": { - "_arg": 0, - "type": "primitive" - }, - "names": { - "_arg": 1, - "type": "nullable:array:string" - }, - "options": { - "_arg": 2, - "type": "nullable:json" - } - }, - "response": { - "_retval": "storagestoreobject" - } - } - ], - "events": {} - }, - "sessionStorage": { - "category": "actor", - "typeName": "sessionStorage", - "methods": [ - { - "name": "getStoreObjects", - "request": { - "type": "getStoreObjects", - "host": { - "_arg": 0, - "type": "primitive" - }, - "names": { - "_arg": 1, - "type": "nullable:array:string" - }, - "options": { - "_arg": 2, - "type": "nullable:json" - } - }, - "response": { - "_retval": "storagestoreobject" - } - } - ], - "events": {} - }, - "indexedDB": { - "category": "actor", - "typeName": "indexedDB", - "methods": [ - { - "name": "getStoreObjects", - "request": { - "type": "getStoreObjects", - "host": { - "_arg": 0, - "type": "primitive" - }, - "names": { - "_arg": 1, - "type": "nullable:array:string" - }, - "options": { - "_arg": 2, - "type": "nullable:json" - } - }, - "response": { - "_retval": "idbstoreobject" - } - } - ], - "events": {} - }, - "storelist": { - "category": "dict", - "typeName": "storelist", - "specializations": { - "cookies": "cookies", - "localStorage": "localStorage", - "sessionStorage": "sessionStorage", - "indexedDB": "indexedDB" - } - }, - "storage": { - "category": "actor", - "typeName": "storage", - "methods": [ - { - "name": "listStores", - "request": { - "type": "listStores" - }, - "response": { - "_retval": "storelist" - } - } - ], - "events": { - "stores-update": { - "type": "storesUpdate", - "data": { - "_arg": 0, - "type": "storeUpdateObject" - } - }, - "stores-cleared": { - "type": "storesCleared", - "data": { - "_arg": 0, - "type": "json" - } - }, - "stores-reloaded": { - "type": "storesRelaoded", - "data": { - "_arg": 0, - "type": "json" - } - } - } - }, - "gcli": { - "category": "actor", - "typeName": "gcli", - "methods": [ - { - "name": "specs", - "request": { - "type": "specs" - }, - "response": { - "_retval": "json" - } - }, - { - "name": "execute", - "request": { - "type": "execute", - "typed": { - "_arg": 0, - "type": "string" - } - }, - "response": { - "_retval": "json" - } - }, - { - "name": "state", - "request": { - "type": "state", - "typed": { - "_arg": 0, - "type": "string" - }, - "start": { - "_arg": 1, - "type": "number" - }, - "rank": { - "_arg": 2, - "type": "number" - } - }, - "response": { - "_retval": "json" - } - }, - { - "name": "typeparse", - "request": { - "type": "typeparse", - "typed": { - "_arg": 0, - "type": "string" - }, - "param": { - "_arg": 1, - "type": "string" - } - }, - "response": { - "_retval": "json" - } - }, - { - "name": "typeincrement", - "request": { - "type": "typeincrement", - "typed": { - "_arg": 0, - "type": "string" - }, - "param": { - "_arg": 1, - "type": "string" - } - }, - "response": { - "_retval": "string" - } - }, - { - "name": "typedecrement", - "request": { - "type": "typedecrement", - "typed": { - "_arg": 0, - "type": "string" - }, - "param": { - "_arg": 1, - "type": "string" - } - }, - "response": { - "_retval": "string" - } - }, - { - "name": "selectioninfo", - "request": { - "type": "selectioninfo", - "typed": { - "_arg": 0, - "type": "string" - }, - "param": { - "_arg": 1, - "type": "string" - }, - "action": { - "_arg": 1, - "type": "string" - } - }, - "response": { - "_retval": "json" - } - } - ], - "events": {} - }, - "memory": { - "category": "actor", - "typeName": "memory", - "methods": [ - { - "name": "measure", - "request": { - "type": "measure" - }, - "response": { - "_retval": "json" - } - } - ], - "events": {} - }, - "eventLoopLag": { - "category": "actor", - "typeName": "eventLoopLag", - "methods": [ - { - "name": "start", - "request": { - "type": "start" - }, - "response": { - "success": { - "_retval": "number" - } - } - }, - { - "name": "stop", - "request": { - "type": "stop" - }, - "response": {} - } - ], - "events": { - "event-loop-lag": { - "type": "event-loop-lag", - "time": { - "_arg": 0, - "type": "number" - } - } - } - }, - "preference": { - "category": "actor", - "typeName": "preference", - "methods": [ - { - "name": "getBoolPref", - "request": { - "type": "getBoolPref", - "value": { - "_arg": 0, - "type": "primitive" - } - }, - "response": { - "value": { - "_retval": "boolean" - } - } - }, - { - "name": "getCharPref", - "request": { - "type": "getCharPref", - "value": { - "_arg": 0, - "type": "primitive" - } - }, - "response": { - "value": { - "_retval": "string" - } - } - }, - { - "name": "getIntPref", - "request": { - "type": "getIntPref", - "value": { - "_arg": 0, - "type": "primitive" - } - }, - "response": { - "value": { - "_retval": "number" - } - } - }, - { - "name": "getAllPrefs", - "request": { - "type": "getAllPrefs" - }, - "response": { - "value": { - "_retval": "json" - } - } - }, - { - "name": "setBoolPref", - "request": { - "type": "setBoolPref", - "name": { - "_arg": 0, - "type": "primitive" - }, - "value": { - "_arg": 1, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "setCharPref", - "request": { - "type": "setCharPref", - "name": { - "_arg": 0, - "type": "primitive" - }, - "value": { - "_arg": 1, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "setIntPref", - "request": { - "type": "setIntPref", - "name": { - "_arg": 0, - "type": "primitive" - }, - "value": { - "_arg": 1, - "type": "primitive" - } - }, - "response": {} - }, - { - "name": "clearUserPref", - "request": { - "type": "clearUserPref", - "name": { - "_arg": 0, - "type": "primitive" - } - }, - "response": {} - } - ], - "events": {} - }, - "device": { - "category": "actor", - "typeName": "device", - "methods": [ - { - "name": "getDescription", - "request": { - "type": "getDescription" - }, - "response": { - "value": { - "_retval": "json" - } - } - }, - { - "name": "getWallpaper", - "request": { - "type": "getWallpaper" - }, - "response": { - "value": { - "_retval": "longstring" - } - } - }, - { - "name": "screenshotToDataURL", - "request": { - "type": "screenshotToDataURL" - }, - "response": { - "value": { - "_retval": "longstring" - } - } - }, - { - "name": "getRawPermissionsTable", - "request": { - "type": "getRawPermissionsTable" - }, - "response": { - "value": { - "_retval": "json" - } - } - } - ], - "events": {} - } - }, - "from": "root" -} - -},{}],25:[function(_dereq_,module,exports){ -"use strict"; - -var Class = _dereq_("./class").Class; -var util = _dereq_("./util"); -var keys = util.keys; -var values = util.values; -var pairs = util.pairs; -var query = util.query; -var findPath = util.findPath; -var EventTarget = _dereq_("./event").EventTarget; - -var TypeSystem = Class({ - constructor: function(client) { - var types = Object.create(null); - var specification = Object.create(null); - - this.specification = specification; - this.types = types; - - var typeFor = function typeFor(typeName) { - typeName = typeName || "primitive"; - if (!types[typeName]) { - defineType(typeName); - } - - return types[typeName]; - }; - this.typeFor = typeFor; - - var defineType = function(descriptor) { - var type = void(0); - if (typeof(descriptor) === "string") { - if (descriptor.indexOf(":") > 0) - type = makeCompoundType(descriptor); - else if (descriptor.indexOf("#") > 0) - type = new ActorDetail(descriptor); - else if (specification[descriptor]) - type = makeCategoryType(specification[descriptor]); - } else { - type = makeCategoryType(descriptor); - } - - if (type) - types[type.name] = type; - else - throw TypeError("Invalid type: " + descriptor); - }; - this.defineType = defineType; - - - var makeCompoundType = function(name) { - var index = name.indexOf(":"); - var baseType = name.slice(0, index); - var subType = name.slice(index + 1); - - return baseType === "array" ? new ArrayOf(subType) : - baseType === "nullable" ? new Maybe(subType) : - null; - }; - - var makeCategoryType = function(descriptor) { - var category = descriptor.category; - return category === "dict" ? new Dictionary(descriptor) : - category === "actor" ? new Actor(descriptor) : - null; - }; - - var read = function(input, context, typeName) { - return typeFor(typeName).read(input, context); - } - this.read = read; - - var write = function(input, context, typeName) { - return typeFor(typeName).write(input); - }; - this.write = write; - - - var Type = Class({ - constructor: function() { - }, - get name() { - return this.category ? this.category + ":" + this.type : - this.type; - }, - read: function(input, context) { - throw new TypeError("`Type` subclass must implement `read`"); - }, - write: function(input, context) { - throw new TypeError("`Type` subclass must implement `write`"); - } - }); - - var Primitve = Class({ - extends: Type, - constuctor: function(type) { - this.type = type; - }, - read: function(input, context) { - return input; - }, - write: function(input, context) { - return input; - } - }); - - var Maybe = Class({ - extends: Type, - category: "nullable", - constructor: function(type) { - this.type = type; - }, - read: function(input, context) { - return input === null ? null : - input === void(0) ? void(0) : - read(input, context, this.type); - }, - write: function(input, context) { - return input === null ? null : - input === void(0) ? void(0) : - write(input, context, this.type); - } - }); - - var ArrayOf = Class({ - extends: Type, - category: "array", - constructor: function(type) { - this.type = type; - }, - read: function(input, context) { - var type = this.type; - return input.map(function($) { return read($, context, type) }); - }, - write: function(input, context) { - var type = this.type; - return input.map(function($) { return write($, context, type) }); - } - }); - - var makeField = function makeField(name, type) { - return { - enumerable: true, - configurable: true, - get: function() { - Object.defineProperty(this, name, { - configurable: false, - value: read(this.state[name], this.context, type) - }); - return this[name]; - } - } - }; - - var makeFields = function(descriptor) { - return pairs(descriptor).reduce(function(fields, pair) { - var name = pair[0], type = pair[1]; - fields[name] = makeField(name, type); - return fields; - }, {}); - } - - var DictionaryType = Class({}); - - var Dictionary = Class({ - extends: Type, - category: "dict", - get name() { return this.type; }, - constructor: function(descriptor) { - this.type = descriptor.typeName; - this.types = descriptor.specializations; - - var proto = Object.defineProperties({ - extends: DictionaryType, - constructor: function(state, context) { - Object.defineProperties(this, { - state: { - enumerable: false, - writable: true, - configurable: true, - value: state - }, - context: { - enumerable: false, - writable: false, - configurable: true, - value: context - } - }); - } - }, makeFields(this.types)); - - this.class = new Class(proto); - }, - read: function(input, context) { - return new this.class(input, context); - }, - write: function(input, context) { - var output = {}; - for (var key in input) { - output[key] = write(value, context, types[key]); - } - return output; - } - }); - - var makeMethods = function(descriptors) { - return descriptors.reduce(function(methods, descriptor) { - methods[descriptor.name] = { - enumerable: true, - configurable: true, - writable: false, - value: makeMethod(descriptor) - }; - return methods; - }, {}); - }; - - var makeEvents = function(descriptors) { - return pairs(descriptors).reduce(function(events, pair) { - var name = pair[0], descriptor = pair[1]; - var event = new Event(name, descriptor); - events[event.eventType] = event; - return events; - }, Object.create(null)); - }; - - var Actor = Class({ - extends: Type, - category: "actor", - get name() { return this.type; }, - constructor: function(descriptor) { - this.type = descriptor.typeName; - - var events = makeEvents(descriptor.events || {}); - var fields = makeFields(descriptor.fields || {}); - var methods = makeMethods(descriptor.methods || []); - - - var proto = { - extends: Front, - constructor: function() { - Front.apply(this, arguments); - }, - events: events - }; - Object.defineProperties(proto, fields); - Object.defineProperties(proto, methods); - - this.class = Class(proto); - }, - read: function(input, context, detail) { - var state = typeof(input) === "string" ? { actor: input } : input; - - var actor = client.get(state.actor) || new this.class(state, context); - actor.form(state, detail, context); - - return actor; - }, - write: function(input, context, detail) { - return input.id; - } - }); - exports.Actor = Actor; - - - var ActorDetail = Class({ - extends: Actor, - constructor: function(name) { - var parts = name.split("#") - this.actorType = parts[0] - this.detail = parts[1]; - }, - read: function(input, context) { - return typeFor(this.actorType).read(input, context, this.detail); - }, - write: function(input, context) { - return typeFor(this.actorType).write(input, context, this.detail); - } - }); - exports.ActorDetail = ActorDetail; - - var Method = Class({ - extends: Type, - constructor: function(descriptor) { - this.type = descriptor.name; - this.path = findPath(descriptor.response, "_retval"); - this.responseType = this.path && query(descriptor.response, this.path)._retval; - this.requestType = descriptor.request.type; - - var params = []; - for (var key in descriptor.request) { - if (key !== "type") { - var param = descriptor.request[key]; - var index = "_arg" in param ? param._arg : param._option; - var isParam = param._option === index; - var isArgument = param._arg === index; - params[index] = { - type: param.type, - key: key, - index: index, - isParam: isParam, - isArgument: isArgument - }; - } - } - this.params = params; - }, - read: function(input, context) { - return read(query(input, this.path), context, this.responseType); - }, - write: function(input, context) { - return this.params.reduce(function(result, param) { - result[param.key] = write(input[param.index], context, param.type); - return result; - }, {type: this.type}); - } - }); - exports.Method = Method; - - var profiler = function(method, id) { - return function() { - var start = new Date(); - return method.apply(this, arguments).then(function(result) { - var end = new Date(); - client.telemetry.add(id, +end - start); - return result; - }); - }; - }; - - var destructor = function(method) { - return function() { - return method.apply(this, arguments).then(function(result) { - client.release(this); - return result; - }); - }; - }; - - function makeMethod(descriptor) { - var type = new Method(descriptor); - var method = descriptor.oneway ? makeUnidirecationalMethod(descriptor, type) : - makeBidirectionalMethod(descriptor, type); - - if (descriptor.telemetry) - method = profiler(method); - if (descriptor.release) - method = destructor(method); - - return method; - } - - var makeUnidirecationalMethod = function(descriptor, type) { - return function() { - var packet = type.write(arguments, this); - packet.to = this.id; - client.send(packet); - return Promise.resolve(void(0)); - }; - }; - - var makeBidirectionalMethod = function(descriptor, type) { - return function() { - var context = this.context; - var packet = type.write(arguments, context); - var context = this.context; - packet.to = this.id; - return client.request(packet).then(function(packet) { - return type.read(packet, context); - }); - }; - }; - - var Event = Class({ - constructor: function(name, descriptor) { - this.name = descriptor.type || name; - this.eventType = descriptor.type || name; - this.types = Object.create(null); - - var types = this.types; - for (var key in descriptor) { - if (key === "type") { - types[key] = "string"; - } else { - types[key] = descriptor[key].type; - } - } - }, - read: function(input, context) { - var output = {}; - var types = this.types; - for (var key in input) { - output[key] = read(input[key], context, types[key]); - } - return output; - }, - write: function(input, context) { - var output = {}; - var types = this.types; - for (var key in this.types) { - output[key] = write(input[key], context, types[key]); - } - return output; - } - }); - - var Front = Class({ - extends: EventTarget, - EventTarget: EventTarget, - constructor: function(state) { - this.EventTarget(); - Object.defineProperties(this, { - state: { - enumerable: false, - writable: true, - configurable: true, - value: state - } - }); - - client.register(this); - }, - get id() { - return this.state.actor; - }, - get context() { - return this; - }, - form: function(state, detail, context) { - if (this.state !== state) { - if (detail) { - this.state[detail] = state[detail]; - } else { - pairs(state).forEach(function(pair) { - var key = pair[0], value = pair[1]; - this.state[key] = value; - }, this); - } - } - - if (context) { - client.supervise(context, this); - } - }, - requestTypes: function() { - return client.request({ - to: this.id, - type: "requestTypes" - }).then(function(packet) { - return packet.requestTypes; - }); - } - }); - types.primitive = new Primitve("primitive"); - types.string = new Primitve("string"); - types.number = new Primitve("number"); - types.boolean = new Primitve("boolean"); - types.json = new Primitve("json"); - types.array = new Primitve("array"); - }, - registerTypes: function(descriptor) { - var specification = this.specification; - values(descriptor.types).forEach(function(descriptor) { - specification[descriptor.typeName] = descriptor; - }); - } -}); -exports.TypeSystem = TypeSystem; - -},{"./class":3,"./event":5,"./util":26}],26:[function(_dereq_,module,exports){ -"use strict"; - -var keys = Object.keys; -exports.keys = keys; - -// Returns array of values for the given object. -var values = function(object) { - return keys(object).map(function(key) { - return object[key] - }); -}; -exports.values = values; - -// Returns [key, value] pairs for the given object. -var pairs = function(object) { - return keys(object).map(function(key) { - return [key, object[key]] - }); -}; -exports.pairs = pairs; - - -// Queries an object for the field nested with in it. -var query = function(object, path) { - return path.reduce(function(object, entry) { - return object && object[entry] - }, object); -}; -exports.query = query; - -var isObject = function(x) { - return x && typeof(x) === "object" -} - -var findPath = function(object, key) { - var path = void(0); - if (object && typeof(object) === "object") { - var names = keys(object); - if (names.indexOf(key) >= 0) { - path = []; - } else { - var index = 0; - var count = names.length; - while (index < count && !path){ - var head = names[index]; - var tail = findPath(object[head], key); - path = tail ? [head].concat(tail) : tail; - index = index + 1 - } - } - } - return path; -}; -exports.findPath = findPath; - -},{}]},{},[1]) -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlcyI6WyIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vYnJvd3Nlci9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL2Jyb3dzZXIvcHJvbWlzZS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL2NsYXNzLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vY2xpZW50LmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vZXZlbnQuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvZXZlbnRzL2V2ZW50cy5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL2luZGV4LmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvaXMtaW1wbGVtZW50ZWQuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZC9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L29iamVjdC9hc3NpZ24vaW5kZXguanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3QvYXNzaWduL2lzLWltcGxlbWVudGVkLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2Fzc2lnbi9zaGltLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2lzLWNhbGxhYmxlLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2tleXMvaW5kZXguanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3Qva2V5cy9pcy1pbXBsZW1lbnRlZC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L29iamVjdC9rZXlzL3NoaW0uanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3Qvbm9ybWFsaXplLW9wdGlvbnMuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3QvdmFsaWQtdmFsdWUuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9zdHJpbmcvIy9jb250YWlucy9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L3N0cmluZy8jL2NvbnRhaW5zL2lzLWltcGxlbWVudGVkLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvc3RyaW5nLyMvY29udGFpbnMvc2hpbS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL3BvbHlmaWxsLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vc3BlY2lmaWNhdGlvbi9jb3JlLmpzb24iLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9zcGVjaWZpY2F0aW9uL3Byb3RvY29sLmpzb24iLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi90eXBlLXN5c3RlbS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL3V0aWwuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNUQTtBQUNBO0FBQ0E7QUFDQTs7QUNIQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaExBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9TQTtBQUNBO0FBQ0E7QUFDQTs7QUNIQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNMQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNUQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDTEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ0xBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNSQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1BBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ05BO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNMQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDUkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNQQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDckRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pKQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzdUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcmRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt0aHJvdyBuZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpfXZhciBmPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChmLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGYsZi5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIENsaWVudCA9IHJlcXVpcmUoXCIuLi9jbGllbnRcIikuQ2xpZW50O1xuXG5mdW5jdGlvbiBjb25uZWN0KHBvcnQpIHtcbiAgdmFyIGNsaWVudCA9IG5ldyBDbGllbnQoKTtcbiAgcmV0dXJuIGNsaWVudC5jb25uZWN0KHBvcnQpO1xufVxuZXhwb3J0cy5jb25uZWN0ID0gY29ubmVjdDtcbiIsIlwidXNlIHN0cmljdFwiO1xuXG5leHBvcnRzLlByb21pc2UgPSBQcm9taXNlO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnZhciBkZXNjcmliZSA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3I7XG52YXIgQ2xhc3MgPSBmdW5jdGlvbihmaWVsZHMpIHtcbiAgdmFyIG5hbWVzID0gT2JqZWN0LmtleXMoZmllbGRzKTtcbiAgdmFyIGNvbnN0cnVjdG9yID0gbmFtZXMuaW5kZXhPZihcImNvbnN0cnVjdG9yXCIpID49IDAgPyBmaWVsZHMuY29uc3RydWN0b3IgOlxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbigpIHt9O1xuICB2YXIgYW5jZXN0b3IgPSBmaWVsZHMuZXh0ZW5kcyB8fCBPYmplY3Q7XG5cbiAgdmFyIGRlc2NyaXB0b3IgPSBuYW1lcy5yZWR1Y2UoZnVuY3Rpb24oZGVzY3JpcHRvciwga2V5KSB7XG4gICAgZGVzY3JpcHRvcltrZXldID0gZGVzY3JpYmUoZmllbGRzLCBrZXkpO1xuICAgIHJldHVybiBkZXNjcmlwdG9yO1xuICB9LCB7fSk7XG5cbiAgdmFyIHByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoYW5jZXN0b3IucHJvdG90eXBlLCBkZXNjcmlwdG9yKTtcblxuICBjb25zdHJ1Y3Rvci5wcm90b3R5cGUgPSBwcm90b3R5cGU7XG4gIHByb3RvdHlwZS5jb25zdHJ1Y3RvciA9IGNvbnN0cnVjdG9yO1xuXG4gIHJldHVybiBjb25zdHJ1Y3Rvcjtcbn07XG5leHBvcnRzLkNsYXNzID0gQ2xhc3M7XG4iLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIENsYXNzID0gcmVxdWlyZShcIi4vY2xhc3NcIikuQ2xhc3M7XG52YXIgVHlwZVN5c3RlbSA9IHJlcXVpcmUoXCIuL3R5cGUtc3lzdGVtXCIpLlR5cGVTeXN0ZW07XG52YXIgdmFsdWVzID0gcmVxdWlyZShcIi4vdXRpbFwiKS52YWx1ZXM7XG52YXIgUHJvbWlzZSA9IHJlcXVpcmUoXCJlczYtcHJvbWlzZVwiKS5Qcm9taXNlO1xudmFyIE1lc3NhZ2VFdmVudCA9IHJlcXVpcmUoXCIuL2V2ZW50XCIpLk1lc3NhZ2VFdmVudDtcblxudmFyIHNwZWNpZmljYXRpb24gPSByZXF1aXJlKFwiLi9zcGVjaWZpY2F0aW9uL2NvcmUuanNvblwiKTtcblxuZnVuY3Rpb24gcmVjb3ZlckFjdG9yRGVzY3JpcHRpb25zKGVycm9yKSB7XG4gIGNvbnNvbGUud2FybihcIkZhaWxlZCB0byBmZXRjaCBwcm90b2NvbCBzcGVjaWZpY2F0aW9uIChzZWUgcmVhc29uIGJlbG93KS4gXCIgK1xuICAgICAgICAgICAgICAgXCJVc2luZyBhIGZhbGxiYWNrIHByb3RvY2FsIHNwZWNpZmljYXRpb24hXCIsXG4gICAgICAgICAgICAgICBlcnJvcik7XG4gIHJldHVybiByZXF1aXJlKFwiLi9zcGVjaWZpY2F0aW9uL3Byb3RvY29sLmpzb25cIik7XG59XG5cbi8vIFR5cGUgdG8gcmVwcmVzZW50IHN1cGVydmlzZXIgYWN0b3IgcmVsYXRpb25zIHRvIGFjdG9ycyB0aGV5IHN1cGVydmlzZVxuLy8gaW4gdGVybXMgb2YgbGlmZXRpbWUgbWFuYWdlbWVudC5cbnZhciBTdXBlcnZpc29yID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oaWQpIHtcbiAgICB0aGlzLmlkID0gaWQ7XG4gICAgdGhpcy53b3JrZXJzID0gW107XG4gIH1cbn0pO1xuXG52YXIgVGVsZW1ldHJ5ID0gQ2xhc3Moe1xuICBhZGQ6IGZ1bmN0aW9uKGlkLCBtcykge1xuICAgIGNvbnNvbGUubG9nKFwidGVsZW1ldHJ5OjpcIiwgaWQsIG1zKVxuICB9XG59KTtcblxuLy8gQ29uc2lkZXIgbWFraW5nIGNsaWVudCBhIHJvb3QgYWN0b3IuXG5cbnZhciBDbGllbnQgPSBDbGFzcyh7XG4gIGNvbnN0cnVjdG9yOiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLnJvb3QgPSBudWxsO1xuICAgIHRoaXMudGVsZW1ldHJ5ID0gbmV3IFRlbGVtZXRyeSgpO1xuXG4gICAgdGhpcy5zZXR1cENvbm5lY3Rpb24oKTtcbiAgICB0aGlzLnNldHVwTGlmZU1hbmFnZW1lbnQoKTtcbiAgICB0aGlzLnNldHVwVHlwZVN5c3RlbSgpO1xuICB9LFxuXG4gIHNldHVwQ29ubmVjdGlvbjogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy5yZXF1ZXN0cyA9IFtdO1xuICB9LFxuICBzZXR1cExpZmVNYW5hZ2VtZW50OiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLmNhY2hlID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmdyYXBoID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmdldCA9IHRoaXMuZ2V0LmJpbmQodGhpcyk7XG4gICAgdGhpcy5yZWxlYXNlID0gdGhpcy5yZWxlYXNlLmJpbmQodGhpcyk7XG4gIH0sXG4gIHNldHVwVHlwZVN5c3RlbTogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy50eXBlU3lzdGVtID0gbmV3IFR5cGVTeXN0ZW0odGhpcyk7XG4gICAgdGhpcy50eXBlU3lzdGVtLnJlZ2lzdGVyVHlwZXMoc3BlY2lmaWNhdGlvbik7XG4gIH0sXG5cbiAgY29ubmVjdDogZnVuY3Rpb24ocG9ydCkge1xuICAgIHZhciBjbGllbnQgPSB0aGlzO1xuICAgIHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihyZXNvbHZlLCByZWplY3QpIHtcbiAgICAgIGNsaWVudC5wb3J0ID0gcG9ydDtcbiAgICAgIHBvcnQub25tZXNzYWdlID0gY2xpZW50LnJlY2VpdmUuYmluZChjbGllbnQpO1xuICAgICAgY2xpZW50Lm9uUmVhZHkgPSByZXNvbHZlO1xuICAgICAgY2xpZW50Lm9uRmFpbCA9IHJlamVjdDtcblxuICAgICAgcG9ydC5zdGFydCgpO1xuICAgIH0pO1xuICB9LFxuICBzZW5kOiBmdW5jdGlvbihwYWNrZXQpIHtcbiAgICB0aGlzLnBvcnQucG9zdE1lc3NhZ2UocGFja2V0KTtcbiAgfSxcbiAgcmVxdWVzdDogZnVuY3Rpb24ocGFja2V0KSB7XG4gICAgdmFyIGNsaWVudCA9IHRoaXM7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKHJlc29sdmUsIHJlamVjdCkge1xuICAgICAgY2xpZW50LnJlcXVlc3RzLnB1c2gocGFja2V0LnRvLCB7IHJlc29sdmU6IHJlc29sdmUsIHJlamVjdDogcmVqZWN0IH0pO1xuICAgICAgY2xpZW50LnNlbmQocGFja2V0KTtcbiAgICB9KTtcbiAgfSxcblxuICByZWNlaXZlOiBmdW5jdGlvbihldmVudCkge1xuICAgIHZhciBwYWNrZXQgPSBldmVudC5kYXRhO1xuICAgIGlmICghdGhpcy5yb290KSB7XG4gICAgICBpZiAocGFja2V0LmZyb20gIT09IFwicm9vdFwiKVxuICAgICAgICB0aHJvdyBFcnJvcihcIkluaXRpYWwgcGFja2V0IG11c3QgYmUgZnJvbSByb290XCIpO1xuICAgICAgaWYgKCEoXCJhcHBsaWNhdGlvblR5cGVcIiBpbiBwYWNrZXQpKVxuICAgICAgICB0aHJvdyBFcnJvcihcIkluaXRpYWwgcGFja2V0IG11c3QgY29udGFpbiBhcHBsaWNhdGlvblR5cGUgZmllbGRcIik7XG5cbiAgICAgIHRoaXMucm9vdCA9IHRoaXMudHlwZVN5c3RlbS5yZWFkKFwicm9vdFwiLCBudWxsLCBcInJvb3RcIik7XG4gICAgICB0aGlzLnJvb3RcbiAgICAgICAgICAucHJvdG9jb2xEZXNjcmlwdGlvbigpXG4gICAgICAgICAgLmNhdGNoKHJlY292ZXJBY3RvckRlc2NyaXB0aW9ucylcbiAgICAgICAgICAudGhlbih0aGlzLnR5cGVTeXN0ZW0ucmVnaXN0ZXJUeXBlcy5iaW5kKHRoaXMudHlwZVN5c3RlbSkpXG4gICAgICAgICAgLnRoZW4odGhpcy5vblJlYWR5LmJpbmQodGhpcywgdGhpcy5yb290KSwgdGhpcy5vbkZhaWwpO1xuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgYWN0b3IgPSB0aGlzLmdldChwYWNrZXQuZnJvbSkgfHwgdGhpcy5yb290O1xuICAgICAgdmFyIGV2ZW50ID0gYWN0b3IuZXZlbnRzW3BhY2tldC50eXBlXTtcbiAgICAgIGlmIChldmVudCkge1xuICAgICAgICB2YXIgbWVzc2FnZSA9IG5ldyBNZXNzYWdlRXZlbnQocGFja2V0LnR5cGUsIHtcbiAgICAgICAgICBkYXRhOiBldmVudC5yZWFkKHBhY2tldClcbiAgICAgICAgfSk7XG4gICAgICAgIGFjdG9yLmRpc3BhdGNoRXZlbnQobWVzc2FnZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB2YXIgaW5kZXggPSB0aGlzLnJlcXVlc3RzLmluZGV4T2YoYWN0b3IuaWQpO1xuICAgICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICAgIHZhciByZXF1ZXN0ID0gdGhpcy5yZXF1ZXN0cy5zcGxpY2UoaW5kZXgsIDIpLnBvcCgpO1xuICAgICAgICAgIGlmIChwYWNrZXQuZXJyb3IpXG4gICAgICAgICAgICByZXF1ZXN0LnJlamVjdChwYWNrZXQpO1xuICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgIHJlcXVlc3QucmVzb2x2ZShwYWNrZXQpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbnNvbGUuZXJyb3IoRXJyb3IoXCJVbmV4cGVjdGVkIHBhY2tldCBcIiArIEpTT04uc3RyaW5naWZ5KHBhY2tldCwgMiwgMikpLFxuICAgICAgICAgICAgICAgICAgICAgICAgcGFja2V0LFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5yZXF1ZXN0cy5zbGljZSgwKSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH0sXG5cbiAgZ2V0OiBmdW5jdGlvbihpZCkge1xuICAgIHJldHVybiB0aGlzLmNhY2hlW2lkXTtcbiAgfSxcbiAgc3VwZXJ2aXNvck9mOiBmdW5jdGlvbihhY3Rvcikge1xuICAgIGZvciAodmFyIGlkIGluIHRoaXMuZ3JhcGgpIHtcbiAgICAgIGlmICh0aGlzLmdyYXBoW2lkXS5pbmRleE9mKGFjdG9yLmlkKSA+PSAwKSB7XG4gICAgICAgIHJldHVybiBpZDtcbiAgICAgIH1cbiAgICB9XG4gIH0sXG4gIHdvcmtlcnNPZjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICByZXR1cm4gdGhpcy5ncmFwaFthY3Rvci5pZF07XG4gIH0sXG4gIHN1cGVydmlzZTogZnVuY3Rpb24oYWN0b3IsIHdvcmtlcikge1xuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpXG4gICAgaWYgKHdvcmtlcnMuaW5kZXhPZih3b3JrZXIuaWQpIDwgMCkge1xuICAgICAgd29ya2Vycy5wdXNoKHdvcmtlci5pZCk7XG4gICAgfVxuICB9LFxuICB1bnN1cGVydmlzZTogZnVuY3Rpb24oYWN0b3IsIHdvcmtlcikge1xuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpO1xuICAgIHZhciBpbmRleCA9IHdvcmtlcnMuaW5kZXhPZih3b3JrZXIuaWQpXG4gICAgaWYgKGluZGV4ID49IDApIHtcbiAgICAgIHdvcmtlcnMuc3BsaWNlKGluZGV4LCAxKVxuICAgIH1cbiAgfSxcblxuICByZWdpc3RlcjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICB2YXIgcmVnaXN0ZXJlZCA9IHRoaXMuZ2V0KGFjdG9yLmlkKTtcbiAgICBpZiAoIXJlZ2lzdGVyZWQpIHtcbiAgICAgIHRoaXMuY2FjaGVbYWN0b3IuaWRdID0gYWN0b3I7XG4gICAgICB0aGlzLmdyYXBoW2FjdG9yLmlkXSA9IFtdO1xuICAgIH0gZWxzZSBpZiAocmVnaXN0ZXJlZCAhPT0gYWN0b3IpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIkRpZmZlcmVudCBhY3RvciB3aXRoIHNhbWUgaWQgaXMgYWxyZWFkeSByZWdpc3RlcmVkXCIpO1xuICAgIH1cbiAgfSxcbiAgdW5yZWdpc3RlcjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICBpZiAodGhpcy5nZXQoYWN0b3IuaWQpKSB7XG4gICAgICBkZWxldGUgdGhpcy5jYWNoZVthY3Rvci5pZF07XG4gICAgICBkZWxldGUgdGhpcy5ncmFwaFthY3Rvci5pZF07XG4gICAgfVxuICB9LFxuXG4gIHJlbGVhc2U6IGZ1bmN0aW9uKGFjdG9yKSB7XG4gICAgdmFyIHN1cGVydmlzb3IgPSB0aGlzLnN1cGVydmlzb3JPZihhY3Rvcik7XG4gICAgaWYgKHN1cGVydmlzb3IpXG4gICAgICB0aGlzLnVuc3VwZXJ2aXNlKHN1cGVydmlzb3IsIGFjdG9yKTtcblxuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpXG5cbiAgICBpZiAod29ya2Vycykge1xuICAgICAgd29ya2Vycy5tYXAodGhpcy5nZXQpLmZvckVhY2godGhpcy5yZWxlYXNlKVxuICAgIH1cbiAgICB0aGlzLnVucmVnaXN0ZXIoYWN0b3IpO1xuICB9XG59KTtcbmV4cG9ydHMuQ2xpZW50ID0gQ2xpZW50O1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnZhciBTeW1ib2wgPSByZXF1aXJlKFwiZXM2LXN5bWJvbFwiKVxudmFyIEV2ZW50RW1pdHRlciA9IHJlcXVpcmUoXCJldmVudHNcIikuRXZlbnRFbWl0dGVyO1xudmFyIENsYXNzID0gcmVxdWlyZShcIi4vY2xhc3NcIikuQ2xhc3M7XG5cbnZhciAkYm91bmQgPSBTeW1ib2woXCJFdmVudFRhcmdldC9oYW5kbGVFdmVudFwiKTtcbnZhciAkZW1pdHRlciA9IFN5bWJvbChcIkV2ZW50VGFyZ2V0L2VtaXR0ZXJcIik7XG5cbmZ1bmN0aW9uIG1ha2VIYW5kbGVyKGhhbmRsZXIpIHtcbiAgcmV0dXJuIGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgaGFuZGxlci5oYW5kbGVFdmVudChldmVudCk7XG4gIH1cbn1cblxudmFyIEV2ZW50VGFyZ2V0ID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsICRlbWl0dGVyLCB7XG4gICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgIHdyaXRhYmxlOiB0cnVlLFxuICAgICAgdmFsdWU6IG5ldyBFdmVudEVtaXR0ZXIoKVxuICAgIH0pO1xuICB9LFxuICBhZGRFdmVudExpc3RlbmVyOiBmdW5jdGlvbih0eXBlLCBoYW5kbGVyKSB7XG4gICAgaWYgKHR5cGVvZihoYW5kbGVyKSA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICB0aGlzWyRlbWl0dGVyXS5vbih0eXBlLCBoYW5kbGVyKTtcbiAgICB9XG4gICAgZWxzZSBpZiAoaGFuZGxlciAmJiB0eXBlb2YoaGFuZGxlcikgPT09IFwib2JqZWN0XCIpIHtcbiAgICAgIGlmICghaGFuZGxlclskYm91bmRdKSBoYW5kbGVyWyRib3VuZF0gPSBtYWtlSGFuZGxlcihoYW5kbGVyKTtcbiAgICAgIHRoaXNbJGVtaXR0ZXJdLm9uKHR5cGUsIGhhbmRsZXJbJGJvdW5kXSk7XG4gICAgfVxuICB9LFxuICByZW1vdmVFdmVudExpc3RlbmVyOiBmdW5jdGlvbih0eXBlLCBoYW5kbGVyKSB7XG4gICAgaWYgKHR5cGVvZihoYW5kbGVyKSA9PT0gXCJmdW5jdGlvblwiKVxuICAgICAgdGhpc1skZW1pdHRlcl0ucmVtb3ZlTGlzdGVuZXIodHlwZSwgaGFuZGxlcik7XG4gICAgZWxzZSBpZiAoaGFuZGxlciAmJiBoYW5kbGVyWyRib3VuZF0pXG4gICAgICB0aGlzWyRlbWl0dGVyXS5yZW1vdmVMaXN0ZW5lcih0eXBlLCBoYW5kbGVyWyRib3VuZF0pO1xuICB9LFxuICBkaXNwYXRjaEV2ZW50OiBmdW5jdGlvbihldmVudCkge1xuICAgIGV2ZW50LnRhcmdldCA9IHRoaXM7XG4gICAgdGhpc1skZW1pdHRlcl0uZW1pdChldmVudC50eXBlLCBldmVudCk7XG4gIH1cbn0pO1xuZXhwb3J0cy5FdmVudFRhcmdldCA9IEV2ZW50VGFyZ2V0O1xuXG52YXIgTWVzc2FnZUV2ZW50ID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24odHlwZSwgb3B0aW9ucykge1xuICAgIG9wdGlvbnMgPSBvcHRpb25zIHx8IHt9O1xuICAgIHRoaXMudHlwZSA9IHR5cGU7XG4gICAgdGhpcy5kYXRhID0gb3B0aW9ucy5kYXRhID09PSB2b2lkKDApID8gbnVsbCA6IG9wdGlvbnMuZGF0YTtcblxuICAgIHRoaXMubGFzdEV2ZW50SWQgPSBvcHRpb25zLmxhc3RFdmVudElkIHx8IFwiXCI7XG4gICAgdGhpcy5vcmlnaW4gPSBvcHRpb25zLm9yaWdpbiB8fCBcIlwiO1xuICAgIHRoaXMuYnViYmxlcyA9IG9wdGlvbnMuYnViYmxlcyB8fCBmYWxzZTtcbiAgICB0aGlzLmNhbmNlbGFibGUgPSBvcHRpb25zLmNhbmNlbGFibGUgfHwgZmFsc2U7XG4gIH0sXG4gIHNvdXJjZTogbnVsbCxcbiAgcG9ydHM6IG51bGwsXG4gIHByZXZlbnREZWZhdWx0OiBmdW5jdGlvbigpIHtcbiAgfSxcbiAgc3RvcFByb3BhZ2F0aW9uOiBmdW5jdGlvbigpIHtcbiAgfSxcbiAgc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uOiBmdW5jdGlvbigpIHtcbiAgfVxufSk7XG5leHBvcnRzLk1lc3NhZ2VFdmVudCA9IE1lc3NhZ2VFdmVudDtcbiIsIi8vIENvcHlyaWdodCBKb3llbnQsIEluYy4gYW5kIG90aGVyIE5vZGUgY29udHJpYnV0b3JzLlxuLy9cbi8vIFBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhXG4vLyBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlXG4vLyBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmdcbi8vIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCxcbi8vIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXRcbi8vIHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZVxuLy8gZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWRcbi8vIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1Ncbi8vIE9SIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0Zcbi8vIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU5cbi8vIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLFxuLy8gREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SXG4vLyBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFXG4vLyBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLlxuXG5mdW5jdGlvbiBFdmVudEVtaXR0ZXIoKSB7XG4gIHRoaXMuX2V2ZW50cyA9IHRoaXMuX2V2ZW50cyB8fCB7fTtcbiAgdGhpcy5fbWF4TGlzdGVuZXJzID0gdGhpcy5fbWF4TGlzdGVuZXJzIHx8IHVuZGVmaW5lZDtcbn1cbm1vZHVsZS5leHBvcnRzID0gRXZlbnRFbWl0dGVyO1xuXG4vLyBCYWNrd2FyZHMtY29tcGF0IHdpdGggbm9kZSAwLjEwLnhcbkV2ZW50RW1pdHRlci5FdmVudEVtaXR0ZXIgPSBFdmVudEVtaXR0ZXI7XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX2V2ZW50cyA9IHVuZGVmaW5lZDtcbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX21heExpc3RlbmVycyA9IHVuZGVmaW5lZDtcblxuLy8gQnkgZGVmYXVsdCBFdmVudEVtaXR0ZXJzIHdpbGwgcHJpbnQgYSB3YXJuaW5nIGlmIG1vcmUgdGhhbiAxMCBsaXN0ZW5lcnMgYXJlXG4vLyBhZGRlZCB0byBpdC4gVGhpcyBpcyBhIHVzZWZ1bCBkZWZhdWx0IHdoaWNoIGhlbHBzIGZpbmRpbmcgbWVtb3J5IGxlYWtzLlxuRXZlbnRFbWl0dGVyLmRlZmF1bHRNYXhMaXN0ZW5lcnMgPSAxMDtcblxuLy8gT2J2aW91c2x5IG5vdCBhbGwgRW1pdHRlcnMgc2hvdWxkIGJlIGxpbWl0ZWQgdG8gMTAuIFRoaXMgZnVuY3Rpb24gYWxsb3dzXG4vLyB0aGF0IHRvIGJlIGluY3JlYXNlZC4gU2V0IHRvIHplcm8gZm9yIHVubGltaXRlZC5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuc2V0TWF4TGlzdGVuZXJzID0gZnVuY3Rpb24obikge1xuICBpZiAoIWlzTnVtYmVyKG4pIHx8IG4gPCAwIHx8IGlzTmFOKG4pKVxuICAgIHRocm93IFR5cGVFcnJvcignbiBtdXN0IGJlIGEgcG9zaXRpdmUgbnVtYmVyJyk7XG4gIHRoaXMuX21heExpc3RlbmVycyA9IG47XG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5lbWl0ID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIgZXIsIGhhbmRsZXIsIGxlbiwgYXJncywgaSwgbGlzdGVuZXJzO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzKVxuICAgIHRoaXMuX2V2ZW50cyA9IHt9O1xuXG4gIC8vIElmIHRoZXJlIGlzIG5vICdlcnJvcicgZXZlbnQgbGlzdGVuZXIgdGhlbiB0aHJvdy5cbiAgaWYgKHR5cGUgPT09ICdlcnJvcicpIHtcbiAgICBpZiAoIXRoaXMuX2V2ZW50cy5lcnJvciB8fFxuICAgICAgICAoaXNPYmplY3QodGhpcy5fZXZlbnRzLmVycm9yKSAmJiAhdGhpcy5fZXZlbnRzLmVycm9yLmxlbmd0aCkpIHtcbiAgICAgIGVyID0gYXJndW1lbnRzWzFdO1xuICAgICAgaWYgKGVyIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgdGhyb3cgZXI7IC8vIFVuaGFuZGxlZCAnZXJyb3InIGV2ZW50XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aHJvdyBUeXBlRXJyb3IoJ1VuY2F1Z2h0LCB1bnNwZWNpZmllZCBcImVycm9yXCIgZXZlbnQuJyk7XG4gICAgICB9XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgaGFuZGxlciA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICBpZiAoaXNVbmRlZmluZWQoaGFuZGxlcikpXG4gICAgcmV0dXJuIGZhbHNlO1xuXG4gIGlmIChpc0Z1bmN0aW9uKGhhbmRsZXIpKSB7XG4gICAgc3dpdGNoIChhcmd1bWVudHMubGVuZ3RoKSB7XG4gICAgICAvLyBmYXN0IGNhc2VzXG4gICAgICBjYXNlIDE6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIDI6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzLCBhcmd1bWVudHNbMV0pO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgMzpcbiAgICAgICAgaGFuZGxlci5jYWxsKHRoaXMsIGFyZ3VtZW50c1sxXSwgYXJndW1lbnRzWzJdKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICAvLyBzbG93ZXJcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGxlbiA9IGFyZ3VtZW50cy5sZW5ndGg7XG4gICAgICAgIGFyZ3MgPSBuZXcgQXJyYXkobGVuIC0gMSk7XG4gICAgICAgIGZvciAoaSA9IDE7IGkgPCBsZW47IGkrKylcbiAgICAgICAgICBhcmdzW2kgLSAxXSA9IGFyZ3VtZW50c1tpXTtcbiAgICAgICAgaGFuZGxlci5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9XG4gIH0gZWxzZSBpZiAoaXNPYmplY3QoaGFuZGxlcikpIHtcbiAgICBsZW4gPSBhcmd1bWVudHMubGVuZ3RoO1xuICAgIGFyZ3MgPSBuZXcgQXJyYXkobGVuIC0gMSk7XG4gICAgZm9yIChpID0gMTsgaSA8IGxlbjsgaSsrKVxuICAgICAgYXJnc1tpIC0gMV0gPSBhcmd1bWVudHNbaV07XG5cbiAgICBsaXN0ZW5lcnMgPSBoYW5kbGVyLnNsaWNlKCk7XG4gICAgbGVuID0gbGlzdGVuZXJzLmxlbmd0aDtcbiAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspXG4gICAgICBsaXN0ZW5lcnNbaV0uYXBwbHkodGhpcywgYXJncyk7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuYWRkTGlzdGVuZXIgPSBmdW5jdGlvbih0eXBlLCBsaXN0ZW5lcikge1xuICB2YXIgbTtcblxuICBpZiAoIWlzRnVuY3Rpb24obGlzdGVuZXIpKVxuICAgIHRocm93IFR5cGVFcnJvcignbGlzdGVuZXIgbXVzdCBiZSBhIGZ1bmN0aW9uJyk7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHMpXG4gICAgdGhpcy5fZXZlbnRzID0ge307XG5cbiAgLy8gVG8gYXZvaWQgcmVjdXJzaW9uIGluIHRoZSBjYXNlIHRoYXQgdHlwZSA9PT0gXCJuZXdMaXN0ZW5lclwiISBCZWZvcmVcbiAgLy8gYWRkaW5nIGl0IHRvIHRoZSBsaXN0ZW5lcnMsIGZpcnN0IGVtaXQgXCJuZXdMaXN0ZW5lclwiLlxuICBpZiAodGhpcy5fZXZlbnRzLm5ld0xpc3RlbmVyKVxuICAgIHRoaXMuZW1pdCgnbmV3TGlzdGVuZXInLCB0eXBlLFxuICAgICAgICAgICAgICBpc0Z1bmN0aW9uKGxpc3RlbmVyLmxpc3RlbmVyKSA/XG4gICAgICAgICAgICAgIGxpc3RlbmVyLmxpc3RlbmVyIDogbGlzdGVuZXIpO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIC8vIE9wdGltaXplIHRoZSBjYXNlIG9mIG9uZSBsaXN0ZW5lci4gRG9uJ3QgbmVlZCB0aGUgZXh0cmEgYXJyYXkgb2JqZWN0LlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXSA9IGxpc3RlbmVyO1xuICBlbHNlIGlmIChpc09iamVjdCh0aGlzLl9ldmVudHNbdHlwZV0pKVxuICAgIC8vIElmIHdlJ3ZlIGFscmVhZHkgZ290IGFuIGFycmF5LCBqdXN0IGFwcGVuZC5cbiAgICB0aGlzLl9ldmVudHNbdHlwZV0ucHVzaChsaXN0ZW5lcik7XG4gIGVsc2VcbiAgICAvLyBBZGRpbmcgdGhlIHNlY29uZCBlbGVtZW50LCBuZWVkIHRvIGNoYW5nZSB0byBhcnJheS5cbiAgICB0aGlzLl9ldmVudHNbdHlwZV0gPSBbdGhpcy5fZXZlbnRzW3R5cGVdLCBsaXN0ZW5lcl07XG5cbiAgLy8gQ2hlY2sgZm9yIGxpc3RlbmVyIGxlYWtcbiAgaWYgKGlzT2JqZWN0KHRoaXMuX2V2ZW50c1t0eXBlXSkgJiYgIXRoaXMuX2V2ZW50c1t0eXBlXS53YXJuZWQpIHtcbiAgICB2YXIgbTtcbiAgICBpZiAoIWlzVW5kZWZpbmVkKHRoaXMuX21heExpc3RlbmVycykpIHtcbiAgICAgIG0gPSB0aGlzLl9tYXhMaXN0ZW5lcnM7XG4gICAgfSBlbHNlIHtcbiAgICAgIG0gPSBFdmVudEVtaXR0ZXIuZGVmYXVsdE1heExpc3RlbmVycztcbiAgICB9XG5cbiAgICBpZiAobSAmJiBtID4gMCAmJiB0aGlzLl9ldmVudHNbdHlwZV0ubGVuZ3RoID4gbSkge1xuICAgICAgdGhpcy5fZXZlbnRzW3R5cGVdLndhcm5lZCA9IHRydWU7XG4gICAgICBjb25zb2xlLmVycm9yKCcobm9kZSkgd2FybmluZzogcG9zc2libGUgRXZlbnRFbWl0dGVyIG1lbW9yeSAnICtcbiAgICAgICAgICAgICAgICAgICAgJ2xlYWsgZGV0ZWN0ZWQuICVkIGxpc3RlbmVycyBhZGRlZC4gJyArXG4gICAgICAgICAgICAgICAgICAgICdVc2UgZW1pdHRlci5zZXRNYXhMaXN0ZW5lcnMoKSB0byBpbmNyZWFzZSBsaW1pdC4nLFxuICAgICAgICAgICAgICAgICAgICB0aGlzLl9ldmVudHNbdHlwZV0ubGVuZ3RoKTtcbiAgICAgIGlmICh0eXBlb2YgY29uc29sZS50cmFjZSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAvLyBub3Qgc3VwcG9ydGVkIGluIElFIDEwXG4gICAgICAgIGNvbnNvbGUudHJhY2UoKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUub24gPSBFdmVudEVtaXR0ZXIucHJvdG90eXBlLmFkZExpc3RlbmVyO1xuXG5FdmVudEVtaXR0ZXIucHJvdG90eXBlLm9uY2UgPSBmdW5jdGlvbih0eXBlLCBsaXN0ZW5lcikge1xuICBpZiAoIWlzRnVuY3Rpb24obGlzdGVuZXIpKVxuICAgIHRocm93IFR5cGVFcnJvcignbGlzdGVuZXIgbXVzdCBiZSBhIGZ1bmN0aW9uJyk7XG5cbiAgdmFyIGZpcmVkID0gZmFsc2U7XG5cbiAgZnVuY3Rpb24gZygpIHtcbiAgICB0aGlzLnJlbW92ZUxpc3RlbmVyKHR5cGUsIGcpO1xuXG4gICAgaWYgKCFmaXJlZCkge1xuICAgICAgZmlyZWQgPSB0cnVlO1xuICAgICAgbGlzdGVuZXIuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICB9XG4gIH1cblxuICBnLmxpc3RlbmVyID0gbGlzdGVuZXI7XG4gIHRoaXMub24odHlwZSwgZyk7XG5cbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vLyBlbWl0cyBhICdyZW1vdmVMaXN0ZW5lcicgZXZlbnQgaWZmIHRoZSBsaXN0ZW5lciB3YXMgcmVtb3ZlZFxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVMaXN0ZW5lciA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIHZhciBsaXN0LCBwb3NpdGlvbiwgbGVuZ3RoLCBpO1xuXG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICBpZiAoIXRoaXMuX2V2ZW50cyB8fCAhdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIHJldHVybiB0aGlzO1xuXG4gIGxpc3QgPSB0aGlzLl9ldmVudHNbdHlwZV07XG4gIGxlbmd0aCA9IGxpc3QubGVuZ3RoO1xuICBwb3NpdGlvbiA9IC0xO1xuXG4gIGlmIChsaXN0ID09PSBsaXN0ZW5lciB8fFxuICAgICAgKGlzRnVuY3Rpb24obGlzdC5saXN0ZW5lcikgJiYgbGlzdC5saXN0ZW5lciA9PT0gbGlzdGVuZXIpKSB7XG4gICAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgICBpZiAodGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKVxuICAgICAgdGhpcy5lbWl0KCdyZW1vdmVMaXN0ZW5lcicsIHR5cGUsIGxpc3RlbmVyKTtcblxuICB9IGVsc2UgaWYgKGlzT2JqZWN0KGxpc3QpKSB7XG4gICAgZm9yIChpID0gbGVuZ3RoOyBpLS0gPiAwOykge1xuICAgICAgaWYgKGxpc3RbaV0gPT09IGxpc3RlbmVyIHx8XG4gICAgICAgICAgKGxpc3RbaV0ubGlzdGVuZXIgJiYgbGlzdFtpXS5saXN0ZW5lciA9PT0gbGlzdGVuZXIpKSB7XG4gICAgICAgIHBvc2l0aW9uID0gaTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHBvc2l0aW9uIDwgMClcbiAgICAgIHJldHVybiB0aGlzO1xuXG4gICAgaWYgKGxpc3QubGVuZ3RoID09PSAxKSB7XG4gICAgICBsaXN0Lmxlbmd0aCA9IDA7XG4gICAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIH0gZWxzZSB7XG4gICAgICBsaXN0LnNwbGljZShwb3NpdGlvbiwgMSk7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuX2V2ZW50cy5yZW1vdmVMaXN0ZW5lcilcbiAgICAgIHRoaXMuZW1pdCgncmVtb3ZlTGlzdGVuZXInLCB0eXBlLCBsaXN0ZW5lcik7XG4gIH1cblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUucmVtb3ZlQWxsTGlzdGVuZXJzID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIga2V5LCBsaXN0ZW5lcnM7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHMpXG4gICAgcmV0dXJuIHRoaXM7XG5cbiAgLy8gbm90IGxpc3RlbmluZyBmb3IgcmVtb3ZlTGlzdGVuZXIsIG5vIG5lZWQgdG8gZW1pdFxuICBpZiAoIXRoaXMuX2V2ZW50cy5yZW1vdmVMaXN0ZW5lcikge1xuICAgIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAwKVxuICAgICAgdGhpcy5fZXZlbnRzID0ge307XG4gICAgZWxzZSBpZiAodGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgICAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuXG4gIC8vIGVtaXQgcmVtb3ZlTGlzdGVuZXIgZm9yIGFsbCBsaXN0ZW5lcnMgb24gYWxsIGV2ZW50c1xuICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMCkge1xuICAgIGZvciAoa2V5IGluIHRoaXMuX2V2ZW50cykge1xuICAgICAgaWYgKGtleSA9PT0gJ3JlbW92ZUxpc3RlbmVyJykgY29udGludWU7XG4gICAgICB0aGlzLnJlbW92ZUFsbExpc3RlbmVycyhrZXkpO1xuICAgIH1cbiAgICB0aGlzLnJlbW92ZUFsbExpc3RlbmVycygncmVtb3ZlTGlzdGVuZXInKTtcbiAgICB0aGlzLl9ldmVudHMgPSB7fTtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuXG4gIGxpc3RlbmVycyA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICBpZiAoaXNGdW5jdGlvbihsaXN0ZW5lcnMpKSB7XG4gICAgdGhpcy5yZW1vdmVMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lcnMpO1xuICB9IGVsc2Uge1xuICAgIC8vIExJRk8gb3JkZXJcbiAgICB3aGlsZSAobGlzdGVuZXJzLmxlbmd0aClcbiAgICAgIHRoaXMucmVtb3ZlTGlzdGVuZXIodHlwZSwgbGlzdGVuZXJzW2xpc3RlbmVycy5sZW5ndGggLSAxXSk7XG4gIH1cbiAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUubGlzdGVuZXJzID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIgcmV0O1xuICBpZiAoIXRoaXMuX2V2ZW50cyB8fCAhdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIHJldCA9IFtdO1xuICBlbHNlIGlmIChpc0Z1bmN0aW9uKHRoaXMuX2V2ZW50c1t0eXBlXSkpXG4gICAgcmV0ID0gW3RoaXMuX2V2ZW50c1t0eXBlXV07XG4gIGVsc2VcbiAgICByZXQgPSB0aGlzLl9ldmVudHNbdHlwZV0uc2xpY2UoKTtcbiAgcmV0dXJuIHJldDtcbn07XG5cbkV2ZW50RW1pdHRlci5saXN0ZW5lckNvdW50ID0gZnVuY3Rpb24oZW1pdHRlciwgdHlwZSkge1xuICB2YXIgcmV0O1xuICBpZiAoIWVtaXR0ZXIuX2V2ZW50cyB8fCAhZW1pdHRlci5fZXZlbnRzW3R5cGVdKVxuICAgIHJldCA9IDA7XG4gIGVsc2UgaWYgKGlzRnVuY3Rpb24oZW1pdHRlci5fZXZlbnRzW3R5cGVdKSlcbiAgICByZXQgPSAxO1xuICBlbHNlXG4gICAgcmV0ID0gZW1pdHRlci5fZXZlbnRzW3R5cGVdLmxlbmd0aDtcbiAgcmV0dXJuIHJldDtcbn07XG5cbmZ1bmN0aW9uIGlzRnVuY3Rpb24oYXJnKSB7XG4gIHJldHVybiB0eXBlb2YgYXJnID09PSAnZnVuY3Rpb24nO1xufVxuXG5mdW5jdGlvbiBpc051bWJlcihhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdudW1iZXInO1xufVxuXG5mdW5jdGlvbiBpc09iamVjdChhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdvYmplY3QnICYmIGFyZyAhPT0gbnVsbDtcbn1cblxuZnVuY3Rpb24gaXNVbmRlZmluZWQoYXJnKSB7XG4gIHJldHVybiBhcmcgPT09IHZvaWQgMDtcbn1cbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKSA/IFN5bWJvbCA6IHJlcXVpcmUoJy4vcG9seWZpbGwnKTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoKSB7XG5cdHZhciBzeW1ib2w7XG5cdGlmICh0eXBlb2YgU3ltYm9sICE9PSAnZnVuY3Rpb24nKSByZXR1cm4gZmFsc2U7XG5cdHN5bWJvbCA9IFN5bWJvbCgndGVzdCBzeW1ib2wnKTtcblx0dHJ5IHtcblx0XHRpZiAoU3RyaW5nKHN5bWJvbCkgIT09ICdTeW1ib2wgKHRlc3Qgc3ltYm9sKScpIHJldHVybiBmYWxzZTtcblx0fSBjYXRjaCAoZSkgeyByZXR1cm4gZmFsc2U7IH1cblx0aWYgKHR5cGVvZiBTeW1ib2wuaXRlcmF0b3IgPT09ICdzeW1ib2wnKSByZXR1cm4gdHJ1ZTtcblxuXHQvLyBSZXR1cm4gJ3RydWUnIGZvciBwb2x5ZmlsbHNcblx0aWYgKHR5cGVvZiBTeW1ib2wuaXNDb25jYXRTcHJlYWRhYmxlICE9PSAnb2JqZWN0JykgcmV0dXJuIGZhbHNlO1xuXHRpZiAodHlwZW9mIFN5bWJvbC5pc1JlZ0V4cCAhPT0gJ29iamVjdCcpIHJldHVybiBmYWxzZTtcblx0aWYgKHR5cGVvZiBTeW1ib2wuaXRlcmF0b3IgIT09ICdvYmplY3QnKSByZXR1cm4gZmFsc2U7XG5cdGlmICh0eXBlb2YgU3ltYm9sLnRvUHJpbWl0aXZlICE9PSAnb2JqZWN0JykgcmV0dXJuIGZhbHNlO1xuXHRpZiAodHlwZW9mIFN5bWJvbC50b1N0cmluZ1RhZyAhPT0gJ29iamVjdCcpIHJldHVybiBmYWxzZTtcblx0aWYgKHR5cGVvZiBTeW1ib2wudW5zY29wYWJsZXMgIT09ICdvYmplY3QnKSByZXR1cm4gZmFsc2U7XG5cblx0cmV0dXJuIHRydWU7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgYXNzaWduICAgICAgICA9IHJlcXVpcmUoJ2VzNS1leHQvb2JqZWN0L2Fzc2lnbicpXG4gICwgbm9ybWFsaXplT3B0cyA9IHJlcXVpcmUoJ2VzNS1leHQvb2JqZWN0L25vcm1hbGl6ZS1vcHRpb25zJylcbiAgLCBpc0NhbGxhYmxlICAgID0gcmVxdWlyZSgnZXM1LWV4dC9vYmplY3QvaXMtY2FsbGFibGUnKVxuICAsIGNvbnRhaW5zICAgICAgPSByZXF1aXJlKCdlczUtZXh0L3N0cmluZy8jL2NvbnRhaW5zJylcblxuICAsIGQ7XG5cbmQgPSBtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChkc2NyLCB2YWx1ZS8qLCBvcHRpb25zKi8pIHtcblx0dmFyIGMsIGUsIHcsIG9wdGlvbnMsIGRlc2M7XG5cdGlmICgoYXJndW1lbnRzLmxlbmd0aCA8IDIpIHx8ICh0eXBlb2YgZHNjciAhPT0gJ3N0cmluZycpKSB7XG5cdFx0b3B0aW9ucyA9IHZhbHVlO1xuXHRcdHZhbHVlID0gZHNjcjtcblx0XHRkc2NyID0gbnVsbDtcblx0fSBlbHNlIHtcblx0XHRvcHRpb25zID0gYXJndW1lbnRzWzJdO1xuXHR9XG5cdGlmIChkc2NyID09IG51bGwpIHtcblx0XHRjID0gdyA9IHRydWU7XG5cdFx0ZSA9IGZhbHNlO1xuXHR9IGVsc2Uge1xuXHRcdGMgPSBjb250YWlucy5jYWxsKGRzY3IsICdjJyk7XG5cdFx0ZSA9IGNvbnRhaW5zLmNhbGwoZHNjciwgJ2UnKTtcblx0XHR3ID0gY29udGFpbnMuY2FsbChkc2NyLCAndycpO1xuXHR9XG5cblx0ZGVzYyA9IHsgdmFsdWU6IHZhbHVlLCBjb25maWd1cmFibGU6IGMsIGVudW1lcmFibGU6IGUsIHdyaXRhYmxlOiB3IH07XG5cdHJldHVybiAhb3B0aW9ucyA/IGRlc2MgOiBhc3NpZ24obm9ybWFsaXplT3B0cyhvcHRpb25zKSwgZGVzYyk7XG59O1xuXG5kLmdzID0gZnVuY3Rpb24gKGRzY3IsIGdldCwgc2V0LyosIG9wdGlvbnMqLykge1xuXHR2YXIgYywgZSwgb3B0aW9ucywgZGVzYztcblx0aWYgKHR5cGVvZiBkc2NyICE9PSAnc3RyaW5nJykge1xuXHRcdG9wdGlvbnMgPSBzZXQ7XG5cdFx0c2V0ID0gZ2V0O1xuXHRcdGdldCA9IGRzY3I7XG5cdFx0ZHNjciA9IG51bGw7XG5cdH0gZWxzZSB7XG5cdFx0b3B0aW9ucyA9IGFyZ3VtZW50c1szXTtcblx0fVxuXHRpZiAoZ2V0ID09IG51bGwpIHtcblx0XHRnZXQgPSB1bmRlZmluZWQ7XG5cdH0gZWxzZSBpZiAoIWlzQ2FsbGFibGUoZ2V0KSkge1xuXHRcdG9wdGlvbnMgPSBnZXQ7XG5cdFx0Z2V0ID0gc2V0ID0gdW5kZWZpbmVkO1xuXHR9IGVsc2UgaWYgKHNldCA9PSBudWxsKSB7XG5cdFx0c2V0ID0gdW5kZWZpbmVkO1xuXHR9IGVsc2UgaWYgKCFpc0NhbGxhYmxlKHNldCkpIHtcblx0XHRvcHRpb25zID0gc2V0O1xuXHRcdHNldCA9IHVuZGVmaW5lZDtcblx0fVxuXHRpZiAoZHNjciA9PSBudWxsKSB7XG5cdFx0YyA9IHRydWU7XG5cdFx0ZSA9IGZhbHNlO1xuXHR9IGVsc2Uge1xuXHRcdGMgPSBjb250YWlucy5jYWxsKGRzY3IsICdjJyk7XG5cdFx0ZSA9IGNvbnRhaW5zLmNhbGwoZHNjciwgJ2UnKTtcblx0fVxuXG5cdGRlc2MgPSB7IGdldDogZ2V0LCBzZXQ6IHNldCwgY29uZmlndXJhYmxlOiBjLCBlbnVtZXJhYmxlOiBlIH07XG5cdHJldHVybiAhb3B0aW9ucyA/IGRlc2MgOiBhc3NpZ24obm9ybWFsaXplT3B0cyhvcHRpb25zKSwgZGVzYyk7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoJy4vaXMtaW1wbGVtZW50ZWQnKSgpXG5cdD8gT2JqZWN0LmFzc2lnblxuXHQ6IHJlcXVpcmUoJy4vc2hpbScpO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICgpIHtcblx0dmFyIGFzc2lnbiA9IE9iamVjdC5hc3NpZ24sIG9iajtcblx0aWYgKHR5cGVvZiBhc3NpZ24gIT09ICdmdW5jdGlvbicpIHJldHVybiBmYWxzZTtcblx0b2JqID0geyBmb286ICdyYXonIH07XG5cdGFzc2lnbihvYmosIHsgYmFyOiAnZHdhJyB9LCB7IHRyenk6ICd0cnp5JyB9KTtcblx0cmV0dXJuIChvYmouZm9vICsgb2JqLmJhciArIG9iai50cnp5KSA9PT0gJ3JhemR3YXRyenknO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGtleXMgID0gcmVxdWlyZSgnLi4va2V5cycpXG4gICwgdmFsdWUgPSByZXF1aXJlKCcuLi92YWxpZC12YWx1ZScpXG5cbiAgLCBtYXggPSBNYXRoLm1heDtcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoZGVzdCwgc3JjLyosIOKApnNyY24qLykge1xuXHR2YXIgZXJyb3IsIGksIGwgPSBtYXgoYXJndW1lbnRzLmxlbmd0aCwgMiksIGFzc2lnbjtcblx0ZGVzdCA9IE9iamVjdCh2YWx1ZShkZXN0KSk7XG5cdGFzc2lnbiA9IGZ1bmN0aW9uIChrZXkpIHtcblx0XHR0cnkgeyBkZXN0W2tleV0gPSBzcmNba2V5XTsgfSBjYXRjaCAoZSkge1xuXHRcdFx0aWYgKCFlcnJvcikgZXJyb3IgPSBlO1xuXHRcdH1cblx0fTtcblx0Zm9yIChpID0gMTsgaSA8IGw7ICsraSkge1xuXHRcdHNyYyA9IGFyZ3VtZW50c1tpXTtcblx0XHRrZXlzKHNyYykuZm9yRWFjaChhc3NpZ24pO1xuXHR9XG5cdGlmIChlcnJvciAhPT0gdW5kZWZpbmVkKSB0aHJvdyBlcnJvcjtcblx0cmV0dXJuIGRlc3Q7XG59O1xuIiwiLy8gRGVwcmVjYXRlZFxuXG4ndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9iaikgeyByZXR1cm4gdHlwZW9mIG9iaiA9PT0gJ2Z1bmN0aW9uJzsgfTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKVxuXHQ/IE9iamVjdC5rZXlzXG5cdDogcmVxdWlyZSgnLi9zaGltJyk7XG4iLCIndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKCkge1xuXHR0cnkge1xuXHRcdE9iamVjdC5rZXlzKCdwcmltaXRpdmUnKTtcblx0XHRyZXR1cm4gdHJ1ZTtcblx0fSBjYXRjaCAoZSkgeyByZXR1cm4gZmFsc2U7IH1cbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBrZXlzID0gT2JqZWN0LmtleXM7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9iamVjdCkge1xuXHRyZXR1cm4ga2V5cyhvYmplY3QgPT0gbnVsbCA/IG9iamVjdCA6IE9iamVjdChvYmplY3QpKTtcbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBhc3NpZ24gPSByZXF1aXJlKCcuL2Fzc2lnbicpXG5cbiAgLCBmb3JFYWNoID0gQXJyYXkucHJvdG90eXBlLmZvckVhY2hcbiAgLCBjcmVhdGUgPSBPYmplY3QuY3JlYXRlLCBnZXRQcm90b3R5cGVPZiA9IE9iamVjdC5nZXRQcm90b3R5cGVPZlxuXG4gICwgcHJvY2VzcztcblxucHJvY2VzcyA9IGZ1bmN0aW9uIChzcmMsIG9iaikge1xuXHR2YXIgcHJvdG8gPSBnZXRQcm90b3R5cGVPZihzcmMpO1xuXHRyZXR1cm4gYXNzaWduKHByb3RvID8gcHJvY2Vzcyhwcm90bywgb2JqKSA6IG9iaiwgc3JjKTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9wdGlvbnMvKiwg4oCmb3B0aW9ucyovKSB7XG5cdHZhciByZXN1bHQgPSBjcmVhdGUobnVsbCk7XG5cdGZvckVhY2guY2FsbChhcmd1bWVudHMsIGZ1bmN0aW9uIChvcHRpb25zKSB7XG5cdFx0aWYgKG9wdGlvbnMgPT0gbnVsbCkgcmV0dXJuO1xuXHRcdHByb2Nlc3MoT2JqZWN0KG9wdGlvbnMpLCByZXN1bHQpO1xuXHR9KTtcblx0cmV0dXJuIHJlc3VsdDtcbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKHZhbHVlKSB7XG5cdGlmICh2YWx1ZSA9PSBudWxsKSB0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ2Fubm90IHVzZSBudWxsIG9yIHVuZGVmaW5lZFwiKTtcblx0cmV0dXJuIHZhbHVlO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKVxuXHQ/IFN0cmluZy5wcm90b3R5cGUuY29udGFpbnNcblx0OiByZXF1aXJlKCcuL3NoaW0nKTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIHN0ciA9ICdyYXpkd2F0cnp5JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoKSB7XG5cdGlmICh0eXBlb2Ygc3RyLmNvbnRhaW5zICE9PSAnZnVuY3Rpb24nKSByZXR1cm4gZmFsc2U7XG5cdHJldHVybiAoKHN0ci5jb250YWlucygnZHdhJykgPT09IHRydWUpICYmIChzdHIuY29udGFpbnMoJ2ZvbycpID09PSBmYWxzZSkpO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGluZGV4T2YgPSBTdHJpbmcucHJvdG90eXBlLmluZGV4T2Y7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKHNlYXJjaFN0cmluZy8qLCBwb3NpdGlvbiovKSB7XG5cdHJldHVybiBpbmRleE9mLmNhbGwodGhpcywgc2VhcmNoU3RyaW5nLCBhcmd1bWVudHNbMV0pID4gLTE7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgZCA9IHJlcXVpcmUoJ2QnKVxuXG4gICwgY3JlYXRlID0gT2JqZWN0LmNyZWF0ZSwgZGVmaW5lUHJvcGVydGllcyA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzXG4gICwgZ2VuZXJhdGVOYW1lLCBTeW1ib2w7XG5cbmdlbmVyYXRlTmFtZSA9IChmdW5jdGlvbiAoKSB7XG5cdHZhciBjcmVhdGVkID0gY3JlYXRlKG51bGwpO1xuXHRyZXR1cm4gZnVuY3Rpb24gKGRlc2MpIHtcblx0XHR2YXIgcG9zdGZpeCA9IDA7XG5cdFx0d2hpbGUgKGNyZWF0ZWRbZGVzYyArIChwb3N0Zml4IHx8ICcnKV0pICsrcG9zdGZpeDtcblx0XHRkZXNjICs9IChwb3N0Zml4IHx8ICcnKTtcblx0XHRjcmVhdGVkW2Rlc2NdID0gdHJ1ZTtcblx0XHRyZXR1cm4gJ0BAJyArIGRlc2M7XG5cdH07XG59KCkpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IFN5bWJvbCA9IGZ1bmN0aW9uIChkZXNjcmlwdGlvbikge1xuXHR2YXIgc3ltYm9sO1xuXHRpZiAodGhpcyBpbnN0YW5jZW9mIFN5bWJvbCkge1xuXHRcdHRocm93IG5ldyBUeXBlRXJyb3IoJ1R5cGVFcnJvcjogU3ltYm9sIGlzIG5vdCBhIGNvbnN0cnVjdG9yJyk7XG5cdH1cblx0c3ltYm9sID0gY3JlYXRlKFN5bWJvbC5wcm90b3R5cGUpO1xuXHRkZXNjcmlwdGlvbiA9IChkZXNjcmlwdGlvbiA9PT0gdW5kZWZpbmVkID8gJycgOiBTdHJpbmcoZGVzY3JpcHRpb24pKTtcblx0cmV0dXJuIGRlZmluZVByb3BlcnRpZXMoc3ltYm9sLCB7XG5cdFx0X19kZXNjcmlwdGlvbl9fOiBkKCcnLCBkZXNjcmlwdGlvbiksXG5cdFx0X19uYW1lX186IGQoJycsIGdlbmVyYXRlTmFtZShkZXNjcmlwdGlvbikpXG5cdH0pO1xufTtcblxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoU3ltYm9sLCB7XG5cdGNyZWF0ZTogZCgnJywgU3ltYm9sKCdjcmVhdGUnKSksXG5cdGhhc0luc3RhbmNlOiBkKCcnLCBTeW1ib2woJ2hhc0luc3RhbmNlJykpLFxuXHRpc0NvbmNhdFNwcmVhZGFibGU6IGQoJycsIFN5bWJvbCgnaXNDb25jYXRTcHJlYWRhYmxlJykpLFxuXHRpc1JlZ0V4cDogZCgnJywgU3ltYm9sKCdpc1JlZ0V4cCcpKSxcblx0aXRlcmF0b3I6IGQoJycsIFN5bWJvbCgnaXRlcmF0b3InKSksXG5cdHRvUHJpbWl0aXZlOiBkKCcnLCBTeW1ib2woJ3RvUHJpbWl0aXZlJykpLFxuXHR0b1N0cmluZ1RhZzogZCgnJywgU3ltYm9sKCd0b1N0cmluZ1RhZycpKSxcblx0dW5zY29wYWJsZXM6IGQoJycsIFN5bWJvbCgndW5zY29wYWJsZXMnKSlcbn0pO1xuXG5kZWZpbmVQcm9wZXJ0aWVzKFN5bWJvbC5wcm90b3R5cGUsIHtcblx0cHJvcGVyVG9TdHJpbmc6IGQoZnVuY3Rpb24gKCkge1xuXHRcdHJldHVybiAnU3ltYm9sICgnICsgdGhpcy5fX2Rlc2NyaXB0aW9uX18gKyAnKSc7XG5cdH0pLFxuXHR0b1N0cmluZzogZCgnJywgZnVuY3Rpb24gKCkgeyByZXR1cm4gdGhpcy5fX25hbWVfXzsgfSlcbn0pO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KFN5bWJvbC5wcm90b3R5cGUsIFN5bWJvbC50b1ByaW1pdGl2ZSwgZCgnJyxcblx0ZnVuY3Rpb24gKGhpbnQpIHtcblx0XHR0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ29udmVyc2lvbiBvZiBzeW1ib2wgb2JqZWN0cyBpcyBub3QgYWxsb3dlZFwiKTtcblx0fSkpO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KFN5bWJvbC5wcm90b3R5cGUsIFN5bWJvbC50b1N0cmluZ1RhZywgZCgnYycsICdTeW1ib2wnKSk7XG4iLCJtb2R1bGUuZXhwb3J0cz17XG4gIFwidHlwZXNcIjoge1xuICAgIFwicm9vdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJyb290XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZWNob1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInN0cmluZ1wiOiB7IFwiX2FyZ1wiOiAwLCBcInR5cGVcIjogXCJzdHJpbmdcIiB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwic3RyaW5nXCI6IHsgXCJfcmV0dmFsXCI6IFwic3RyaW5nXCIgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImxpc3RUYWJzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHt9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjogeyBcIl9yZXR2YWxcIjogXCJ0YWJsaXN0XCIgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicHJvdG9jb2xEZXNjcmlwdGlvblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7fSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHsgXCJfcmV0dmFsXCI6IFwianNvblwiIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJ0YWJMaXN0Q2hhbmdlZFwiOiB7fVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJ0YWJsaXN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwidGFibGlzdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInNlbGVjdGVkXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwidGFic1wiOiBcImFycmF5OnRhYlwiLFxuICAgICAgICBcInVybFwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImNvbnNvbGVBY3RvclwiOiBcImNvbnNvbGVcIixcbiAgICAgICAgXCJpbnNwZWN0b3JBY3RvclwiOiBcImluc3BlY3RvclwiLFxuICAgICAgICBcInN0eWxlU2hlZXRzQWN0b3JcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgICBcInN0eWxlRWRpdG9yQWN0b3JcIjogXCJzdHlsZWVkaXRvclwiLFxuICAgICAgICBcIm1lbW9yeUFjdG9yXCI6IFwibWVtb3J5XCIsXG4gICAgICAgIFwiZXZlbnRMb29wTGFnQWN0b3JcIjogXCJldmVudExvb3BMYWdcIixcbiAgICAgICAgXCJwcmVmZXJlbmNlQWN0b3JcIjogXCJwcmVmZXJlbmNlXCIsXG4gICAgICAgIFwiZGV2aWNlQWN0b3JcIjogXCJkZXZpY2VcIixcblxuICAgICAgICBcInByb2ZpbGVyQWN0b3JcIjogXCJwcm9maWxlclwiLFxuICAgICAgICBcImNocm9tZURlYnVnZ2VyXCI6IFwiY2hyb21lRGVidWdnZXJcIixcbiAgICAgICAgXCJ3ZWJhcHBzQWN0b3JcIjogXCJ3ZWJhcHBzXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwidGFiXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInRhYlwiLFxuICAgICAgXCJmaWVsZHNcIjoge1xuICAgICAgICBcInRpdGxlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidXJsXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwib3V0ZXJXaW5kb3dJRFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImluc3BlY3RvckFjdG9yXCI6IFwiaW5zcGVjdG9yXCIsXG4gICAgICAgIFwiY2FsbFdhdGNoZXJBY3RvclwiOiBcImNhbGwtd2F0Y2hlclwiLFxuICAgICAgICBcImNhbnZhc0FjdG9yXCI6IFwiY2FudmFzXCIsXG4gICAgICAgIFwid2ViZ2xBY3RvclwiOiBcIndlYmdsXCIsXG4gICAgICAgIFwid2ViYXVkaW9BY3RvclwiOiBcIndlYmF1ZGlvXCIsXG4gICAgICAgIFwic3RvcmFnZUFjdG9yXCI6IFwic3RvcmFnZVwiLFxuICAgICAgICBcImdjbGlBY3RvclwiOiBcImdjbGlcIixcbiAgICAgICAgXCJtZW1vcnlBY3RvclwiOiBcIm1lbW9yeVwiLFxuICAgICAgICBcImV2ZW50TG9vcExhZ1wiOiBcImV2ZW50TG9vcExhZ1wiLFxuICAgICAgICBcInN0eWxlU2hlZXRzQWN0b3JcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgICBcInN0eWxlRWRpdG9yQWN0b3JcIjogXCJzdHlsZWVkaXRvclwiLFxuXG4gICAgICAgIFwiY29uc29sZUFjdG9yXCI6IFwiY29uc29sZVwiLFxuICAgICAgICBcInRyYWNlQWN0b3JcIjogXCJ0cmFjZVwiXG4gICAgICB9LFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJhdHRhY2hcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge30sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7IFwiX3JldHZhbFwiOiBcImpzb25cIiB9XG4gICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcInRhYk5hdmlnYXRlZFwiOiB7XG4gICAgICAgICAgIFwidHlwZU5hbWVcIjogXCJ0YWJOYXZpZ2F0ZWRcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImNvbnNvbGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiY29uc29sZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImV2YWx1YXRlSlNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ1cmxcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImJpbmRPYmplY3RBY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwiZnJhbWVBY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwic2VsZWN0ZWROb2RlQWN0b3JcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6c3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZXZhbHVhdGVqc3Jlc3BvbnNlXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJldmFsdWF0ZWpzcmVzcG9uc2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJldmFsdWF0ZWpzcmVzcG9uc2VcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJyZXN1bHRcIjogXCJvYmplY3RcIixcbiAgICAgICAgXCJleGNlcHRpb25cIjogXCJvYmplY3RcIixcbiAgICAgICAgXCJleGNlcHRpb25NZXNzYWdlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwiaW5wdXRcIjogXCJzdHJpbmdcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJvYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwib2JqZWN0XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICAge1xuICAgICAgICAgICBcIm5hbWVcIjogXCJwcm9wZXJ0eVwiLFxuICAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgICBcIm5hbWVcIjoge1xuICAgICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgfSxcbiAgICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICAgIFwiZGVzY3JpcHRvclwiOiB7XG4gICAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgfVxuICAgICAgICAgfVxuICAgICAgXVxuICAgIH1cbiAgfVxufVxuIiwibW9kdWxlLmV4cG9ydHM9e1xuICBcInR5cGVzXCI6IHtcbiAgICBcImxvbmdzdHJhY3RvclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJsb25nc3RyYWN0b3JcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzdWJzdHJpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3Vic3RyaW5nXCIsXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJlbmRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdWJzdHJpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInJlbGVhc2VcIixcbiAgICAgICAgICBcInJlbGVhc2VcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVsZWFzZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJzdHlsZXNoZWV0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0eWxlc2hlZXRcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0b2dnbGVEaXNhYmxlZFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ0b2dnbGVEaXNhYmxlZFwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZGlzYWJsZWRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRPcmlnaW5hbFNvdXJjZXNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0T3JpZ2luYWxTb3VyY2VzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJvcmlnaW5hbFNvdXJjZXNcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTphcnJheTpvcmlnaW5hbHNvdXJjZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0T3JpZ2luYWxMb2NhdGlvblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRPcmlnaW5hbExvY2F0aW9uXCIsXG4gICAgICAgICAgICBcImxpbmVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVtYmVyXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNvbHVtblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJvcmlnaW5hbGxvY2F0aW9ucmVzcG9uc2VcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVwZGF0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ1cGRhdGVcIixcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidHJhbnNpdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJwcm9wZXJ0eS1jaGFuZ2VcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInByb3BlcnR5Q2hhbmdlXCIsXG4gICAgICAgICAgXCJwcm9wZXJ0eVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcInN0eWxlLWFwcGxpZWRcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInN0eWxlQXBwbGllZFwiXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIFwib3JpZ2luYWxzb3VyY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwib3JpZ2luYWxzb3VyY2VcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJzdHlsZXNoZWV0c1wiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFN0eWxlU2hlZXRzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFN0eWxlU2hlZXRzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdHlsZVNoZWV0c1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OnN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImFkZFN0eWxlU2hlZXRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYWRkU3R5bGVTaGVldFwiLFxuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwic3R5bGVTaGVldFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcIm9yaWdpbmFsbG9jYXRpb25yZXNwb25zZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcIm9yaWdpbmFsbG9jYXRpb25yZXNwb25zZVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInNvdXJjZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImxpbmVcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJjb2x1bW5cIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJkb21ub2RlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRvbW5vZGVcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXROb2RlVmFsdWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0Tm9kZVZhbHVlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldE5vZGVWYWx1ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXROb2RlVmFsdWVcIixcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEltYWdlRGF0YVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRJbWFnZURhdGFcIixcbiAgICAgICAgICAgIFwibWF4RGltXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOm51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImltYWdlRGF0YVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibW9kaWZ5QXR0cmlidXRlc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtb2RpZnlBdHRyaWJ1dGVzXCIsXG4gICAgICAgICAgICBcIm1vZGlmaWNhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJhcHBsaWVkc3R5bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJhcHBsaWVkc3R5bGVcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJydWxlXCI6IFwiZG9tc3R5bGVydWxlI2FjdG9yaWRcIixcbiAgICAgICAgXCJpbmhlcml0ZWRcIjogXCJudWxsYWJsZTpkb21ub2RlI2FjdG9yaWRcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJtYXRjaGVkc2VsZWN0b3JcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJtYXRjaGVkc2VsZWN0b3JcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJydWxlXCI6IFwiZG9tc3R5bGVydWxlI2FjdG9yaWRcIixcbiAgICAgICAgXCJzZWxlY3RvclwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcInZhbHVlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwic3RhdHVzXCI6IFwibnVtYmVyXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwibWF0Y2hlZHNlbGVjdG9ycmVzcG9uc2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJtYXRjaGVkc2VsZWN0b3JyZXNwb25zZVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInJ1bGVzXCI6IFwiYXJyYXk6ZG9tc3R5bGVydWxlXCIsXG4gICAgICAgIFwic2hlZXRzXCI6IFwiYXJyYXk6c3R5bGVzaGVldFwiLFxuICAgICAgICBcIm1hdGNoZWRcIjogXCJhcnJheTptYXRjaGVkc2VsZWN0b3JcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJhcHBsaWVkU3R5bGVzUmV0dXJuXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiYXBwbGllZFN0eWxlc1JldHVyblwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcImVudHJpZXNcIjogXCJhcnJheTphcHBsaWVkc3R5bGVcIixcbiAgICAgICAgXCJydWxlc1wiOiBcImFycmF5OmRvbXN0eWxlcnVsZVwiLFxuICAgICAgICBcInNoZWV0c1wiOiBcImFycmF5OnN0eWxlc2hlZXRcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJwYWdlc3R5bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwicGFnZXN0eWxlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0Q29tcHV0ZWRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0Q29tcHV0ZWRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm1hcmtNYXRjaGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwib25seU1hdGNoZWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJmaWx0ZXJcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJjb21wdXRlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldE1hdGNoZWRTZWxlY3RvcnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0TWF0Y2hlZFNlbGVjdG9yc1wiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicHJvcGVydHlcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImZpbHRlclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJtYXRjaGVkc2VsZWN0b3JyZXNwb25zZVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0QXBwbGllZFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRBcHBsaWVkXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJpbmhlcml0ZWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJtYXRjaGVkU2VsZWN0b3JzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwiZmlsdGVyXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFwcGxpZWRTdHlsZXNSZXR1cm5cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldExheW91dFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRMYXlvdXRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImF1dG9NYXJnaW5zXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJkb21zdHlsZXJ1bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZG9tc3R5bGVydWxlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibW9kaWZ5UHJvcGVydGllc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtb2RpZnlQcm9wZXJ0aWVzXCIsXG4gICAgICAgICAgICBcIm1vZGlmaWNhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwicnVsZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbXN0eWxlcnVsZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiaGlnaGxpZ2h0ZXJcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiaGlnaGxpZ2h0ZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzaG93Qm94TW9kZWxcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2hvd0JveE1vZGVsXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJyZWdpb25cIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZGVCb3hNb2RlbFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJoaWRlQm94TW9kZWxcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNhbmNlbFBpY2tcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY2FuY2VsUGlja1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJpbWFnZURhdGFcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbWFnZURhdGFcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJkYXRhXCI6IFwibnVsbGFibGU6bG9uZ3N0cmluZ1wiLFxuICAgICAgICBcInNpemVcIjogXCJqc29uXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZGlzY29ubmVjdGVkTm9kZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJub2RlXCI6IFwiZG9tbm9kZVwiLFxuICAgICAgICBcIm5ld1BhcmVudHNcIjogXCJhcnJheTpkb21ub2RlXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwibm9kZXNcIjogXCJhcnJheTpkb21ub2RlXCIsXG4gICAgICAgIFwibmV3UGFyZW50c1wiOiBcImFycmF5OmRvbW5vZGVcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJkb21tdXRhdGlvblwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRvbW11dGF0aW9uXCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7fVxuICAgIH0sXG4gICAgXCJkb21ub2RlbGlzdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb21ub2RlbGlzdFwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIml0ZW1cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXRlbVwiLFxuICAgICAgICAgICAgXCJpdGVtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIml0ZW1zXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIml0ZW1zXCIsXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOm51bWJlclwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJlbmRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6bnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZWxlYXNlXCIsXG4gICAgICAgICAgXCJyZWxlYXNlXCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbGVhc2VcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiZG9tdHJhdmVyc2FsYXJyYXlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb210cmF2ZXJzYWxhcnJheVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcIm5vZGVzXCI6IFwiYXJyYXk6ZG9tbm9kZVwiXG4gICAgICB9XG4gICAgfSxcbiAgICBcImRvbXdhbGtlclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb213YWxrZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZWxlYXNlXCIsXG4gICAgICAgICAgXCJyZWxlYXNlXCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbGVhc2VcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZGlzY29ubmVjdGVkTm9kZVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiY2FuY2VsUGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjYW5jZWxQaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJoaWdobGlnaHRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImRvY3VtZW50XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRvY3VtZW50XCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImRvY3VtZW50RWxlbWVudFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJkb2N1bWVudEVsZW1lbnRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGFyZW50c1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwYXJlbnRzXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzYW1lRG9jdW1lbnRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2Rlc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInJldGFpbk5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmV0YWluTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidW5yZXRhaW5Ob2RlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVucmV0YWluTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVsZWFzZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVsZWFzZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImZvcmNlXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJjaGlsZHJlblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjaGlsZHJlblwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwibWF4Tm9kZXNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNlbnRlclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwid2hhdFRvU2hvd1wiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkb210cmF2ZXJzYWxhcnJheVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2libGluZ3NcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2libGluZ3NcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm1heE5vZGVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJjZW50ZXJcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzdGFydFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIndoYXRUb1Nob3dcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZG9tdHJhdmVyc2FsYXJyYXlcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIm5leHRTaWJsaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIm5leHRTaWJsaW5nXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ3aGF0VG9TaG93XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInByZXZpb3VzU2libGluZ1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwcmV2aW91c1NpYmxpbmdcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIndoYXRUb1Nob3dcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicXVlcnlTZWxlY3RvclwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJxdWVyeVNlbGVjdG9yXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzZWxlY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkaXNjb25uZWN0ZWROb2RlXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJxdWVyeVNlbGVjdG9yQWxsXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInF1ZXJ5U2VsZWN0b3JBbGxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNlbGVjdG9yXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibGlzdFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbW5vZGVsaXN0XCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdWdnZXN0aW9uc0ZvclF1ZXJ5XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFN1Z2dlc3Rpb25zRm9yUXVlcnlcIixcbiAgICAgICAgICAgIFwicXVlcnlcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNvbXBsZXRpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNlbGVjdG9yU3RhdGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJsaXN0XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYXJyYXk6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJhZGRQc2V1ZG9DbGFzc0xvY2tcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYWRkUHNldWRvQ2xhc3NMb2NrXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwc2V1ZG9DbGFzc1wiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGFyZW50c1wiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaGlkZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaGlkZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVuaGlkZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwidW5oaWRlTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVtb3ZlUHNldWRvQ2xhc3NMb2NrXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbW92ZVBzZXVkb0NsYXNzTG9ja1wiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicHNldWRvQ2xhc3NcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmVudHNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNsZWFyUHNldWRvQ2xhc3NMb2Nrc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjbGVhclBzZXVkb0NsYXNzTG9ja3NcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImlubmVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJpbm5lckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIm91dGVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJvdXRlckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldE91dGVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXRPdXRlckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZW1vdmVOb2RlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbW92ZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJuZXh0U2libGluZ1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImluc2VydEJlZm9yZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJpbnNlcnRCZWZvcmVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmVudFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNpYmxpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRNdXRhdGlvbnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0TXV0YXRpb25zXCIsXG4gICAgICAgICAgICBcImNsZWFudXBcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJtdXRhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJhcnJheTpkb21tdXRhdGlvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaXNJbkRPTVRyZWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXNJbkRPTVRyZWVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJhdHRhY2hlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldE5vZGVBY3RvckZyb21PYmplY3RBY3RvclwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXROb2RlQWN0b3JGcm9tT2JqZWN0QWN0b3JcIixcbiAgICAgICAgICAgIFwib2JqZWN0QWN0b3JJRFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIm5vZGVGcm9udFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJuZXctbXV0YXRpb25zXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJuZXdNdXRhdGlvbnNcIlxuICAgICAgICB9LFxuICAgICAgICBcInBpY2tlci1ub2RlLXBpY2tlZFwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwicGlja2VyTm9kZVBpY2tlZFwiLFxuICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJwaWNrZXItbm9kZS1ob3ZlcmVkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJwaWNrZXJOb2RlSG92ZXJlZFwiLFxuICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJoaWdobGlnaHRlci1yZWFkeVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwiaGlnaGxpZ2h0ZXItcmVhZHlcIlxuICAgICAgICB9LFxuICAgICAgICBcImhpZ2hsaWdodGVyLWhpZGVcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcImhpZ2hsaWdodGVyLWhpZGVcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImluc3BlY3RvclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbnNwZWN0b3JcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRXYWxrZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0V2Fsa2VyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ3YWxrZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkb213YWxrZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhZ2VTdHlsZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYWdlU3R5bGVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInBhZ2VTdHlsZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInBhZ2VzdHlsZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0SGlnaGxpZ2h0ZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0SGlnaGxpZ2h0ZXJcIixcbiAgICAgICAgICAgIFwiYXV0b2hpZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiaGlnaGxpZ3RlclwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImhpZ2hsaWdodGVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRJbWFnZURhdGFGcm9tVVJMXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEltYWdlRGF0YUZyb21VUkxcIixcbiAgICAgICAgICAgIFwidXJsXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJtYXhEaW1cIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6bnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiaW1hZ2VEYXRhXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJjYWxsLXN0YWNrLWl0ZW1cIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjYWxsLXN0YWNrLWl0ZW1cIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwiZmlsZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImxpbmVcIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjYWxsLWRldGFpbHNcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjYWxsLWRldGFpbHNcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJ0eXBlXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwibmFtZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcInN0YWNrXCI6IFwiYXJyYXk6Y2FsbC1zdGFjay1pdGVtXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZnVuY3Rpb24tY2FsbFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJmdW5jdGlvbi1jYWxsXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0RGV0YWlsc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXREZXRhaWxzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJpbmZvXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiY2FsbC1kZXRhaWxzXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJjYWxsLXdhdGNoZXJcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiY2FsbC13YXRjaGVyXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0dXBcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgICAgXCJ0cmFjZWRHbG9iYWxzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ0cmFjZWRGdW5jdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInN0YXJ0UmVjb3JkaW5nXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGVyZm9ybVJlbG9hZFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZpbmFsaXplXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZmluYWxpemVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaXNSZWNvcmRpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXNSZWNvcmRpbmdcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZXN1bWVSZWNvcmRpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVzdW1lUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInBhdXNlUmVjb3JkaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInBhdXNlUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJjYWxsc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OmZ1bmN0aW9uLWNhbGxcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImVyYXNlUmVjb3JkaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImVyYXNlUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcInNuYXBzaG90LWltYWdlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic25hcHNob3QtaW1hZ2VcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJpbmRleFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcIndpZHRoXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwiaGVpZ2h0XCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwiZmxpcHBlZFwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJwaXhlbHNcIjogXCJ1aW50MzItYXJyYXlcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJzbmFwc2hvdC1vdmVydmlld1wiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInNuYXBzaG90LW92ZXJ2aWV3XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwiY2FsbHNcIjogXCJhcnJheTpmdW5jdGlvbi1jYWxsXCIsXG4gICAgICAgIFwidGh1bWJuYWlsc1wiOiBcImFycmF5OnNuYXBzaG90LWltYWdlXCIsXG4gICAgICAgIFwic2NyZWVuc2hvdFwiOiBcInNuYXBzaG90LWltYWdlXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZnJhbWUtc25hcHNob3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZnJhbWUtc25hcHNob3RcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRPdmVydmlld1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRPdmVydmlld1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwib3ZlcnZpZXdcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzbmFwc2hvdC1vdmVydmlld1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2VuZXJhdGVTY3JlZW5zaG90Rm9yXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdlbmVyYXRlU2NyZWVuc2hvdEZvclwiLFxuICAgICAgICAgICAgXCJjYWxsXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImZ1bmN0aW9uLWNhbGxcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInNjcmVlbnNob3RcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzbmFwc2hvdC1pbWFnZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiY2FudmFzXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNhbnZhc1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldHVwXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2V0dXBcIixcbiAgICAgICAgICAgIFwicmVsb2FkXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZmluYWxpemVcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJmaW5hbGl6ZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJpc0luaXRpYWxpemVkXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImlzSW5pdGlhbGl6ZWRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcImluaXRpYWxpemVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVjb3JkQW5pbWF0aW9uRnJhbWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVjb3JkQW5pbWF0aW9uRnJhbWVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInNuYXBzaG90XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZnJhbWUtc25hcHNob3RcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcImdsLXNoYWRlclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJnbC1zaGFkZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNvbXBpbGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY29tcGlsZVwiLFxuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZXJyb3JcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTpqc29uXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJnbC1wcm9ncmFtXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImdsLXByb2dyYW1cIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRWZXJ0ZXhTaGFkZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0VmVydGV4U2hhZGVyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzaGFkZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJnbC1zaGFkZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEZyYWdtZW50U2hhZGVyXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEZyYWdtZW50U2hhZGVyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzaGFkZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJnbC1zaGFkZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgICAgXCJ0aW50XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImFycmF5Om51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ1bmhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVuaGlnaGxpZ2h0XCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImJsYWNrYm94XCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYmxhY2tib3hcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidW5ibGFja2JveFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVuYmxhY2tib3hcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwid2ViZ2xcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwid2ViZ2xcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldHVwXCIsXG4gICAgICAgICAgICBcInJlbG9hZFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZpbmFsaXplXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZmluYWxpemVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UHJvZ3JhbXNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0UHJvZ3JhbXNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInByb2dyYW1zXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYXJyYXk6Z2wtcHJvZ3JhbVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcInByb2dyYW0tbGlua2VkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJwcm9ncmFtTGlua2VkXCIsXG4gICAgICAgICAgXCJwcm9ncmFtXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2wtcHJvZ3JhbVwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImF1ZGlvbm9kZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJhdWRpb25vZGVcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUeXBlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFR5cGVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImlzU291cmNlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImlzU291cmNlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXRQYXJhbVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXRQYXJhbVwiLFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6cHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJlcnJvclwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhcmFtXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFBhcmFtXCIsXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOnByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UGFyYW1GbGFnc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYXJhbUZsYWdzXCIsXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZmxhZ3NcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTpwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhcmFtc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYXJhbXNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInBhcmFtc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcIndlYmF1ZGlvXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcIndlYmF1ZGlvXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0dXBcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgICAgXCJyZWxvYWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJmaW5hbGl6ZVwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImZpbmFsaXplXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJzdGFydC1jb250ZXh0XCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzdGFydENvbnRleHRcIlxuICAgICAgICB9LFxuICAgICAgICBcImNvbm5lY3Qtbm9kZVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwiY29ubmVjdE5vZGVcIixcbiAgICAgICAgICBcInNvdXJjZVwiOiB7XG4gICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImF1ZGlvbm9kZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcImRlc3RcIjoge1xuICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJkaXNjb25uZWN0LW5vZGVcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3ROb2RlXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJjb25uZWN0LXBhcmFtXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjb25uZWN0UGFyYW1cIixcbiAgICAgICAgICBcInNvdXJjZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImF1ZGlvbm9kZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIFwiY2hhbmdlLXBhcmFtXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjaGFuZ2VQYXJhbVwiLFxuICAgICAgICAgIFwic291cmNlXCI6IHtcbiAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXVkaW9ub2RlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicGFyYW1cIjoge1xuICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcImNyZWF0ZS1ub2RlXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjcmVhdGVOb2RlXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJvbGQtc3R5bGVzaGVldFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJvbGQtc3R5bGVzaGVldFwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInRvZ2dsZURpc2FibGVkXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInRvZ2dsZURpc2FibGVkXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJkaXNhYmxlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZldGNoU291cmNlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImZldGNoU291cmNlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVwZGF0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ1cGRhdGVcIixcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidHJhbnNpdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJwcm9wZXJ0eS1jaGFuZ2VcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInByb3BlcnR5Q2hhbmdlXCIsXG4gICAgICAgICAgXCJwcm9wZXJ0eVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcInNvdXJjZS1sb2FkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzb3VyY2VMb2FkXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdHlsZS1hcHBsaWVkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzdHlsZUFwcGxpZWRcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcInN0eWxlZWRpdG9yXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0eWxlZWRpdG9yXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibmV3RG9jdW1lbnRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwibmV3RG9jdW1lbnRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibmV3U3R5bGVTaGVldFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJuZXdTdHlsZVNoZWV0XCIsXG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdHlsZVNoZWV0XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwib2xkLXN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJkb2N1bWVudC1sb2FkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJkb2N1bWVudExvYWRcIixcbiAgICAgICAgICBcInN0eWxlU2hlZXRzXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6b2xkLXN0eWxlc2hlZXRcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVvYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjb29raWVvYmplY3RcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidmFsdWVcIjogXCJsb25nc3RyaW5nXCIsXG4gICAgICAgIFwicGF0aFwiOiBcIm51bGxhYmxlOnN0cmluZ1wiLFxuICAgICAgICBcImhvc3RcIjogXCJzdHJpbmdcIixcbiAgICAgICAgXCJpc0RvbWFpblwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJpc1NlY3VyZVwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJpc0h0dHBPbmx5XCI6IFwiYm9vbGVhblwiLFxuICAgICAgICBcImNyZWF0aW9uVGltZVwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImxhc3RBY2Nlc3NlZFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImV4cGlyZXNcIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVzdG9yZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNvb2tpZXN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6Y29va2llb2JqZWN0XCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0b3JhZ2VvYmplY3RcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidmFsdWVcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZXN0b3Jlb2JqZWN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic3RvcmFnZXN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6c3RvcmFnZW9iamVjdFwiXG4gICAgICB9XG4gICAgfSxcbiAgICBcImlkYm9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImlkYm9iamVjdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcIm5hbWVcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJkYlwiOiBcIm51bGxhYmxlOnN0cmluZ1wiLFxuICAgICAgICBcIm9iamVjdFN0b3JlXCI6IFwibnVsbGFibGU6c3RyaW5nXCIsXG4gICAgICAgIFwib3JpZ2luXCI6IFwibnVsbGFibGU6c3RyaW5nXCIsXG4gICAgICAgIFwidmVyc2lvblwiOiBcIm51bGxhYmxlOm51bWJlclwiLFxuICAgICAgICBcIm9iamVjdFN0b3Jlc1wiOiBcIm51bGxhYmxlOm51bWJlclwiLFxuICAgICAgICBcImtleVBhdGhcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJhdXRvSW5jcmVtZW50XCI6IFwibnVsbGFibGU6Ym9vbGVhblwiLFxuICAgICAgICBcImluZGV4ZXNcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJ2YWx1ZVwiOiBcIm51bGxhYmxlOmxvbmdzdHJpbmdcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJpZGJzdG9yZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImlkYnN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6aWRib2JqZWN0XCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmVVcGRhdGVPYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdG9yZVVwZGF0ZU9iamVjdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcImNoYW5nZWRcIjogXCJudWxsYWJsZTpqc29uXCIsXG4gICAgICAgIFwiZGVsZXRlZFwiOiBcIm51bGxhYmxlOmpzb25cIixcbiAgICAgICAgXCJhZGRlZFwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVzXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNvb2tpZXNcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJjb29raWVzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwibG9jYWxTdG9yYWdlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImxvY2FsU3RvcmFnZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFN0b3JlT2JqZWN0c1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICAgIFwiaG9zdFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwibmFtZXNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm9wdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0b3JhZ2VzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwic2Vzc2lvblN0b3JhZ2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic2Vzc2lvblN0b3JhZ2VcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdG9yYWdlc3RvcmVvYmplY3RcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcImluZGV4ZWREQlwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbmRleGVkREJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJpZGJzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwic3RvcmVsaXN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic3RvcmVsaXN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwiY29va2llc1wiOiBcImNvb2tpZXNcIixcbiAgICAgICAgXCJsb2NhbFN0b3JhZ2VcIjogXCJsb2NhbFN0b3JhZ2VcIixcbiAgICAgICAgXCJzZXNzaW9uU3RvcmFnZVwiOiBcInNlc3Npb25TdG9yYWdlXCIsXG4gICAgICAgIFwiaW5kZXhlZERCXCI6IFwiaW5kZXhlZERCXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdG9yYWdlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibGlzdFN0b3Jlc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJsaXN0U3RvcmVzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwic3RvcmVsaXN0XCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7XG4gICAgICAgIFwic3RvcmVzLXVwZGF0ZVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVzVXBkYXRlXCIsXG4gICAgICAgICAgXCJkYXRhXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVVcGRhdGVPYmplY3RcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdG9yZXMtY2xlYXJlZFwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVzQ2xlYXJlZFwiLFxuICAgICAgICAgIFwiZGF0YVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImpzb25cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdG9yZXMtcmVsb2FkZWRcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInN0b3Jlc1JlbGFvZGVkXCIsXG4gICAgICAgICAgXCJkYXRhXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImdjbGlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZ2NsaVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNwZWNzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNwZWNzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZXhlY3V0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJleGVjdXRlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInN0YXRlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0YXRlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzdGFydFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicmFua1wiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0eXBlcGFyc2VcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwidHlwZXBhcnNlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0eXBlaW5jcmVtZW50XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInR5cGVpbmNyZW1lbnRcIixcbiAgICAgICAgICAgIFwidHlwZWRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidHlwZWRlY3JlbWVudFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ0eXBlZGVjcmVtZW50XCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNlbGVjdGlvbmluZm9cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2VsZWN0aW9uaW5mb1wiLFxuICAgICAgICAgICAgXCJ0eXBlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGFyYW1cIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImFjdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJtZW1vcnlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwibWVtb3J5XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibWVhc3VyZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtZWFzdXJlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiZXZlbnRMb29wTGFnXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImV2ZW50TG9vcExhZ1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInN0YXJ0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0YXJ0XCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdWNjZXNzXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzdG9wXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0b3BcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcImV2ZW50LWxvb3AtbGFnXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJldmVudC1sb29wLWxhZ1wiLFxuICAgICAgICAgIFwidGltZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bWJlclwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcInByZWZlcmVuY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwicHJlZmVyZW5jZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEJvb2xQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEJvb2xQcmVmXCIsXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRDaGFyUHJlZlwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRDaGFyUHJlZlwiLFxuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRJbnRQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEludFByZWZcIixcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0QWxsUHJlZnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0QWxsUHJlZnNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0Qm9vbFByZWZcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2V0Qm9vbFByZWZcIixcbiAgICAgICAgICAgIFwibmFtZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldENoYXJQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldENoYXJQcmVmXCIsXG4gICAgICAgICAgICBcIm5hbWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXRJbnRQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldEludFByZWZcIixcbiAgICAgICAgICAgIFwibmFtZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNsZWFyVXNlclByZWZcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY2xlYXJVc2VyUHJlZlwiLFxuICAgICAgICAgICAgXCJuYW1lXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJkZXZpY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZGV2aWNlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0RGVzY3JpcHRpb25cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0RGVzY3JpcHRpb25cIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0V2FsbHBhcGVyXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFdhbGxwYXBlclwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzY3JlZW5zaG90VG9EYXRhVVJMXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNjcmVlbnNob3RUb0RhdGFVUkxcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibG9uZ3N0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UmF3UGVybWlzc2lvbnNUYWJsZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRSYXdQZXJtaXNzaW9uc1RhYmxlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfVxuICB9LFxuICBcImZyb21cIjogXCJyb290XCJcbn1cbiIsIlwidXNlIHN0cmljdFwiO1xuXG52YXIgQ2xhc3MgPSByZXF1aXJlKFwiLi9jbGFzc1wiKS5DbGFzcztcbnZhciB1dGlsID0gcmVxdWlyZShcIi4vdXRpbFwiKTtcbnZhciBrZXlzID0gdXRpbC5rZXlzO1xudmFyIHZhbHVlcyA9IHV0aWwudmFsdWVzO1xudmFyIHBhaXJzID0gdXRpbC5wYWlycztcbnZhciBxdWVyeSA9IHV0aWwucXVlcnk7XG52YXIgZmluZFBhdGggPSB1dGlsLmZpbmRQYXRoO1xudmFyIEV2ZW50VGFyZ2V0ID0gcmVxdWlyZShcIi4vZXZlbnRcIikuRXZlbnRUYXJnZXQ7XG5cbnZhciBUeXBlU3lzdGVtID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oY2xpZW50KSB7XG4gICAgdmFyIHR5cGVzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB2YXIgc3BlY2lmaWNhdGlvbiA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG5cbiAgICB0aGlzLnNwZWNpZmljYXRpb24gPSBzcGVjaWZpY2F0aW9uO1xuICAgIHRoaXMudHlwZXMgPSB0eXBlcztcblxuICAgIHZhciB0eXBlRm9yID0gZnVuY3Rpb24gdHlwZUZvcih0eXBlTmFtZSkge1xuICAgICAgdHlwZU5hbWUgPSB0eXBlTmFtZSB8fCBcInByaW1pdGl2ZVwiO1xuICAgICAgaWYgKCF0eXBlc1t0eXBlTmFtZV0pIHtcbiAgICAgICAgZGVmaW5lVHlwZSh0eXBlTmFtZSk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0eXBlc1t0eXBlTmFtZV07XG4gICAgfTtcbiAgICB0aGlzLnR5cGVGb3IgPSB0eXBlRm9yO1xuXG4gICAgdmFyIGRlZmluZVR5cGUgPSBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgdHlwZSA9IHZvaWQoMCk7XG4gICAgICBpZiAodHlwZW9mKGRlc2NyaXB0b3IpID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIGlmIChkZXNjcmlwdG9yLmluZGV4T2YoXCI6XCIpID4gMClcbiAgICAgICAgICB0eXBlID0gbWFrZUNvbXBvdW5kVHlwZShkZXNjcmlwdG9yKTtcbiAgICAgICAgZWxzZSBpZiAoZGVzY3JpcHRvci5pbmRleE9mKFwiI1wiKSA+IDApXG4gICAgICAgICAgdHlwZSA9IG5ldyBBY3RvckRldGFpbChkZXNjcmlwdG9yKTtcbiAgICAgICAgICBlbHNlIGlmIChzcGVjaWZpY2F0aW9uW2Rlc2NyaXB0b3JdKVxuICAgICAgICAgICAgdHlwZSA9IG1ha2VDYXRlZ29yeVR5cGUoc3BlY2lmaWNhdGlvbltkZXNjcmlwdG9yXSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0eXBlID0gbWFrZUNhdGVnb3J5VHlwZShkZXNjcmlwdG9yKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHR5cGUpXG4gICAgICAgIHR5cGVzW3R5cGUubmFtZV0gPSB0eXBlO1xuICAgICAgZWxzZVxuICAgICAgICB0aHJvdyBUeXBlRXJyb3IoXCJJbnZhbGlkIHR5cGU6IFwiICsgZGVzY3JpcHRvcik7XG4gICAgfTtcbiAgICB0aGlzLmRlZmluZVR5cGUgPSBkZWZpbmVUeXBlO1xuXG5cbiAgICB2YXIgbWFrZUNvbXBvdW5kVHlwZSA9IGZ1bmN0aW9uKG5hbWUpIHtcbiAgICAgIHZhciBpbmRleCA9IG5hbWUuaW5kZXhPZihcIjpcIik7XG4gICAgICB2YXIgYmFzZVR5cGUgPSBuYW1lLnNsaWNlKDAsIGluZGV4KTtcbiAgICAgIHZhciBzdWJUeXBlID0gbmFtZS5zbGljZShpbmRleCArIDEpO1xuXG4gICAgICByZXR1cm4gYmFzZVR5cGUgPT09IFwiYXJyYXlcIiA/IG5ldyBBcnJheU9mKHN1YlR5cGUpIDpcbiAgICAgIGJhc2VUeXBlID09PSBcIm51bGxhYmxlXCIgPyBuZXcgTWF5YmUoc3ViVHlwZSkgOlxuICAgICAgbnVsbDtcbiAgICB9O1xuXG4gICAgdmFyIG1ha2VDYXRlZ29yeVR5cGUgPSBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgY2F0ZWdvcnkgPSBkZXNjcmlwdG9yLmNhdGVnb3J5O1xuICAgICAgcmV0dXJuIGNhdGVnb3J5ID09PSBcImRpY3RcIiA/IG5ldyBEaWN0aW9uYXJ5KGRlc2NyaXB0b3IpIDpcbiAgICAgIGNhdGVnb3J5ID09PSBcImFjdG9yXCIgPyBuZXcgQWN0b3IoZGVzY3JpcHRvcikgOlxuICAgICAgbnVsbDtcbiAgICB9O1xuXG4gICAgdmFyIHJlYWQgPSBmdW5jdGlvbihpbnB1dCwgY29udGV4dCwgdHlwZU5hbWUpIHtcbiAgICAgIHJldHVybiB0eXBlRm9yKHR5cGVOYW1lKS5yZWFkKGlucHV0LCBjb250ZXh0KTtcbiAgICB9XG4gICAgdGhpcy5yZWFkID0gcmVhZDtcblxuICAgIHZhciB3cml0ZSA9IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0LCB0eXBlTmFtZSkge1xuICAgICAgcmV0dXJuIHR5cGVGb3IodHlwZU5hbWUpLndyaXRlKGlucHV0KTtcbiAgICB9O1xuICAgIHRoaXMud3JpdGUgPSB3cml0ZTtcblxuXG4gICAgdmFyIFR5cGUgPSBDbGFzcyh7XG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oKSB7XG4gICAgICB9LFxuICAgICAgZ2V0IG5hbWUoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmNhdGVnb3J5ID8gdGhpcy5jYXRlZ29yeSArIFwiOlwiICsgdGhpcy50eXBlIDpcbiAgICAgICAgdGhpcy50eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJgVHlwZWAgc3ViY2xhc3MgbXVzdCBpbXBsZW1lbnQgYHJlYWRgXCIpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiYFR5cGVgIHN1YmNsYXNzIG11c3QgaW1wbGVtZW50IGB3cml0ZWBcIik7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgUHJpbWl0dmUgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBUeXBlLFxuICAgICAgY29uc3R1Y3RvcjogZnVuY3Rpb24odHlwZSkge1xuICAgICAgICB0aGlzLnR5cGUgPSB0eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHJldHVybiBpbnB1dDtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgcmV0dXJuIGlucHV0O1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdmFyIE1heWJlID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNhdGVnb3J5OiBcIm51bGxhYmxlXCIsXG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24odHlwZSkge1xuICAgICAgICB0aGlzLnR5cGUgPSB0eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHJldHVybiBpbnB1dCA9PT0gbnVsbCA/IG51bGwgOlxuICAgICAgICBpbnB1dCA9PT0gdm9pZCgwKSA/IHZvaWQoMCkgOlxuICAgICAgICByZWFkKGlucHV0LCBjb250ZXh0LCB0aGlzLnR5cGUpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gaW5wdXQgPT09IG51bGwgPyBudWxsIDpcbiAgICAgICAgaW5wdXQgPT09IHZvaWQoMCkgPyB2b2lkKDApIDpcbiAgICAgICAgd3JpdGUoaW5wdXQsIGNvbnRleHQsIHRoaXMudHlwZSk7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgQXJyYXlPZiA9IENsYXNzKHtcbiAgICAgIGV4dGVuZHM6IFR5cGUsXG4gICAgICBjYXRlZ29yeTogXCJhcnJheVwiLFxuICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKHR5cGUpIHtcbiAgICAgICAgdGhpcy50eXBlID0gdHlwZTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB2YXIgdHlwZSA9IHRoaXMudHlwZTtcbiAgICAgICAgcmV0dXJuIGlucHV0Lm1hcChmdW5jdGlvbigkKSB7IHJldHVybiByZWFkKCQsIGNvbnRleHQsIHR5cGUpIH0pO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB2YXIgdHlwZSA9IHRoaXMudHlwZTtcbiAgICAgICAgcmV0dXJuIGlucHV0Lm1hcChmdW5jdGlvbigkKSB7IHJldHVybiB3cml0ZSgkLCBjb250ZXh0LCB0eXBlKSB9KTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIHZhciBtYWtlRmllbGQgPSBmdW5jdGlvbiBtYWtlRmllbGQobmFtZSwgdHlwZSkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgICAgICBnZXQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBuYW1lLCB7XG4gICAgICAgICAgICBjb25maWd1cmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgdmFsdWU6IHJlYWQodGhpcy5zdGF0ZVtuYW1lXSwgdGhpcy5jb250ZXh0LCB0eXBlKVxuICAgICAgICAgIH0pO1xuICAgICAgICAgIHJldHVybiB0aGlzW25hbWVdO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfTtcblxuICAgIHZhciBtYWtlRmllbGRzID0gZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgICAgcmV0dXJuIHBhaXJzKGRlc2NyaXB0b3IpLnJlZHVjZShmdW5jdGlvbihmaWVsZHMsIHBhaXIpIHtcbiAgICAgICAgdmFyIG5hbWUgPSBwYWlyWzBdLCB0eXBlID0gcGFpclsxXTtcbiAgICAgICAgZmllbGRzW25hbWVdID0gbWFrZUZpZWxkKG5hbWUsIHR5cGUpO1xuICAgICAgICByZXR1cm4gZmllbGRzO1xuICAgICAgfSwge30pO1xuICAgIH1cblxuICAgIHZhciBEaWN0aW9uYXJ5VHlwZSA9IENsYXNzKHt9KTtcblxuICAgIHZhciBEaWN0aW9uYXJ5ID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNhdGVnb3J5OiBcImRpY3RcIixcbiAgICAgIGdldCBuYW1lKCkgeyByZXR1cm4gdGhpcy50eXBlOyB9LFxuICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKGRlc2NyaXB0b3IpIHtcbiAgICAgICAgdGhpcy50eXBlID0gZGVzY3JpcHRvci50eXBlTmFtZTtcbiAgICAgICAgdGhpcy50eXBlcyA9IGRlc2NyaXB0b3Iuc3BlY2lhbGl6YXRpb25zO1xuXG4gICAgICAgIHZhciBwcm90byA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHtcbiAgICAgICAgICBleHRlbmRzOiBEaWN0aW9uYXJ5VHlwZSxcbiAgICAgICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oc3RhdGUsIGNvbnRleHQpIHtcbiAgICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMsIHtcbiAgICAgICAgICAgICAgc3RhdGU6IHtcbiAgICAgICAgICAgICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB3cml0YWJsZTogdHJ1ZSxcbiAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgdmFsdWU6IHN0YXRlXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIGNvbnRleHQ6IHtcbiAgICAgICAgICAgICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB3cml0YWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgICAgICAgICAgICAgIHZhbHVlOiBjb250ZXh0XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSwgbWFrZUZpZWxkcyh0aGlzLnR5cGVzKSk7XG5cbiAgICAgICAgdGhpcy5jbGFzcyA9IG5ldyBDbGFzcyhwcm90byk7XG4gICAgICB9LFxuICAgICAgcmVhZDogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgcmV0dXJuIG5ldyB0aGlzLmNsYXNzKGlucHV0LCBjb250ZXh0KTtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgdmFyIG91dHB1dCA9IHt9O1xuICAgICAgICBmb3IgKHZhciBrZXkgaW4gaW5wdXQpIHtcbiAgICAgICAgICBvdXRwdXRba2V5XSA9IHdyaXRlKHZhbHVlLCBjb250ZXh0LCB0eXBlc1trZXldKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gb3V0cHV0O1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdmFyIG1ha2VNZXRob2RzID0gZnVuY3Rpb24oZGVzY3JpcHRvcnMpIHtcbiAgICAgIHJldHVybiBkZXNjcmlwdG9ycy5yZWR1Y2UoZnVuY3Rpb24obWV0aG9kcywgZGVzY3JpcHRvcikge1xuICAgICAgICBtZXRob2RzW2Rlc2NyaXB0b3IubmFtZV0gPSB7XG4gICAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgd3JpdGFibGU6IGZhbHNlLFxuICAgICAgICAgIHZhbHVlOiBtYWtlTWV0aG9kKGRlc2NyaXB0b3IpXG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiBtZXRob2RzO1xuICAgICAgfSwge30pO1xuICAgIH07XG5cbiAgICB2YXIgbWFrZUV2ZW50cyA9IGZ1bmN0aW9uKGRlc2NyaXB0b3JzKSB7XG4gICAgICByZXR1cm4gcGFpcnMoZGVzY3JpcHRvcnMpLnJlZHVjZShmdW5jdGlvbihldmVudHMsIHBhaXIpIHtcbiAgICAgICAgdmFyIG5hbWUgPSBwYWlyWzBdLCBkZXNjcmlwdG9yID0gcGFpclsxXTtcbiAgICAgICAgdmFyIGV2ZW50ID0gbmV3IEV2ZW50KG5hbWUsIGRlc2NyaXB0b3IpO1xuICAgICAgICBldmVudHNbZXZlbnQuZXZlbnRUeXBlXSA9IGV2ZW50O1xuICAgICAgICByZXR1cm4gZXZlbnRzO1xuICAgICAgfSwgT2JqZWN0LmNyZWF0ZShudWxsKSk7XG4gICAgfTtcblxuICAgIHZhciBBY3RvciA9IENsYXNzKHtcbiAgICAgIGV4dGVuZHM6IFR5cGUsXG4gICAgICBjYXRlZ29yeTogXCJhY3RvclwiLFxuICAgICAgZ2V0IG5hbWUoKSB7IHJldHVybiB0aGlzLnR5cGU7IH0sXG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgICAgICB0aGlzLnR5cGUgPSBkZXNjcmlwdG9yLnR5cGVOYW1lO1xuXG4gICAgICAgIHZhciBldmVudHMgPSBtYWtlRXZlbnRzKGRlc2NyaXB0b3IuZXZlbnRzIHx8IHt9KTtcbiAgICAgICAgdmFyIGZpZWxkcyA9IG1ha2VGaWVsZHMoZGVzY3JpcHRvci5maWVsZHMgfHwge30pO1xuICAgICAgICB2YXIgbWV0aG9kcyA9IG1ha2VNZXRob2RzKGRlc2NyaXB0b3IubWV0aG9kcyB8fCBbXSk7XG5cblxuICAgICAgICB2YXIgcHJvdG8gPSB7XG4gICAgICAgICAgZXh0ZW5kczogRnJvbnQsXG4gICAgICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgRnJvbnQuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICAgICAgICB9LFxuICAgICAgICAgIGV2ZW50czogZXZlbnRzXG4gICAgICAgIH07XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHByb3RvLCBmaWVsZHMpO1xuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydGllcyhwcm90bywgbWV0aG9kcyk7XG5cbiAgICAgICAgdGhpcy5jbGFzcyA9IENsYXNzKHByb3RvKTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCwgZGV0YWlsKSB7XG4gICAgICAgIHZhciBzdGF0ZSA9IHR5cGVvZihpbnB1dCkgPT09IFwic3RyaW5nXCIgPyB7IGFjdG9yOiBpbnB1dCB9IDogaW5wdXQ7XG5cbiAgICAgICAgdmFyIGFjdG9yID0gY2xpZW50LmdldChzdGF0ZS5hY3RvcikgfHwgbmV3IHRoaXMuY2xhc3Moc3RhdGUsIGNvbnRleHQpO1xuICAgICAgICBhY3Rvci5mb3JtKHN0YXRlLCBkZXRhaWwsIGNvbnRleHQpO1xuXG4gICAgICAgIHJldHVybiBhY3RvcjtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQsIGRldGFpbCkge1xuICAgICAgICByZXR1cm4gaW5wdXQuaWQ7XG4gICAgICB9XG4gICAgfSk7XG4gICAgZXhwb3J0cy5BY3RvciA9IEFjdG9yO1xuXG5cbiAgICB2YXIgQWN0b3JEZXRhaWwgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBBY3RvcixcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihuYW1lKSB7XG4gICAgICAgIHZhciBwYXJ0cyA9IG5hbWUuc3BsaXQoXCIjXCIpXG4gICAgICAgIHRoaXMuYWN0b3JUeXBlID0gcGFydHNbMF1cbiAgICAgICAgdGhpcy5kZXRhaWwgPSBwYXJ0c1sxXTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdHlwZUZvcih0aGlzLmFjdG9yVHlwZSkucmVhZChpbnB1dCwgY29udGV4dCwgdGhpcy5kZXRhaWwpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdHlwZUZvcih0aGlzLmFjdG9yVHlwZSkud3JpdGUoaW5wdXQsIGNvbnRleHQsIHRoaXMuZGV0YWlsKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICBleHBvcnRzLkFjdG9yRGV0YWlsID0gQWN0b3JEZXRhaWw7XG5cbiAgICB2YXIgTWV0aG9kID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICAgIHRoaXMudHlwZSA9IGRlc2NyaXB0b3IubmFtZTtcbiAgICAgICAgdGhpcy5wYXRoID0gZmluZFBhdGgoZGVzY3JpcHRvci5yZXNwb25zZSwgXCJfcmV0dmFsXCIpO1xuICAgICAgICB0aGlzLnJlc3BvbnNlVHlwZSA9IHRoaXMucGF0aCAmJiBxdWVyeShkZXNjcmlwdG9yLnJlc3BvbnNlLCB0aGlzLnBhdGgpLl9yZXR2YWw7XG4gICAgICAgIHRoaXMucmVxdWVzdFR5cGUgPSBkZXNjcmlwdG9yLnJlcXVlc3QudHlwZTtcblxuICAgICAgICB2YXIgcGFyYW1zID0gW107XG4gICAgICAgIGZvciAodmFyIGtleSBpbiBkZXNjcmlwdG9yLnJlcXVlc3QpIHtcbiAgICAgICAgICBpZiAoa2V5ICE9PSBcInR5cGVcIikge1xuICAgICAgICAgICAgdmFyIHBhcmFtID0gZGVzY3JpcHRvci5yZXF1ZXN0W2tleV07XG4gICAgICAgICAgICB2YXIgaW5kZXggPSBcIl9hcmdcIiBpbiBwYXJhbSA/IHBhcmFtLl9hcmcgOiBwYXJhbS5fb3B0aW9uO1xuICAgICAgICAgICAgdmFyIGlzUGFyYW0gPSBwYXJhbS5fb3B0aW9uID09PSBpbmRleDtcbiAgICAgICAgICAgIHZhciBpc0FyZ3VtZW50ID0gcGFyYW0uX2FyZyA9PT0gaW5kZXg7XG4gICAgICAgICAgICBwYXJhbXNbaW5kZXhdID0ge1xuICAgICAgICAgICAgICB0eXBlOiBwYXJhbS50eXBlLFxuICAgICAgICAgICAgICBrZXk6IGtleSxcbiAgICAgICAgICAgICAgaW5kZXg6IGluZGV4LFxuICAgICAgICAgICAgICBpc1BhcmFtOiBpc1BhcmFtLFxuICAgICAgICAgICAgICBpc0FyZ3VtZW50OiBpc0FyZ3VtZW50XG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICB0aGlzLnBhcmFtcyA9IHBhcmFtcztcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gcmVhZChxdWVyeShpbnB1dCwgdGhpcy5wYXRoKSwgY29udGV4dCwgdGhpcy5yZXNwb25zZVR5cGUpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdGhpcy5wYXJhbXMucmVkdWNlKGZ1bmN0aW9uKHJlc3VsdCwgcGFyYW0pIHtcbiAgICAgICAgICByZXN1bHRbcGFyYW0ua2V5XSA9IHdyaXRlKGlucHV0W3BhcmFtLmluZGV4XSwgY29udGV4dCwgcGFyYW0udHlwZSk7XG4gICAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgICAgfSwge3R5cGU6IHRoaXMudHlwZX0pO1xuICAgICAgfVxuICAgIH0pO1xuICAgIGV4cG9ydHMuTWV0aG9kID0gTWV0aG9kO1xuXG4gICAgdmFyIHByb2ZpbGVyID0gZnVuY3Rpb24obWV0aG9kLCBpZCkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgc3RhcnQgPSBuZXcgRGF0ZSgpO1xuICAgICAgICByZXR1cm4gbWV0aG9kLmFwcGx5KHRoaXMsIGFyZ3VtZW50cykudGhlbihmdW5jdGlvbihyZXN1bHQpIHtcbiAgICAgICAgICB2YXIgZW5kID0gbmV3IERhdGUoKTtcbiAgICAgICAgICBjbGllbnQudGVsZW1ldHJ5LmFkZChpZCwgK2VuZCAtIHN0YXJ0KTtcbiAgICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgICB9KTtcbiAgICAgIH07XG4gICAgfTtcblxuICAgIHZhciBkZXN0cnVjdG9yID0gZnVuY3Rpb24obWV0aG9kKSB7XG4gICAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICAgIHJldHVybiBtZXRob2QuYXBwbHkodGhpcywgYXJndW1lbnRzKS50aGVuKGZ1bmN0aW9uKHJlc3VsdCkge1xuICAgICAgICAgIGNsaWVudC5yZWxlYXNlKHRoaXMpO1xuICAgICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICAgIH0pO1xuICAgICAgfTtcbiAgICB9O1xuXG4gICAgZnVuY3Rpb24gbWFrZU1ldGhvZChkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgdHlwZSA9IG5ldyBNZXRob2QoZGVzY3JpcHRvcik7XG4gICAgICB2YXIgbWV0aG9kID0gZGVzY3JpcHRvci5vbmV3YXkgPyBtYWtlVW5pZGlyZWNhdGlvbmFsTWV0aG9kKGRlc2NyaXB0b3IsIHR5cGUpIDpcbiAgICAgICAgICAgICAgICAgICBtYWtlQmlkaXJlY3Rpb25hbE1ldGhvZChkZXNjcmlwdG9yLCB0eXBlKTtcblxuICAgICAgaWYgKGRlc2NyaXB0b3IudGVsZW1ldHJ5KVxuICAgICAgICBtZXRob2QgPSBwcm9maWxlcihtZXRob2QpO1xuICAgICAgaWYgKGRlc2NyaXB0b3IucmVsZWFzZSlcbiAgICAgICAgbWV0aG9kID0gZGVzdHJ1Y3RvcihtZXRob2QpO1xuXG4gICAgICByZXR1cm4gbWV0aG9kO1xuICAgIH1cblxuICAgIHZhciBtYWtlVW5pZGlyZWNhdGlvbmFsTWV0aG9kID0gZnVuY3Rpb24oZGVzY3JpcHRvciwgdHlwZSkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgcGFja2V0ID0gdHlwZS53cml0ZShhcmd1bWVudHMsIHRoaXMpO1xuICAgICAgICBwYWNrZXQudG8gPSB0aGlzLmlkO1xuICAgICAgICBjbGllbnQuc2VuZChwYWNrZXQpO1xuICAgICAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKHZvaWQoMCkpO1xuICAgICAgfTtcbiAgICB9O1xuXG4gICAgdmFyIG1ha2VCaWRpcmVjdGlvbmFsTWV0aG9kID0gZnVuY3Rpb24oZGVzY3JpcHRvciwgdHlwZSkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgY29udGV4dCA9IHRoaXMuY29udGV4dDtcbiAgICAgICAgdmFyIHBhY2tldCA9IHR5cGUud3JpdGUoYXJndW1lbnRzLCBjb250ZXh0KTtcbiAgICAgICAgdmFyIGNvbnRleHQgPSB0aGlzLmNvbnRleHQ7XG4gICAgICAgIHBhY2tldC50byA9IHRoaXMuaWQ7XG4gICAgICAgIHJldHVybiBjbGllbnQucmVxdWVzdChwYWNrZXQpLnRoZW4oZnVuY3Rpb24ocGFja2V0KSB7XG4gICAgICAgICAgcmV0dXJuIHR5cGUucmVhZChwYWNrZXQsIGNvbnRleHQpO1xuICAgICAgICB9KTtcbiAgICAgIH07XG4gICAgfTtcblxuICAgIHZhciBFdmVudCA9IENsYXNzKHtcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihuYW1lLCBkZXNjcmlwdG9yKSB7XG4gICAgICAgIHRoaXMubmFtZSA9IGRlc2NyaXB0b3IudHlwZSB8fCBuYW1lO1xuICAgICAgICB0aGlzLmV2ZW50VHlwZSA9IGRlc2NyaXB0b3IudHlwZSB8fCBuYW1lO1xuICAgICAgICB0aGlzLnR5cGVzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcblxuICAgICAgICB2YXIgdHlwZXMgPSB0aGlzLnR5cGVzO1xuICAgICAgICBmb3IgKHZhciBrZXkgaW4gZGVzY3JpcHRvcikge1xuICAgICAgICAgIGlmIChrZXkgPT09IFwidHlwZVwiKSB7XG4gICAgICAgICAgICB0eXBlc1trZXldID0gXCJzdHJpbmdcIjtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdHlwZXNba2V5XSA9IGRlc2NyaXB0b3Jba2V5XS50eXBlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHZhciBvdXRwdXQgPSB7fTtcbiAgICAgICAgdmFyIHR5cGVzID0gdGhpcy50eXBlcztcbiAgICAgICAgZm9yICh2YXIga2V5IGluIGlucHV0KSB7XG4gICAgICAgICAgb3V0cHV0W2tleV0gPSByZWFkKGlucHV0W2tleV0sIGNvbnRleHQsIHR5cGVzW2tleV0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvdXRwdXQ7XG4gICAgICB9LFxuICAgICAgd3JpdGU6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHZhciBvdXRwdXQgPSB7fTtcbiAgICAgICAgdmFyIHR5cGVzID0gdGhpcy50eXBlcztcbiAgICAgICAgZm9yICh2YXIga2V5IGluIHRoaXMudHlwZXMpIHtcbiAgICAgICAgICBvdXRwdXRba2V5XSA9IHdyaXRlKGlucHV0W2tleV0sIGNvbnRleHQsIHR5cGVzW2tleV0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvdXRwdXQ7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgRnJvbnQgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBFdmVudFRhcmdldCxcbiAgICAgIEV2ZW50VGFyZ2V0OiBFdmVudFRhcmdldCxcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihzdGF0ZSkge1xuICAgICAgICB0aGlzLkV2ZW50VGFyZ2V0KCk7XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMsICB7XG4gICAgICAgICAgc3RhdGU6IHtcbiAgICAgICAgICAgIGVudW1lcmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgd3JpdGFibGU6IHRydWUsXG4gICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICB2YWx1ZTogc3RhdGVcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGNsaWVudC5yZWdpc3Rlcih0aGlzKTtcbiAgICAgIH0sXG4gICAgICBnZXQgaWQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnN0YXRlLmFjdG9yO1xuICAgICAgfSxcbiAgICAgIGdldCBjb250ZXh0KCkge1xuICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgIH0sXG4gICAgICBmb3JtOiBmdW5jdGlvbihzdGF0ZSwgZGV0YWlsLCBjb250ZXh0KSB7XG4gICAgICAgIGlmICh0aGlzLnN0YXRlICE9PSBzdGF0ZSkge1xuICAgICAgICAgIGlmIChkZXRhaWwpIHtcbiAgICAgICAgICAgIHRoaXMuc3RhdGVbZGV0YWlsXSA9IHN0YXRlW2RldGFpbF07XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHBhaXJzKHN0YXRlKS5mb3JFYWNoKGZ1bmN0aW9uKHBhaXIpIHtcbiAgICAgICAgICAgICAgdmFyIGtleSA9IHBhaXJbMF0sIHZhbHVlID0gcGFpclsxXTtcbiAgICAgICAgICAgICAgdGhpcy5zdGF0ZVtrZXldID0gdmFsdWU7XG4gICAgICAgICAgICB9LCB0aGlzKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoY29udGV4dCkge1xuICAgICAgICAgIGNsaWVudC5zdXBlcnZpc2UoY29udGV4dCwgdGhpcyk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICByZXF1ZXN0VHlwZXM6IGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR1cm4gY2xpZW50LnJlcXVlc3Qoe1xuICAgICAgICAgIHRvOiB0aGlzLmlkLFxuICAgICAgICAgIHR5cGU6IFwicmVxdWVzdFR5cGVzXCJcbiAgICAgICAgfSkudGhlbihmdW5jdGlvbihwYWNrZXQpIHtcbiAgICAgICAgICByZXR1cm4gcGFja2V0LnJlcXVlc3RUeXBlcztcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgdHlwZXMucHJpbWl0aXZlID0gbmV3IFByaW1pdHZlKFwicHJpbWl0aXZlXCIpO1xuICAgIHR5cGVzLnN0cmluZyA9IG5ldyBQcmltaXR2ZShcInN0cmluZ1wiKTtcbiAgICB0eXBlcy5udW1iZXIgPSBuZXcgUHJpbWl0dmUoXCJudW1iZXJcIik7XG4gICAgdHlwZXMuYm9vbGVhbiA9IG5ldyBQcmltaXR2ZShcImJvb2xlYW5cIik7XG4gICAgdHlwZXMuanNvbiA9IG5ldyBQcmltaXR2ZShcImpzb25cIik7XG4gICAgdHlwZXMuYXJyYXkgPSBuZXcgUHJpbWl0dmUoXCJhcnJheVwiKTtcbiAgfSxcbiAgcmVnaXN0ZXJUeXBlczogZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgIHZhciBzcGVjaWZpY2F0aW9uID0gdGhpcy5zcGVjaWZpY2F0aW9uO1xuICAgIHZhbHVlcyhkZXNjcmlwdG9yLnR5cGVzKS5mb3JFYWNoKGZ1bmN0aW9uKGRlc2NyaXB0b3IpIHtcbiAgICAgIHNwZWNpZmljYXRpb25bZGVzY3JpcHRvci50eXBlTmFtZV0gPSBkZXNjcmlwdG9yO1xuICAgIH0pO1xuICB9XG59KTtcbmV4cG9ydHMuVHlwZVN5c3RlbSA9IFR5cGVTeXN0ZW07XG4iLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIGtleXMgPSBPYmplY3Qua2V5cztcbmV4cG9ydHMua2V5cyA9IGtleXM7XG5cbi8vIFJldHVybnMgYXJyYXkgb2YgdmFsdWVzIGZvciB0aGUgZ2l2ZW4gb2JqZWN0LlxudmFyIHZhbHVlcyA9IGZ1bmN0aW9uKG9iamVjdCkge1xuICByZXR1cm4ga2V5cyhvYmplY3QpLm1hcChmdW5jdGlvbihrZXkpIHtcbiAgICByZXR1cm4gb2JqZWN0W2tleV1cbiAgfSk7XG59O1xuZXhwb3J0cy52YWx1ZXMgPSB2YWx1ZXM7XG5cbi8vIFJldHVybnMgW2tleSwgdmFsdWVdIHBhaXJzIGZvciB0aGUgZ2l2ZW4gb2JqZWN0LlxudmFyIHBhaXJzID0gZnVuY3Rpb24ob2JqZWN0KSB7XG4gIHJldHVybiBrZXlzKG9iamVjdCkubWFwKGZ1bmN0aW9uKGtleSkge1xuICAgIHJldHVybiBba2V5LCBvYmplY3Rba2V5XV1cbiAgfSk7XG59O1xuZXhwb3J0cy5wYWlycyA9IHBhaXJzO1xuXG5cbi8vIFF1ZXJpZXMgYW4gb2JqZWN0IGZvciB0aGUgZmllbGQgbmVzdGVkIHdpdGggaW4gaXQuXG52YXIgcXVlcnkgPSBmdW5jdGlvbihvYmplY3QsIHBhdGgpIHtcbiAgcmV0dXJuIHBhdGgucmVkdWNlKGZ1bmN0aW9uKG9iamVjdCwgZW50cnkpIHtcbiAgICByZXR1cm4gb2JqZWN0ICYmIG9iamVjdFtlbnRyeV1cbiAgfSwgb2JqZWN0KTtcbn07XG5leHBvcnRzLnF1ZXJ5ID0gcXVlcnk7XG5cbnZhciBpc09iamVjdCA9IGZ1bmN0aW9uKHgpIHtcbiAgcmV0dXJuIHggJiYgdHlwZW9mKHgpID09PSBcIm9iamVjdFwiXG59XG5cbnZhciBmaW5kUGF0aCA9IGZ1bmN0aW9uKG9iamVjdCwga2V5KSB7XG4gIHZhciBwYXRoID0gdm9pZCgwKTtcbiAgaWYgKG9iamVjdCAmJiB0eXBlb2Yob2JqZWN0KSA9PT0gXCJvYmplY3RcIikge1xuICAgIHZhciBuYW1lcyA9IGtleXMob2JqZWN0KTtcbiAgICBpZiAobmFtZXMuaW5kZXhPZihrZXkpID49IDApIHtcbiAgICAgIHBhdGggPSBbXTtcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIGluZGV4ID0gMDtcbiAgICAgIHZhciBjb3VudCA9IG5hbWVzLmxlbmd0aDtcbiAgICAgIHdoaWxlIChpbmRleCA8IGNvdW50ICYmICFwYXRoKXtcbiAgICAgICAgdmFyIGhlYWQgPSBuYW1lc1tpbmRleF07XG4gICAgICAgIHZhciB0YWlsID0gZmluZFBhdGgob2JqZWN0W2hlYWRdLCBrZXkpO1xuICAgICAgICBwYXRoID0gdGFpbCA/IFtoZWFkXS5jb25jYXQodGFpbCkgOiB0YWlsO1xuICAgICAgICBpbmRleCA9IGluZGV4ICsgMVxuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gcGF0aDtcbn07XG5leHBvcnRzLmZpbmRQYXRoID0gZmluZFBhdGg7XG4iXX0= -(1) -}); diff --git a/addon-sdk/source/lib/diffpatcher/.travis.yml b/addon-sdk/source/lib/diffpatcher/.travis.yml deleted file mode 100644 index 780731a47..000000000 --- a/addon-sdk/source/lib/diffpatcher/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - 0.4 - - 0.5 - - 0.6 diff --git a/addon-sdk/source/lib/diffpatcher/History.md b/addon-sdk/source/lib/diffpatcher/History.md deleted file mode 100644 index d38978805..000000000 --- a/addon-sdk/source/lib/diffpatcher/History.md +++ /dev/null @@ -1,14 +0,0 @@ -# Changes - -## 1.0.1 / 2013-05-01 - - - Update method library version. - -## 1.0.0 / 2012-11-09 - - - Test integration for browsers. - - New method library. - -## 0.0.1 / 2012-10-22 - - - Initial release diff --git a/addon-sdk/source/lib/diffpatcher/License.md b/addon-sdk/source/lib/diffpatcher/License.md deleted file mode 100644 index ed76489a3..000000000 --- a/addon-sdk/source/lib/diffpatcher/License.md +++ /dev/null @@ -1,18 +0,0 @@ -Copyright 2012 Irakli Gozalishvili. All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/addon-sdk/source/lib/diffpatcher/Readme.md b/addon-sdk/source/lib/diffpatcher/Readme.md deleted file mode 100644 index 1520b1c37..000000000 --- a/addon-sdk/source/lib/diffpatcher/Readme.md +++ /dev/null @@ -1,70 +0,0 @@ -# diffpatcher - -[![Build Status](https://secure.travis-ci.org/Gozala/diffpatcher.png)](http://travis-ci.org/Gozala/diffpatcher) - -[![Browser support](https://ci.testling.com/Gozala/diffpatcher.png)](http://ci.testling.com/Gozala/diffpatcher) - -Diffpatcher is a small library that lets you treat hashes as if they were -git repositories. - -## diff - -Diff function that takes two hashes and returns delta hash. - -```js -var diff = require("diffpatcher/diff") - -diff({ a: { b: 1 }, c: { d: 2 } }, // hash#1 - { a: { e: 3 }, c: { d: 4 } }) // hash#2 - -// => { // delta -// a: { -// b: null, // - -// e: 3 // + -// }, -// c: { -// d: 4 // ± -// } -// } -``` - -As you can see from the example above `delta` makes no real distinction between -proprety upadate and property addition. Try to think of additions as an update -from `undefined` to whatever it's being updated to. - -## patch - -Patch fuction takes a `hash` and a `delta` and returns a new `hash` which is -just like orginial but with delta applied to it. Let's apply delta from the -previous example to the first hash from the same example - - -```js -var patch = require("diffpatcher/patch") - -patch({ a: { b: 1 }, c: { d: 2 } }, // hash#1 - { // delta - a: { - b: null, // - - e: 3 // + - }, - c: { - d: 4 // ± - } - }) - -// => { a: { e: 3 }, c: { d: 4 } } // hash#2 -``` - -That's about it really, just diffing hashes and applying thes diffs on them. - - -### rebase - -And as Linus mentioned everything in git can be expressed with `rebase`, that -also pretty much the case for `diffpatcher`. `rebase` takes `target` hash, -and rebases `parent` onto it with `diff` applied. - -## Install - - npm install diffpatcher diff --git a/addon-sdk/source/lib/diffpatcher/diff.js b/addon-sdk/source/lib/diffpatcher/diff.js deleted file mode 100644 index 967c137e6..000000000 --- a/addon-sdk/source/lib/diffpatcher/diff.js +++ /dev/null @@ -1,45 +0,0 @@ -"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 diff --git a/addon-sdk/source/lib/diffpatcher/index.js b/addon-sdk/source/lib/diffpatcher/index.js deleted file mode 100644 index 91ddba425..000000000 --- a/addon-sdk/source/lib/diffpatcher/index.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; - -exports.diff = require("./diff") -exports.patch = require("./patch") -exports.rebase = require("./rebase") diff --git a/addon-sdk/source/lib/diffpatcher/package.json b/addon-sdk/source/lib/diffpatcher/package.json deleted file mode 100644 index 54e085d2e..000000000 --- a/addon-sdk/source/lib/diffpatcher/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "diffpatcher", - "id": "diffpatcher", - "version": "1.2.0", - "description": "Utilities for diff-ing & patch-ing hashes", - "keywords": [ - "diff", "patch", "rebase", "hash", "changes", "versions" - ], - "author": "Irakli Gozalishvili (http://jeditoolkit.com)", - "homepage": "https://github.com/Gozala/diffpatcher", - "repository": { - "type": "git", - "url": "https://github.com/Gozala/diffpatcher.git", - "web": "https://github.com/Gozala/diffpatcher" - }, - "bugs": { - "url": "http://github.com/Gozala/diffpatcher/issues/" - }, - "dependencies": { - "method": "~2.0.0" - }, - "devDependencies": { - "test": "~0.x.0", - "phantomify": "~0.x.0", - "retape": "~0.x.0", - "tape": "~0.1.5" - }, - "main": "./index.js", - "scripts": { - "test": "npm run test-node && npm run test-browser", - "test-browser": "node ./node_modules/phantomify/bin/cmd.js ./test/common.js", - "test-node": "node ./test/common.js", - "test-tap": "node ./test/tap.js" - }, - "testling": { - "files": "test/tap.js", - "browsers": [ - "ie/9..latest", - "chrome/25..latest", - "firefox/20..latest", - "safari/6..latest", - "opera/11.0..latest", - "iphone/6..latest", - "ipad/6..latest", - "android-browser/4.2..latest" - ] - }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/Gozala/diffpatcher/License.md" - } - ] -} diff --git a/addon-sdk/source/lib/diffpatcher/patch.js b/addon-sdk/source/lib/diffpatcher/patch.js deleted file mode 100644 index 9271e8893..000000000 --- a/addon-sdk/source/lib/diffpatcher/patch.js +++ /dev/null @@ -1,21 +0,0 @@ -"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 diff --git a/addon-sdk/source/lib/diffpatcher/rebase.js b/addon-sdk/source/lib/diffpatcher/rebase.js deleted file mode 100644 index 03c756fee..000000000 --- a/addon-sdk/source/lib/diffpatcher/rebase.js +++ /dev/null @@ -1,36 +0,0 @@ -"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 diff --git a/addon-sdk/source/lib/diffpatcher/test/common.js b/addon-sdk/source/lib/diffpatcher/test/common.js deleted file mode 100644 index dbc79013c..000000000 --- a/addon-sdk/source/lib/diffpatcher/test/common.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; - -require("test").run(require("./index")) diff --git a/addon-sdk/source/lib/diffpatcher/test/diff.js b/addon-sdk/source/lib/diffpatcher/test/diff.js deleted file mode 100644 index d1d674005..000000000 --- a/addon-sdk/source/lib/diffpatcher/test/diff.js +++ /dev/null @@ -1,59 +0,0 @@ -"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") -} diff --git a/addon-sdk/source/lib/diffpatcher/test/index.js b/addon-sdk/source/lib/diffpatcher/test/index.js deleted file mode 100644 index c06407e7c..000000000 --- a/addon-sdk/source/lib/diffpatcher/test/index.js +++ /dev/null @@ -1,14 +0,0 @@ -"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") -} diff --git a/addon-sdk/source/lib/diffpatcher/test/patch.js b/addon-sdk/source/lib/diffpatcher/test/patch.js deleted file mode 100644 index dc2e38229..000000000 --- a/addon-sdk/source/lib/diffpatcher/test/patch.js +++ /dev/null @@ -1,83 +0,0 @@ -"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") -} diff --git a/addon-sdk/source/lib/diffpatcher/test/tap.js b/addon-sdk/source/lib/diffpatcher/test/tap.js deleted file mode 100644 index e550b82f5..000000000 --- a/addon-sdk/source/lib/diffpatcher/test/tap.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; - -require("retape")(require("./index")) diff --git a/addon-sdk/source/lib/framescript/FrameScriptManager.jsm b/addon-sdk/source/lib/framescript/FrameScriptManager.jsm deleted file mode 100644 index 1ce6ceb07..000000000 --- a/addon-sdk/source/lib/framescript/FrameScriptManager.jsm +++ /dev/null @@ -1,27 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"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']; diff --git a/addon-sdk/source/lib/framescript/content.jsm b/addon-sdk/source/lib/framescript/content.jsm deleted file mode 100644 index eaee26be3..000000000 --- a/addon-sdk/source/lib/framescript/content.jsm +++ /dev/null @@ -1,94 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { 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); - }, false); - - 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); - } -} diff --git a/addon-sdk/source/lib/framescript/context-menu.js b/addon-sdk/source/lib/framescript/context-menu.js deleted file mode 100644 index 3915b7cd8..000000000 --- a/addon-sdk/source/lib/framescript/context-menu.js +++ /dev/null @@ -1,215 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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.ownerDocument.defaultView.HTMLVideoElement && - !isVideoLoadingAudio(node); - -const isAudio = node => { - const {HTMLVideoElement, HTMLAudioElement} = node.ownerDocument.defaultView; - 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.ownerDocument.defaultView.getSelection().isCollapsed && - !node.matches(nonPageSelector)); - -// Reads `true` if node is in an iframe otherwise returns true. -parsers["reader/isFrame()"] = constant(node => - !!node.ownerDocument.defaultView.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; diff --git a/addon-sdk/source/lib/framescript/manager.js b/addon-sdk/source/lib/framescript/manager.js deleted file mode 100644 index 1f261e1fa..000000000 --- a/addon-sdk/source/lib/framescript/manager.js +++ /dev/null @@ -1,26 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; diff --git a/addon-sdk/source/lib/framescript/util.js b/addon-sdk/source/lib/framescript/util.js deleted file mode 100644 index fb6834608..000000000 --- a/addon-sdk/source/lib/framescript/util.js +++ /dev/null @@ -1,25 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -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.ownerDocument.defaultView); -exports.nodeToMessageManager = nodeToMessageManager; diff --git a/addon-sdk/source/lib/index.js b/addon-sdk/source/lib/index.js deleted file mode 100644 index e0032240a..000000000 --- a/addon-sdk/source/lib/index.js +++ /dev/null @@ -1,3 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ diff --git a/addon-sdk/source/lib/jetpack-id/index.js b/addon-sdk/source/lib/jetpack-id/index.js deleted file mode 100644 index 6c1493f1d..000000000 --- a/addon-sdk/source/lib/jetpack-id/index.js +++ /dev/null @@ -1,53 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Takes parsed `package.json` manifest and returns - * valid add-on id for it. - */ -function getID(manifest) { - manifest = manifest || {}; - - if (manifest.id) { - - if (typeof manifest.id !== "string") { - return null; - } - - // If manifest.id is already valid (as domain or GUID), use it - if (isValidAOMName(manifest.id)) { - return manifest.id; - } - // Otherwise, this ID is invalid so return `null` - return null; - } - - // If no `id` defined, turn `name` into a domain ID, - // as we transition to `name` being an id, similar to node/npm, but - // append a '@' to make it compatible with Firefox requirements - if (manifest.name) { - - if (typeof manifest.name !== "string") { - return null; - } - - var modifiedName = "@" + manifest.name; - return isValidAOMName(modifiedName) ? modifiedName : null; - } - - // If no `id` or `name` property, return null as this manifest - // is invalid - return null; -} - -module.exports = getID; - -/** - * Regex taken from XPIProvider.jsm in the Addon Manager to validate proper - * IDs that are able to be used. - * http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm#209 - */ -function isValidAOMName (s) { - return /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i.test(s || ""); -} diff --git a/addon-sdk/source/lib/jetpack-id/package.json b/addon-sdk/source/lib/jetpack-id/package.json deleted file mode 100644 index 62a1c73ba..000000000 --- a/addon-sdk/source/lib/jetpack-id/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "jetpack-id", - "version": "1.0.0", - "description": "Creates an ID from a Firefox Jetpack manifest", - "main": "index.js", - "repository": { - "type": "git", - "url": "http://github.com/jsantell/jetpack-id" - }, - "author": { - "name": "Jordan Santell", - "url": "http://github.com/jsantell" - }, - "license": "MPL-2.0", - "scripts": { - "test": "./node_modules/.bin/mocha --reporter spec --ui bdd" - }, - "keywords": [ - "jetpack", - "addon", - "mozilla", - "firefox" - ], - "devDependencies": { - "mocha": "*", - "chai": "*" - } -} diff --git a/addon-sdk/source/lib/method/.travis.yml b/addon-sdk/source/lib/method/.travis.yml deleted file mode 100644 index 780731a47..000000000 --- a/addon-sdk/source/lib/method/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - 0.4 - - 0.5 - - 0.6 diff --git a/addon-sdk/source/lib/method/History.md b/addon-sdk/source/lib/method/History.md deleted file mode 100644 index 95258c45f..000000000 --- a/addon-sdk/source/lib/method/History.md +++ /dev/null @@ -1,55 +0,0 @@ -# Changes - -## 1.0.2 / 2012-12-26 - - - Delegate to polymorphic methods from `.define` and `.implement` so, they - can be overidden. - -## 1.0.1 / 2012-11-11 - - - Fix issues with different `Error` types as they all inherit from - `Error`. - -## 1.0.0 / 2012-11-09 - - - Add browser test integration. - - Fix cross-browser incompatibilities & test failures. - - Add support for host objects. - - Add optional `hint` argument for method to ease debugging. - - Remove default implementation at definition time. - -## 0.1.1 / 2012-10-15 - - - Fix regression causing custom type implementation to be stored on objects. - -## 0.1.0 / 2012-10-15 - - - Remove dependency on name module. - - Implement fallback for engines that do not support ES5. - - Add support for built-in type extensions without extending their prototypes. - - Make API for default definitions more intuitive. - Skipping type argument now defines default: - - isFoo.define(function(value) { - return false - }) - - - Make exposed `define` and `implement` polymorphic. - - Removed dev dependency on swank-js. - - Primitive types `string, number, boolean` no longer inherit method - implementations from `Object`. - -## 0.0.3 / 2012-07-17 - - - Remove module boilerplate - -## 0.0.2 / 2012-06-26 - - - Name changes to make it less conflicting with other library conventions. - - Expose function version of `define` & `implement` methods. - - Expose `Null` and `Undefined` object holding implementations for an - associated types. - -## 0.0.1 / 2012-06-25 - - - Initial release diff --git a/addon-sdk/source/lib/method/License.md b/addon-sdk/source/lib/method/License.md deleted file mode 100644 index ed76489a3..000000000 --- a/addon-sdk/source/lib/method/License.md +++ /dev/null @@ -1,18 +0,0 @@ -Copyright 2012 Irakli Gozalishvili. All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/addon-sdk/source/lib/method/Readme.md b/addon-sdk/source/lib/method/Readme.md deleted file mode 100644 index 9584c9160..000000000 --- a/addon-sdk/source/lib/method/Readme.md +++ /dev/null @@ -1,117 +0,0 @@ -# method - -[![Build Status](https://secure.travis-ci.org/Gozala/method.png)](http://travis-ci.org/Gozala/method) - -Library provides an API for defining polymorphic methods that dispatch on the -first argument type. This provides a powerful way for decouple abstraction -interface definition from an actual implementation per type, without risks -of interference with other libraries. - -### Motivation - - - Provide a high-performance, dynamic polymorphism construct as an - alternative to existing object methods that does not provides any - mechanics for guarding against name conflicts. - - Allow independent extension of types, and implementations of methods - on types, by different parties. - -## Install - - npm install method - -## Use - -```js -var method = require("method") - -// Define `isWatchable` method that can be implemented for any type. -var isWatchable = method("isWatchable") - -// If you call it on any object it will -// throw as nothing implements that method yet. -//isWatchable({}) // => Exception: method is not implemented - -// If you define private method on `Object.prototype` -// all objects will inherit it. -Object.prototype[isWatchable] = function() { - return false; -} - -isWatchable({}) // => false - - -// Although `isWatchable` property above will be enumerable and there for -// may damage some assumbtions made by other libraries. There for it"s -// recomended to use built-in helpers methods that will define extension -// without breaking assumbtions made by other libraries: - -isWatchable.define(Object, function() { return false }) - - -// There are primitive types in JS that won"t inherit methods from Object: -isWatchable(null) // => Exception: method is not implemented - -// One could either implement methods for such types: -isWatchable.define(null, function() { return false }) -isWatchable.define(undefined, function() { return false }) - -// Or simply define default implementation: -isWatchable.define(function() { return false }) - -// Alternatively default implementation may be provided at creation: -isWatchable = method(function() { return false }) - -// Method dispatches on an first argument type. That allows us to create -// new types with an alternative implementations: -function Watchable() {} -isWatchable.define(Watchable, function() { return true }) - -// This will make all `Watchable` instances watchable! -isWatchable(new Watchable()) // => true - -// Arbitrary objects can also be extended to implement given method. For example -// any object can simply made watchable: -function watchable(object) { - return isWatchable.implement(objct, function() { return true }) -} - -isWatchable(watchable({})) // => true - -// Full protocols can be defined with such methods: -var observers = "observers@" + module.filename -var watchers = method("watchers") -var watch = method("watch") -var unwatch = method("unwatch") - -watchers.define(Watchable, function(target) { - return target[observers] || (target[observers] = []) -}) - -watch.define(Watchable, function(target, watcher) { - var observers = watchers(target) - if (observers.indexOf(watcher) < 0) observers.push(watcher) - return target -}) -unwatch.define(Watchable, function(target, watcher) { - var observers = watchers(target) - var index = observers.indexOf(watcher) - if (observers.indexOf(watcher) >= 0) observers.unshift(watcher) - return target -}) - -// Define type Port that inherits form Watchable - -function Port() {} -Port.prototype = Object.create(Watchable.prototype) - -var emit = method("emit") -emit.define(Port, function(port, message) { - watchers(port).slice().forEach(function(watcher) { - watcher(message) - }) -}) - -var p = new Port() -watch(p, console.log) -emit(p, "hello world") // => info: "hello world" -``` diff --git a/addon-sdk/source/lib/method/core.js b/addon-sdk/source/lib/method/core.js deleted file mode 100644 index a6a5261e6..000000000 --- a/addon-sdk/source/lib/method/core.js +++ /dev/null @@ -1,225 +0,0 @@ -"use strict"; - -var defineProperty = Object.defineProperty || function(object, name, property) { - object[name] = property.value - return object -} - -// Shortcut for `Object.prototype.toString` for faster access. -var typefy = Object.prototype.toString - -// Map to for jumping from typeof(value) to associated type prefix used -// as a hash in the map of builtin implementations. -var types = { "function": "Object", "object": "Object" } - -// Array is used to save method implementations for the host objects in order -// to avoid extending them with non-primitive values that could cause leaks. -var host = [] -// Hash map is used to save method implementations for builtin types in order -// to avoid extending their prototypes. This also allows to share method -// implementations for types across diff contexts / frames / compartments. -var builtin = {} - -function Primitive() {} -function ObjectType() {} -ObjectType.prototype = new Primitive() -function ErrorType() {} -ErrorType.prototype = new ObjectType() - -var Default = builtin.Default = Primitive.prototype -var Null = builtin.Null = new Primitive() -var Void = builtin.Void = new Primitive() -builtin.String = new Primitive() -builtin.Number = new Primitive() -builtin.Boolean = new Primitive() - -builtin.Object = ObjectType.prototype -builtin.Error = ErrorType.prototype - -builtin.EvalError = new ErrorType() -builtin.InternalError = new ErrorType() -builtin.RangeError = new ErrorType() -builtin.ReferenceError = new ErrorType() -builtin.StopIteration = new ErrorType() -builtin.SyntaxError = new ErrorType() -builtin.TypeError = new ErrorType() -builtin.URIError = new ErrorType() - - -function Method(hint) { - /** - Private Method is a callable private name that dispatches on the first - arguments same named Method: - - method(object, ...rest) => object[method](...rest) - - Optionally hint string may be provided that will be used in generated names - to ease debugging. - - ## Example - - var foo = Method() - - // Implementation for any types - foo.define(function(value, arg1, arg2) { - // ... - }) - - // Implementation for a specific type - foo.define(BarType, function(bar, arg1, arg2) { - // ... - }) - **/ - - // Create an internal unique name if `hint` is provided it is used to - // prefix name to ease debugging. - var name = (hint || "") + "#" + Math.random().toString(32).substr(2) - - function dispatch(value) { - // Method dispatches on type of the first argument. - // If first argument is `null` or `void` associated implementation is - // looked up in the `builtin` hash where implementations for built-ins - // are stored. - var type = null - var method = value === null ? Null[name] : - value === void(0) ? Void[name] : - // Otherwise attempt to use method with a generated private - // `name` that is supposedly in the prototype chain of the - // `target`. - value[name] || - // Otherwise assume it's one of the built-in type instances, - // in which case implementation is stored in a `builtin` hash. - // Attempt to find a implementation for the given built-in - // via constructor name and method name. - ((type = builtin[(value.constructor || "").name]) && - type[name]) || - // Otherwise assume it's a host object. For host objects - // actual method implementations are stored in the `host` - // array and only index for the implementation is stored - // in the host object's prototype chain. This avoids memory - // leaks that otherwise could happen when saving JS objects - // on host object. - host[value["!" + name] || void(0)] || - // Otherwise attempt to lookup implementation for builtins by - // a type of the value. This basically makes sure that all - // non primitive values will delegate to an `Object`. - ((type = builtin[types[typeof(value)]]) && type[name]) - - - // If method implementation for the type is still not found then - // just fallback for default implementation. - method = method || Default[name] - - - // If implementation is still not found (which also means there is no - // default) just throw an error with a descriptive message. - if (!method) throw TypeError("Type does not implements method: " + name) - - // If implementation was found then just delegate. - return method.apply(method, arguments) - } - - // Make `toString` of the dispatch return a private name, this enables - // method definition without sugar: - // - // var method = Method() - // object[method] = function() { /***/ } - dispatch.toString = function toString() { return name } - - // Copy utility methods for convenient API. - dispatch.implement = implementMethod - dispatch.define = defineMethod - - return dispatch -} - -// Create method shortcuts form functions. -var defineMethod = function defineMethod(Type, lambda) { - return define(this, Type, lambda) -} -var implementMethod = function implementMethod(object, lambda) { - return implement(this, object, lambda) -} - -// Define `implement` and `define` polymorphic methods to allow definitions -// and implementations through them. -var implement = Method("implement") -var define = Method("define") - - -function _implement(method, object, lambda) { - /** - Implements `Method` for the given `object` with a provided `implementation`. - Calling `Method` with `object` as a first argument will dispatch on provided - implementation. - **/ - return defineProperty(object, method.toString(), { - enumerable: false, - configurable: false, - writable: false, - value: lambda - }) -} - -function _define(method, Type, lambda) { - /** - Defines `Method` for the given `Type` with a provided `implementation`. - Calling `Method` with a first argument of this `Type` will dispatch on - provided `implementation`. If `Type` is a `Method` default implementation - is defined. If `Type` is a `null` or `undefined` `Method` is implemented - for that value type. - **/ - - // Attempt to guess a type via `Object.prototype.toString.call` hack. - var type = Type && typefy.call(Type.prototype) - - // If only two arguments are passed then `Type` is actually an implementation - // for a default type. - if (!lambda) Default[method] = Type - // If `Type` is `null` or `void` store implementation accordingly. - else if (Type === null) Null[method] = lambda - else if (Type === void(0)) Void[method] = lambda - // If `type` hack indicates built-in type and type has a name us it to - // store a implementation into associated hash. If hash for this type does - // not exists yet create one. - else if (type !== "[object Object]" && Type.name) { - var Bulitin = builtin[Type.name] || (builtin[Type.name] = new ObjectType()) - Bulitin[method] = lambda - } - // If `type` hack indicates an object, that may be either object or any - // JS defined "Class". If name of the constructor is `Object`, assume it's - // built-in `Object` and store implementation accordingly. - else if (Type.name === "Object") - builtin.Object[method] = lambda - // Host objects are pain!!! Every browser does some crazy stuff for them - // So far all browser seem to not implement `call` method for host object - // constructors. If that is a case here, assume it's a host object and - // store implementation in a `host` array and store `index` in the array - // in a `Type.prototype` itself. This avoids memory leaks that could be - // caused by storing JS objects on a host objects. - else if (Type.call === void(0)) { - var index = host.indexOf(lambda) - if (index < 0) index = host.push(lambda) - 1 - // Prefix private name with `!` so it can be dispatched from the method - // without type checks. - implement("!" + method, Type.prototype, index) - } - // If Got that far `Type` is user defined JS `Class`. Define private name - // as hidden property on it's prototype. - else - implement(method, Type.prototype, lambda) -} - -// And provided implementations for a polymorphic equivalents. -_define(define, _define) -_define(implement, _implement) - -// Define exports on `Method` as it's only thing being exported. -Method.implement = implement -Method.define = define -Method.Method = Method -Method.method = Method -Method.builtin = builtin -Method.host = host - -module.exports = Method diff --git a/addon-sdk/source/lib/method/package.json b/addon-sdk/source/lib/method/package.json deleted file mode 100644 index 7bb004e28..000000000 --- a/addon-sdk/source/lib/method/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "method", - "id": "method", - "version": "1.0.2", - "description": "Functional polymorphic method dispatch", - "keywords": [ - "method", - "dispatch", - "protocol", - "polymorphism", - "type dispatch" - ], - "author": "Irakli Gozalishvili (http://jeditoolkit.com)", - "homepage": "https://github.com/Gozala/method", - "main": "./core.js", - "repository": { - "type": "git", - "url": "https://github.com/Gozala/method.git", - "web": "https://github.com/Gozala/method" - }, - "bugs": { - "url": "http://github.com/Gozala/method/issues/" - }, - "devDependencies": { - "test": "~0.x.0", - "repl-utils": "~2.0.1", - "phantomify": "~0.1.0" - }, - "scripts": { - "test": "npm run test-node && npm run test-browser", - "test-browser": "node ./node_modules/phantomify/bin/cmd.js ./test/browser.js", - "test-node": "node ./test/common.js", - "repl": "node node_modules/repl-utils" - }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/Gozala/method/License.md" - } - ] -} diff --git a/addon-sdk/source/lib/method/test/browser.js b/addon-sdk/source/lib/method/test/browser.js deleted file mode 100644 index 7c8e6cd52..000000000 --- a/addon-sdk/source/lib/method/test/browser.js +++ /dev/null @@ -1,20 +0,0 @@ -"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) diff --git a/addon-sdk/source/lib/method/test/common.js b/addon-sdk/source/lib/method/test/common.js deleted file mode 100644 index 0418c3a23..000000000 --- a/addon-sdk/source/lib/method/test/common.js +++ /dev/null @@ -1,272 +0,0 @@ -"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) diff --git a/addon-sdk/source/lib/mozilla-toolkit-versioning/index.js b/addon-sdk/source/lib/mozilla-toolkit-versioning/index.js deleted file mode 100644 index 2f607c880..000000000 --- a/addon-sdk/source/lib/mozilla-toolkit-versioning/index.js +++ /dev/null @@ -1,112 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var versionParse = require('./lib/utils').versionParse; - -var COMPARATORS = ['>=', '<=', '>', '<', '=', '~', '^']; - -exports.parse = function (input) { - input = input || ''; - input = input.trim(); - if (!input) - throw new Error('`parse` argument must be a populated string.'); - - // Handle the "*" case - if (input === "*") { - return { min: undefined, max: undefined }; - } - - var inputs = input.split(' '); - var min; - var max; - - // 1.2.3 - 2.3.4 - if (inputs.length === 3 && inputs[1] === '-') { - return { min: inputs[0], max: inputs[2] }; - } - - inputs.forEach(function (input) { - var parsed = parseExpression(input); - var version = parsed.version; - var comparator = parsed.comparator; - - // 1.2.3 - if (inputs.length === 1 && !comparator) - min = max = version; - - // Parse min - if (~comparator.indexOf('>')) { - if (~comparator.indexOf('=')) - min = version; // >=1.2.3 - else - min = increment(version); // >1.2.3 - } - else if (~comparator.indexOf('<')) { - if (~comparator.indexOf('=')) - max = version; // <=1.2.3 - else - max = decrement(version); // <1.2.3 - } - }); - - return { - min: min, - max : max - }; -}; - -function parseExpression (input) { - for (var i = 0; i < COMPARATORS.length; i++) - if (~input.indexOf(COMPARATORS[i])) - return { - comparator: COMPARATORS[i], - version: input.substr(COMPARATORS[i].length) - }; - return { version: input, comparator: '' }; -} - -/** - * Takes a version string ('1.2.3') and returns a version string - * that'll parse as one less than the input string ('1.2.3.-1'). - * - * @param {String} vString - * @return {String} - */ -function decrement (vString) { - return vString + (vString.charAt(vString.length - 1) === '.' ? '' : '.') + '-1'; -} -exports.decrement = decrement; - -/** - * Takes a version string ('1.2.3') and returns a version string - * that'll parse as greater than the input string by the smallest margin - * possible ('1.2.3.1'). - * listed as number-A, string-B, number-C, string-D in - * Mozilla's Toolkit Format. - * https://developer.mozilla.org/en-US/docs/Toolkit_version_format - * - * @param {String} vString - * @return {String} - */ -function increment (vString) { - var match = versionParse(vString); - var a = match[1]; - var b = match[2]; - var c = match[3]; - var d = match[4]; - var lastPos = vString.length - 1; - var lastChar = vString.charAt(lastPos); - - if (!b) { - return vString + (lastChar === '.' ? '' : '.') + '1'; - } - if (!c) { - return vString + '1'; - } - if (!d) { - return vString.substr(0, lastPos) + (++lastChar); - } - return vString.substr(0, lastPos) + String.fromCharCode(lastChar.charCodeAt(0) + 1); -} -exports.increment = increment; diff --git a/addon-sdk/source/lib/mozilla-toolkit-versioning/lib/utils.js b/addon-sdk/source/lib/mozilla-toolkit-versioning/lib/utils.js deleted file mode 100644 index e068085c0..000000000 --- a/addon-sdk/source/lib/mozilla-toolkit-versioning/lib/utils.js +++ /dev/null @@ -1,15 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Breaks up a version string into the 4 components - * defined in: - * https://developer.mozilla.org/en-US/docs/Toolkit_version_format - * @params {String} val - * @return {String} - */ -function versionParse (val) { - return val.match(/^([0-9\.]*)([a-zA-Z]*)([0-9\.]*)([a-zA-Z]*)$/); -} -exports.versionParse = versionParse; diff --git a/addon-sdk/source/lib/mozilla-toolkit-versioning/package.json b/addon-sdk/source/lib/mozilla-toolkit-versioning/package.json deleted file mode 100644 index d9b0424e5..000000000 --- a/addon-sdk/source/lib/mozilla-toolkit-versioning/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "mozilla-toolkit-versioning", - "version": "0.0.2", - "description": "Parser for Mozilla's toolkit version format", - "main": "index.js", - "scripts": { - "test": "./node_modules/.bin/mocha --reporter spec --ui bdd" - }, - "repository": { - "type": "git", - "url": "git://github.com/jsantell/mozilla-toolkit-versioning.git" - }, - "author": "Jordan Santell", - "license": "MIT", - "dependencies": {}, - "devDependencies": { - "mocha": "^1.21.4", - "chai": "^1.9.1", - "mozilla-version-comparator": "^1.0.2" - } -} diff --git a/addon-sdk/source/lib/node/os.js b/addon-sdk/source/lib/node/os.js deleted file mode 100644 index 1c41a9246..000000000 --- a/addon-sdk/source/lib/node/os.js +++ /dev/null @@ -1,90 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci } = require('chrome'); -const system = require('../sdk/system'); -const runtime = require('../sdk/system/runtime'); -const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm"); -const isWindows = system.platform === 'win32'; -const endianness = ((new Uint32Array((new Uint8Array([1,2,3,4])).buffer))[0] === 0x04030201) ? 'LE' : 'BE'; - -XPCOMUtils.defineLazyGetter(this, "oscpu", () => { - try { - return Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu; - } catch (e) { - return ""; - } -}); - -XPCOMUtils.defineLazyGetter(this, "hostname", () => { - try { - // On some platforms (Linux according to try), this service does not exist and fails. - return Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName; - } catch (e) { - return ""; - } -}); - -/** - * Returns a path to a temp directory - */ -exports.tmpdir = () => system.pathFor('TmpD'); - -/** - * Returns the endianness of the architecture: either 'LE' or 'BE' - */ -exports.endianness = () => endianness; - -/** - * Returns hostname of the machine - */ -exports.hostname = () => hostname; - -/** - * Name of the OS type - * Possible values: - * https://developer.mozilla.org/en/OS_TARGET - */ -exports.type = () => runtime.OS; - -/** - * Name of the OS Platform in lower case string. - * Possible values: - * https://developer.mozilla.org/en/OS_TARGET - */ -exports.platform = () => system.platform; - -/** - * Type of processor architecture running: - * 'arm', 'ia32', 'x86', 'x64' - */ -exports.arch = () => system.architecture; - -/** - * Returns the operating system release. - */ -exports.release = () => { - let match = oscpu.match(/(\d[\.\d]*)/); - return match && match.length > 1 ? match[1] : oscpu; -}; - -/** - * Returns EOL character for the OS - */ -exports.EOL = isWindows ? '\r\n' : '\n'; - -/** - * Returns [0, 0, 0], as this is not implemented. - */ -exports.loadavg = () => [0, 0, 0]; - -['uptime', 'totalmem', 'freemem', 'cpus'].forEach(method => { - exports[method] = () => { throw new Error('os.' + method + ' is not supported.'); }; -}); diff --git a/addon-sdk/source/lib/sdk/addon/bootstrap.js b/addon-sdk/source/lib/sdk/addon/bootstrap.js deleted file mode 100644 index 0397d91e5..000000000 --- a/addon-sdk/source/lib/sdk/addon/bootstrap.js +++ /dev/null @@ -1,182 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { 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. - 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; diff --git a/addon-sdk/source/lib/sdk/addon/events.js b/addon-sdk/source/lib/sdk/addon/events.js deleted file mode 100644 index 45bada6e1..000000000 --- a/addon-sdk/source/lib/sdk/addon/events.js +++ /dev/null @@ -1,56 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 || {})); -} diff --git a/addon-sdk/source/lib/sdk/addon/host.js b/addon-sdk/source/lib/sdk/addon/host.js deleted file mode 100644 index 91aa0e869..000000000 --- a/addon-sdk/source/lib/sdk/addon/host.js +++ /dev/null @@ -1,12 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -exports.request = {}; -exports.response = {}; diff --git a/addon-sdk/source/lib/sdk/addon/installer.js b/addon-sdk/source/lib/sdk/addon/installer.js deleted file mode 100644 index bb8cf8d16..000000000 --- a/addon-sdk/source/lib/sdk/addon/installer.js +++ /dev/null @@ -1,121 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm"); -const { defer } = require("../core/promise"); -const { setTimeout } = require("../timers"); - -/** - * `install` method error codes: - * - * https://developer.mozilla.org/en/Addons/Add-on_Manager/AddonManager#AddonInstall_errors - */ -exports.ERROR_NETWORK_FAILURE = AddonManager.ERROR_NETWORK_FAILURE; -exports.ERROR_INCORRECT_HASH = AddonManager.ERROR_INCORRECT_HASH; -exports.ERROR_CORRUPT_FILE = AddonManager.ERROR_CORRUPT_FILE; -exports.ERROR_FILE_ACCESS = AddonManager.ERROR_FILE_ACCESS; - -/** - * Immediatly install an addon. - * - * @param {String} xpiPath - * file path to an xpi file to install - * @return {Promise} - * A promise resolved when the addon is finally installed. - * Resolved with addon id as value or rejected with an error code. - */ -exports.install = function install(xpiPath) { - let { promise, resolve, reject } = defer(); - - // Create nsIFile for the xpi file - let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile); - try { - file.initWithPath(xpiPath); - } - catch(e) { - reject(exports.ERROR_FILE_ACCESS); - return promise; - } - - // Listen for installation end - let listener = { - onInstallEnded: function(aInstall, aAddon) { - aInstall.removeListener(listener); - // Bug 749745: on FF14+, onInstallEnded is called just before `startup()` - // is called, but we expect to resolve the promise only after it. - // As startup is called synchronously just after onInstallEnded, - // a simple setTimeout(0) is enough - setTimeout(resolve, 0, aAddon.id); - }, - onInstallFailed: function (aInstall) { - aInstall.removeListener(listener); - reject(aInstall.error); - }, - onDownloadFailed: function(aInstall) { - this.onInstallFailed(aInstall); - } - }; - - // Order AddonManager to install the addon - AddonManager.getInstallForFile(file, function(install) { - if (install.error == 0) { - install.addListener(listener); - install.install(); - } else { - reject(install.error); - } - }); - - return promise; -}; - -exports.uninstall = function uninstall(addonId) { - let { promise, resolve, reject } = defer(); - - // Listen for uninstallation end - let listener = { - onUninstalled: function onUninstalled(aAddon) { - if (aAddon.id != addonId) - return; - AddonManager.removeAddonListener(listener); - resolve(); - } - }; - AddonManager.addAddonListener(listener); - - // Order Addonmanager to uninstall the addon - getAddon(addonId).then(addon => addon.uninstall(), reject); - - return promise; -}; - -exports.disable = function disable(addonId) { - return getAddon(addonId).then(addon => { - addon.userDisabled = true; - return addonId; - }); -}; - -exports.enable = function enabled(addonId) { - return getAddon(addonId).then(addon => { - addon.userDisabled = false; - return addonId; - }); -}; - -exports.isActive = function isActive(addonId) { - return getAddon(addonId).then(addon => addon.isActive && !addon.appDisabled); -}; - -const getAddon = function getAddon (id) { - let { promise, resolve, reject } = defer(); - AddonManager.getAddonByID(id, addon => addon ? resolve(addon) : reject()); - return promise; -} -exports.getAddon = getAddon; diff --git a/addon-sdk/source/lib/sdk/addon/manager.js b/addon-sdk/source/lib/sdk/addon/manager.js deleted file mode 100644 index 7ac0a7d6e..000000000 --- a/addon-sdk/source/lib/sdk/addon/manager.js +++ /dev/null @@ -1,18 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"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; diff --git a/addon-sdk/source/lib/sdk/addon/runner.js b/addon-sdk/source/lib/sdk/addon/runner.js deleted file mode 100644 index 3977a04e4..000000000 --- a/addon-sdk/source/lib/sdk/addon/runner.js +++ /dev/null @@ -1,180 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { once } = require('../system/events'); -const { exit, env, staticArgs } = require('../system'); -const { when: unload } = require('../system/unload'); -const globals = require('../system/globals'); -const xulApp = require('../system/xul-app'); -const { get } = require('../preferences/service'); -const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. - getService(Ci.nsIAppShellService); -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, "BrowserToolboxProcess", function () { - return Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}). - BrowserToolboxProcess; -}); - -// Initializes default preferences -function setDefaultPrefs(prefsURI) { - const prefs = Cc['@mozilla.org/preferences-service;1']. - getService(Ci.nsIPrefService). - QueryInterface(Ci.nsIPrefBranch2); - 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). - then(null, 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); - }).then(null, 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)) { - BrowserToolboxProcess.init({ addonID: 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); - }); -} diff --git a/addon-sdk/source/lib/sdk/addon/window.js b/addon-sdk/source/lib/sdk/addon/window.js deleted file mode 100644 index 93ed1d8dc..000000000 --- a/addon-sdk/source/lib/sdk/addon/window.js +++ /dev/null @@ -1,66 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -const { Ci, Cc } = require("chrome"); -const { make: makeWindow, getHiddenWindow } = require("../window/utils"); -const { create: makeFrame, getDocShell } = require("../frame/utils"); -const { defer } = require("../core/promise"); -const { when: unload } = require("../system/unload"); -const cfxArgs = require("../test/options"); - -var addonPrincipal = Cc["@mozilla.org/systemprincipal;1"]. - createInstance(Ci.nsIPrincipal); - -var hiddenWindow = getHiddenWindow(); - -if (cfxArgs.parseable) { - console.info("hiddenWindow document.documentURI:" + - hiddenWindow.document.documentURI); - console.info("hiddenWindow document.readyState:" + - hiddenWindow.document.readyState); -} - -// Once Bug 565388 is fixed and shipped we'll be able to make invisible, -// permanent docShells. Meanwhile we create hidden top level window and -// use it's docShell. -var frame = makeFrame(hiddenWindow.document, { - nodeName: "iframe", - namespaceURI: "http://www.w3.org/1999/xhtml", - allowJavascript: true, - allowPlugins: true -}) -var docShell = getDocShell(frame); -var eventTarget = docShell.chromeEventHandler; - -// We need to grant docShell system principals in order to load XUL document -// from data URI into it. -docShell.createAboutBlankContentViewer(addonPrincipal); - -// Get a reference to the DOM window of the given docShell and load -// such document into that would allow us to create XUL iframes, that -// are necessary for hidden frames etc.. -var window = docShell.contentViewer.DOMDocument.defaultView; -window.location = "data:application/vnd.mozilla.xul+xml;charset=utf-8,"; - -// Create a promise that is delivered once add-on window is interactive, -// used by add-on runner to defer add-on loading until window is ready. -var { promise, resolve } = defer(); -eventTarget.addEventListener("DOMContentLoaded", function handler(event) { - eventTarget.removeEventListener("DOMContentLoaded", handler, false); - resolve(); -}, false); - -exports.ready = promise; -exports.window = window; - -// Still close window on unload to claim memory back early. -unload(function() { - window.close() - frame.parentNode.removeChild(frame); -}); diff --git a/addon-sdk/source/lib/sdk/base64.js b/addon-sdk/source/lib/sdk/base64.js deleted file mode 100644 index a07b302e0..000000000 --- a/addon-sdk/source/lib/sdk/base64.js +++ /dev/null @@ -1,47 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"); - -// Passing an empty object as second argument to avoid scope's pollution -// (devtools loader injects these symbols as global and prevent using -// const here) -var { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {}); - -function isUTF8(charset) { - let type = typeof charset; - - if (type === "undefined") - return false; - - if (type === "string" && charset.toLowerCase() === "utf-8") - return true; - - throw new Error("The charset argument can be only 'utf-8'"); -} - -function toOctetChar(c) { - return String.fromCharCode(c.charCodeAt(0) & 0xFF); -} - -exports.decode = function (data, charset) { - if (isUTF8(charset)) - return decodeURIComponent(escape(atob(data))) - - return atob(data); -} - -exports.encode = function (data, charset) { - if (isUTF8(charset)) - return btoa(unescape(encodeURIComponent(data))) - - data = data.replace(/[^\x00-\xFF]/g, toOctetChar); - return btoa(data); -} diff --git a/addon-sdk/source/lib/sdk/browser/events.js b/addon-sdk/source/lib/sdk/browser/events.js deleted file mode 100644 index f91119031..000000000 --- a/addon-sdk/source/lib/sdk/browser/events.js +++ /dev/null @@ -1,20 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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)); diff --git a/addon-sdk/source/lib/sdk/clipboard.js b/addon-sdk/source/lib/sdk/clipboard.js deleted file mode 100644 index 048d5f2f1..000000000 --- a/addon-sdk/source/lib/sdk/clipboard.js +++ /dev/null @@ -1,337 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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", - "engines": { - // TODO Fennec Support 789757 - "Firefox": "*", - "SeaMonkey": "*", - "Thunderbird": "*" - } -}; - -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. -*/ -const kAllowableFlavors = [ - "text/unicode", - "text/html", - "image/png" - /* CURRENTLY UNSUPPORTED FLAVORS - "text/plain", - "image/jpg", - "image/jpeg", - "image/gif", - "text/x-moz-text-internal", - "AOLMAIL", - "application/x-moz-file", - "text/x-moz-url", - "text/x-moz-url-data", - "text/x-moz-url-desc", - "text/x-moz-url-priv", - "application/x-moz-nativeimage", - "application/x-moz-nativehtml", - "application/x-moz-file-promise-url", - "application/x-moz-file-promise-dest-filename", - "application/x-moz-file-promise", - "application/x-moz-file-promise-dir" - */ -]; - -/* -Aliases for common flavors. Not all flavors will -get an alias. New aliases must be approved by a -Jetpack API druid. -*/ -const kFlavorMap = [ - { short: "text", long: "text/unicode" }, - { short: "html", long: "text/html" }, - { short: "image", long: "image/png" } -]; - -var clipboardService = Cc["@mozilla.org/widget/clipboard;1"]. - getService(Ci.nsIClipboard); - -var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]. - getService(Ci.nsIClipboardHelper); - -var imageTools = Cc["@mozilla.org/image/tools;1"]. - getService(Ci.imgITools); - -exports.set = function(aData, aDataType) { - - let options = { - data: aData, - datatype: aDataType || "text" - }; - - // If `aDataType` is not given or if it's "image", the data is parsed as - // data URL to detect a better datatype - if (aData && (!aDataType || aDataType === "image")) { - try { - let dataURL = new DataURL(aData); - - options.datatype = dataURL.mimeType; - options.data = dataURL.data; - } - catch (e) { - // Ignore invalid URIs - if (e.name !== "URIError") { - throw e; - } - } - } - - options = apiUtils.validateOptions(options, { - data: { - is: ["string"] - }, - datatype: { - is: ["string"] - } - }); - - let flavor = fromJetpackFlavor(options.datatype); - - if (!flavor) - throw new Error("Invalid flavor for " + options.datatype); - - // Additional checks for using the simple case - if (flavor == "text/unicode") { - clipboardHelper.copyString(options.data); - return true; - } - - // Below are the more complex cases where we actually have to work with a - // nsITransferable object - var xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - if (!xferable) - throw new Error("Couldn't set the clipboard due to an internal error " + - "(couldn't create a Transferable object)."); - // Bug 769440: Starting with FF16, transferable have to be inited - if ("init" in xferable) - xferable.init(null); - - switch (flavor) { - case "text/html": - // add text/html flavor - let str = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - - str.data = options.data; - xferable.addDataFlavor(flavor); - xferable.setTransferData(flavor, str, str.data.length * 2); - - // add a text/unicode flavor (html converted to plain text) - str = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - let converter = Cc["@mozilla.org/feed-textconstruct;1"]. - createInstance(Ci.nsIFeedTextConstruct); - - converter.type = "html"; - converter.text = options.data; - str.data = converter.plainText(); - xferable.addDataFlavor("text/unicode"); - xferable.setTransferData("text/unicode", str, str.data.length * 2); - break; - - // Set images to the clipboard is not straightforward, to have an idea how - // it works on platform side, see: - // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsCopySupport.cpp?rev=7857c5bff017#530 - case "image/png": - let image = options.data; - - let container = {}; - - try { - let input = Cc["@mozilla.org/io/string-input-stream;1"]. - createInstance(Ci.nsIStringInputStream); - - input.setData(image, image.length); - - imageTools.decodeImageData(input, flavor, container); - } - catch (e) { - throw new Error("Unable to decode data given in a valid image."); - } - - // Store directly the input stream makes the cliboard's data available - // for Firefox but not to the others application or to the OS. Therefore, - // a `nsISupportsInterfacePointer` object that reference an `imgIContainer` - // with the image is needed. - var imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"]. - createInstance(Ci.nsISupportsInterfacePointer); - - imgPtr.data = container.value; - - xferable.addDataFlavor(flavor); - xferable.setTransferData(flavor, imgPtr, -1); - - break; - default: - throw new Error("Unable to handle the flavor " + flavor + "."); - } - - // TODO: Not sure if this will ever actually throw. -zpao - try { - clipboardService.setData( - xferable, - null, - clipboardService.kGlobalClipboard - ); - } catch (e) { - throw new Error("Couldn't set clipboard data due to an internal error: " + e); - } - return true; -}; - - -exports.get = function(aDataType) { - let options = { - datatype: aDataType - }; - - // Figure out the best data type for the clipboard's data, if omitted - if (!aDataType) { - if (~currentFlavors().indexOf("image")) - options.datatype = "image"; - else - options.datatype = "text"; - } - - options = apiUtils.validateOptions(options, { - datatype: { - is: ["string"] - } - }); - - var xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - if (!xferable) - throw new Error("Couldn't set the clipboard due to an internal error " + - "(couldn't create a Transferable object)."); - // Bug 769440: Starting with FF16, transferable have to be inited - if ("init" in xferable) - xferable.init(null); - - var flavor = fromJetpackFlavor(options.datatype); - - // Ensure that the user hasn't requested a flavor that we don't support. - if (!flavor) - throw new Error("Getting the clipboard with the flavor '" + flavor + - "' is not supported."); - - // TODO: Check for matching flavor first? Probably not worth it. - - xferable.addDataFlavor(flavor); - // Get the data into our transferable. - clipboardService.getData( - xferable, - clipboardService.kGlobalClipboard - ); - - var data = {}; - var dataLen = {}; - try { - xferable.getTransferData(flavor, data, dataLen); - } catch (e) { - // Clipboard doesn't contain data in flavor, return null. - return null; - } - - // There's no data available, return. - if (data.value === null) - return null; - - // TODO: Add flavors here as we support more in kAllowableFlavors. - switch (flavor) { - case "text/unicode": - case "text/html": - data = data.value.QueryInterface(Ci.nsISupportsString).data; - break; - case "image/png": - let dataURL = new DataURL(); - - dataURL.mimeType = flavor; - dataURL.base64 = true; - - let image = data.value; - - // Due to the differences in how images could be stored in the clipboard - // the checks below are needed. The clipboard could already provide the - // image as byte streams, but also as pointer, or as image container. - // If it's not possible obtain a byte stream, the function returns `null`. - if (image instanceof Ci.nsISupportsInterfacePointer) - image = image.data; - - if (image instanceof Ci.imgIContainer) - image = imageTools.encodeImage(image, flavor); - - if (image instanceof Ci.nsIInputStream) { - let binaryStream = Cc["@mozilla.org/binaryinputstream;1"]. - createInstance(Ci.nsIBinaryInputStream); - - binaryStream.setInputStream(image); - - dataURL.data = binaryStream.readBytes(binaryStream.available()); - - data = dataURL.toString(); - } - else - data = null; - - break; - default: - data = null; - } - - return data; -}; - -function currentFlavors() { - // Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each. - // This doesn't seem like the most efficient way, but we can't get - // confirmation for specific flavors any other way. This is supposed to be - // an inexpensive call, so performance shouldn't be impacted (much). - var currentFlavors = []; - for (var flavor of kAllowableFlavors) { - var matches = clipboardService.hasDataMatchingFlavors( - [flavor], - 1, - clipboardService.kGlobalClipboard - ); - if (matches) - currentFlavors.push(toJetpackFlavor(flavor)); - } - return currentFlavors; -}; - -Object.defineProperty(exports, "currentFlavors", { get : currentFlavors }); - -// SUPPORT FUNCTIONS //////////////////////////////////////////////////////// - -function toJetpackFlavor(aFlavor) { - for (let flavorMap of kFlavorMap) - if (flavorMap.long == aFlavor) - return flavorMap.short; - // Return null in the case where we don't match - return null; -} - -function fromJetpackFlavor(aJetpackFlavor) { - // TODO: Handle proper flavors better - for (let flavorMap of kFlavorMap) - if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor) - return flavorMap.long; - // Return null in the case where we don't match. - return null; -} diff --git a/addon-sdk/source/lib/sdk/console/plain-text.js b/addon-sdk/source/lib/sdk/console/plain-text.js deleted file mode 100644 index 0e44cf106..000000000 --- a/addon-sdk/source/lib/sdk/console/plain-text.js +++ /dev/null @@ -1,78 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci, Cu, Cr } = require("chrome"); -const self = require("../self"); -const prefs = require("../preferences/service"); -const { merge } = require("../util/object"); -const { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {}); - -const DEFAULT_LOG_LEVEL = "error"; -const ADDON_LOG_LEVEL_PREF = "extensions." + self.id + ".sdk.console.logLevel"; -const SDK_LOG_LEVEL_PREF = "extensions.sdk.console.logLevel"; - -var logLevel = DEFAULT_LOG_LEVEL; -function setLogLevel() { - logLevel = prefs.get(ADDON_LOG_LEVEL_PREF, - prefs.get(SDK_LOG_LEVEL_PREF, - DEFAULT_LOG_LEVEL)); -} -setLogLevel(); - -var logLevelObserver = { - QueryInterface: function(iid) { - if (!iid.equals(Ci.nsIObserver) && - !iid.equals(Ci.nsISupportsWeakReference) && - !iid.equals(Ci.nsISupports)) - throw Cr.NS_ERROR_NO_INTERFACE; - return this; - }, - observe: function(subject, topic, data) { - setLogLevel(); - } -}; -var branch = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService). - getBranch(null); -branch.addObserver(ADDON_LOG_LEVEL_PREF, logLevelObserver, true); -branch.addObserver(SDK_LOG_LEVEL_PREF, logLevelObserver, true); - -function PlainTextConsole(print, innerID) { - - let consoleOptions = { - prefix: self.name, - maxLogLevel: logLevel, - dump: print, - innerID: innerID, - consoleID: "addon/" + self.id - }; - let console = new ConsoleAPI(consoleOptions); - - // As we freeze the console object, we can't modify this property afterward - Object.defineProperty(console, "maxLogLevel", { - get: function() { - return logLevel; - } - }); - - // We defined the `__exposedProps__` in our console chrome object. - // - // Meanwhile we're investigating with the platform team if `__exposedProps__` - // are needed, or are just a left-over. - - console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) { - exposed[prop] = "r"; - return exposed; - }, {}); - - Object.freeze(console); - return console; -}; -exports.PlainTextConsole = PlainTextConsole; diff --git a/addon-sdk/source/lib/sdk/console/traceback.js b/addon-sdk/source/lib/sdk/console/traceback.js deleted file mode 100644 index be0fb7b94..000000000 --- a/addon-sdk/source/lib/sdk/console/traceback.js +++ /dev/null @@ -1,86 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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, components } = require("chrome"); -const { parseStack, sourceURI } = require("toolkit/loader"); -const { readURISync } = require("../net/url"); - -function safeGetFileLine(path, line) { - try { - var scheme = require("../url").URL(path).scheme; - // TODO: There should be an easier, more accurate way to figure out - // what's the case here. - if (!(scheme == "http" || scheme == "https")) - return readURISync(path).split("\n")[line - 1]; - } catch (e) {} - return null; -} - -function nsIStackFramesToJSON(frame) { - var stack = []; - - while (frame) { - if (frame.filename) { - stack.unshift({ - fileName: sourceURI(frame.filename), - lineNumber: frame.lineNumber, - name: frame.name - }); - } - frame = frame.caller; - } - - return stack; -}; - -var fromException = exports.fromException = function fromException(e) { - if (e instanceof Ci.nsIException) - return nsIStackFramesToJSON(e.location); - if (e.stack && e.stack.length) - return parseStack(e.stack); - if (e.fileName && typeof(e.lineNumber == "number")) - return [{fileName: sourceURI(e.fileName), - lineNumber: e.lineNumber, - name: null}]; - return []; -}; - -var get = exports.get = function get() { - return nsIStackFramesToJSON(components.stack.caller); -}; - -var format = exports.format = function format(tbOrException) { - if (tbOrException === undefined) { - tbOrException = get(); - tbOrException.pop(); - } - - var tb; - if (typeof(tbOrException) == "object" && - tbOrException.constructor.name == "Array") - tb = tbOrException; - else - tb = fromException(tbOrException); - - var lines = ["Traceback (most recent call last):"]; - - tb.forEach( - function(frame) { - if (!(frame.fileName || frame.lineNumber || frame.name)) - return; - - lines.push(' File "' + frame.fileName + '", line ' + - frame.lineNumber + ', in ' + frame.name); - var sourceLine = safeGetFileLine(frame.fileName, frame.lineNumber); - if (sourceLine) - lines.push(' ' + sourceLine.trim()); - }); - - return lines.join("\n"); -}; diff --git a/addon-sdk/source/lib/sdk/content/content-worker.js b/addon-sdk/source/lib/sdk/content/content-worker.js deleted file mode 100644 index 0a8225733..000000000 --- a/addon-sdk/source/lib/sdk/content/content-worker.js +++ /dev/null @@ -1,305 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; - } -}); diff --git a/addon-sdk/source/lib/sdk/content/content.js b/addon-sdk/source/lib/sdk/content/content.js deleted file mode 100644 index 9655223a3..000000000 --- a/addon-sdk/source/lib/sdk/content/content.js +++ /dev/null @@ -1,17 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; - } -}); diff --git a/addon-sdk/source/lib/sdk/content/context-menu.js b/addon-sdk/source/lib/sdk/content/context-menu.js deleted file mode 100644 index 2955e2f09..000000000 --- a/addon-sdk/source/lib/sdk/content/context-menu.js +++ /dev/null @@ -1,408 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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.nsIDOMHTMLAppletElement, - 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.ownerDocument.defaultView.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.ownerDocument.defaultView.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.ownerDocument.defaultView; - 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.ownerDocument.defaultView); - 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.ownerDocument.defaultView); - 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); - } -}); diff --git a/addon-sdk/source/lib/sdk/content/events.js b/addon-sdk/source/lib/sdk/content/events.js deleted file mode 100644 index c085b6179..000000000 --- a/addon-sdk/source/lib/sdk/content/events.js +++ /dev/null @@ -1,57 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -const { Ci } = require("chrome"); -const { open } = require("../event/dom"); -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]); diff --git a/addon-sdk/source/lib/sdk/content/l10n-html.js b/addon-sdk/source/lib/sdk/content/l10n-html.js deleted file mode 100644 index f324623dc..000000000 --- a/addon-sdk/source/lib/sdk/content/l10n-html.js +++ /dev/null @@ -1,133 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"); -const core = require("../l10n/core"); -const { loadSheet, removeSheet } = require("../stylesheet/utils"); -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, - false); - - 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, - false); -} - -// 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); diff --git a/addon-sdk/source/lib/sdk/content/loader.js b/addon-sdk/source/lib/sdk/content/loader.js deleted file mode 100644 index e4f0dd2aa..000000000 --- a/addon-sdk/source/lib/sdk/content/loader.js +++ /dev/null @@ -1,74 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "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); diff --git a/addon-sdk/source/lib/sdk/content/mod.js b/addon-sdk/source/lib/sdk/content/mod.js deleted file mode 100644 index 81fe9ee42..000000000 --- a/addon-sdk/source/lib/sdk/content/mod.js +++ /dev/null @@ -1,68 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -const { Ci } = require("chrome"); -const { dispatcher } = require("../util/dispatcher"); -const { add, remove, iterator } = require("../lang/weak-set"); - -var getTargetWindow = dispatcher("getTargetWindow"); - -getTargetWindow.define(function (target) { - if (target instanceof Ci.nsIDOMWindow) - return target; - if (target instanceof Ci.nsIDOMDocument) - return target.defaultView || null; - - return null; -}); - -exports.getTargetWindow = getTargetWindow; - -var attachTo = dispatcher("attachTo"); -exports.attachTo = attachTo; - -var detachFrom = dispatcher("detatchFrom"); -exports.detachFrom = detachFrom; - -function attach(modification, target) { - if (!modification) - return; - - let window = getTargetWindow(target); - - attachTo(modification, window); - - // modification are stored per content; `window` reference can still be the - // same even if the content is changed, therefore `document` is used instead. - add(modification, window.document); -} -exports.attach = attach; - -function detach(modification, target) { - if (!modification) - return; - - if (target) { - let window = getTargetWindow(target); - detachFrom(modification, window); - remove(modification, window.document); - } - else { - let documents = iterator(modification); - for (let document of documents) { - let window = document.defaultView; - // The window might have already gone away - if (!window) - continue; - detachFrom(modification, document.defaultView); - remove(modification, document); - } - } -} -exports.detach = detach; diff --git a/addon-sdk/source/lib/sdk/content/page-mod.js b/addon-sdk/source/lib/sdk/content/page-mod.js deleted file mode 100644 index 8ff9b1e7b..000000000 --- a/addon-sdk/source/lib/sdk/content/page-mod.js +++ /dev/null @@ -1,236 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { getAttachEventType } = require('../content/utils'); -const { Class } = require('../core/heritage'); -const { Disposable } = require('../core/disposable'); -const { WeakReference } = require('../core/reference'); -const { WorkerChild } = require('./worker-child'); -const { EventTarget } = require('../event/target'); -const { on, emit, once, setListeners } = require('../event/core'); -const { on: domOn, removeListener: domOff } = require('../dom/events'); -const { isRegExp, isUndefined } = require('../lang/type'); -const { merge } = require('../util/object'); -const { isBrowser, getFrames } = require('../window/utils'); -const { getTabs, getURI: getTabURI } = require('../tabs/utils'); -const { ignoreWindow } = require('../private-browsing/utils'); -const { Style } = require("../stylesheet/style"); -const { attach, detach } = require("../content/mod"); -const { has, hasAny } = require("../util/array"); -const { Rules } = require("../util/rules"); -const { List, addListItem, removeListItem } = require('../util/list'); -const { when } = require("../system/unload"); -const { uuid } = require('../util/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 (has(this.attachTo, '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 (has(mod.attachTo, "top") && modMatchesURI(mod, uri)) - onContent(mod, window); - if (has(mod.attachTo, "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 && !has(mod.attachTo, "top")) - return; - // Is a frame document and `frame` is not set, ignore - if (!isTopDocument && !has(mod.attachTo, "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(); -}); diff --git a/addon-sdk/source/lib/sdk/content/page-worker.js b/addon-sdk/source/lib/sdk/content/page-worker.js deleted file mode 100644 index e9e741120..000000000 --- a/addon-sdk/source/lib/sdk/content/page-worker.js +++ /dev/null @@ -1,154 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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'); -const { data } = require("../self"); -const { once } = require("../dom/events"); -const { getAttachEventType } = require("./utils"); -const { Rules } = require('../util/rules'); -const { uuid } = require('../util/uuid'); -const { WorkerChild } = require("./worker-child"); -const { Cc, Ci, Cu } = require("chrome"); -const { observe } = require("../event/chrome"); -const { on } = require("../event/core"); - -const appShell = Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci.nsIAppShellService); - -const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm"); - -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"]) -}); - -on(observe(DOC_INSERTED), "data", ({ target }) => { - let page = Array.from(pages.values()).find(p => p.contentWindow.document === target); - if (!page) - return; - - if (getAttachEventType(page.options) == DOC_INSERTED) - page.attachWorker(); -}); - -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(); -}); diff --git a/addon-sdk/source/lib/sdk/content/sandbox.js b/addon-sdk/source/lib/sdk/content/sandbox.js deleted file mode 100644 index 096ba5c87..000000000 --- a/addon-sdk/source/lib/sdk/content/sandbox.js +++ /dev/null @@ -1,426 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -module.metadata = { - 'stability': 'unstable' -}; - -const { Class } = require('../core/heritage'); -const { EventTarget } = require('../event/target'); -const { on, off, emit } = require('../event/core'); -const { events } = require('./sandbox/events'); -const { requiresAddonGlobal } = require('./utils'); -const { delay: async } = require('../lang/functional'); -const { Ci, Cu, Cc } = require('chrome'); -const timer = require('../timers'); -const { URL } = require('../url'); -const { sandbox, evaluate, load } = require('../loader/sandbox'); -const { merge } = require('../util/object'); -const { getTabForContentWindowNoShim } = require('../tabs/utils'); -const { getInnerId } = require('../window/utils'); -const { PlainTextConsole } = require('../console/plain-text'); -const { data } = require('../self');const { isChildLoader } = require('../remote/core'); -// 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'), - markTimeline: genPropDesc('markTimeline'), - timeline: genPropDesc('timeline'), - timelineEnd: genPropDesc('timelineEnd'), - 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}); -} diff --git a/addon-sdk/source/lib/sdk/content/sandbox/events.js b/addon-sdk/source/lib/sdk/content/sandbox/events.js deleted file mode 100644 index d6f7eb004..000000000 --- a/addon-sdk/source/lib/sdk/content/sandbox/events.js +++ /dev/null @@ -1,12 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -const events = {}; -exports.events = events; diff --git a/addon-sdk/source/lib/sdk/content/tab-events.js b/addon-sdk/source/lib/sdk/content/tab-events.js deleted file mode 100644 index 9e244a853..000000000 --- a/addon-sdk/source/lib/sdk/content/tab-events.js +++ /dev/null @@ -1,58 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { 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 }); -} diff --git a/addon-sdk/source/lib/sdk/content/thumbnail.js b/addon-sdk/source/lib/sdk/content/thumbnail.js deleted file mode 100644 index 783615fc6..000000000 --- a/addon-sdk/source/lib/sdk/content/thumbnail.js +++ /dev/null @@ -1,51 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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'; diff --git a/addon-sdk/source/lib/sdk/content/utils.js b/addon-sdk/source/lib/sdk/content/utils.js deleted file mode 100644 index 90995a614..000000000 --- a/addon-sdk/source/lib/sdk/content/utils.js +++ /dev/null @@ -1,105 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { merge } = require('../util/object'); -var { data } = require('../self'); -var assetsURI = data.url(); -var isArray = Array.isArray; -var method = require('../../method/core'); -var { uuid } = require('../util/uuid'); - -const isAddonContent = ({ contentURL }) => - contentURL && data.url(contentURL).startsWith(assetsURI); - -exports.isAddonContent = isAddonContent; - -function hasContentScript({ contentScript, contentScriptFile }) { - return (isArray(contentScript) ? contentScript.length > 0 : - !!contentScript) || - (isArray(contentScriptFile) ? contentScriptFile.length > 0 : - !!contentScriptFile); -} -exports.hasContentScript = hasContentScript; - -function requiresAddonGlobal(model) { - return model.injectInDocument || (isAddonContent(model) && !hasContentScript(model)); -} -exports.requiresAddonGlobal = requiresAddonGlobal; - -function getAttachEventType(model) { - if (!model) return null; - let when = model.contentScriptWhen; - return requiresAddonGlobal(model) ? 'document-element-inserted' : - when === 'start' ? 'document-element-inserted' : - when === 'ready' ? 'DOMContentLoaded' : - when === 'end' ? 'load' : - null; -} -exports.getAttachEventType = getAttachEventType; - -var attach = method('worker-attach'); -exports.attach = attach; - -var connect = method('worker-connect'); -exports.connect = connect; - -var detach = method('worker-detach'); -exports.detach = detach; - -var destroy = method('worker-destroy'); -exports.destroy = destroy; - -function WorkerHost (workerFor) { - // Define worker properties that just proxy to underlying worker - return ['postMessage', 'port', 'url', 'tab'].reduce(function(proto, name) { - // Use descriptor properties instead so we can call - // the worker function in the context of the worker so we - // don't have to create new functions with `fn.bind(worker)` - let descriptorProp = { - value: function (...args) { - let worker = workerFor(this); - return worker[name].apply(worker, args); - } - }; - - let accessorProp = { - get: function () { return workerFor(this)[name]; }, - set: function (value) { workerFor(this)[name] = value; } - }; - - Object.defineProperty(proto, name, merge({ - enumerable: true, - configurable: false, - }, isDescriptor(name) ? descriptorProp : accessorProp)); - return proto; - }, {}); - - function isDescriptor (prop) { - return ~['postMessage'].indexOf(prop); - } -} -exports.WorkerHost = WorkerHost; - -function makeChildOptions(options) { - function makeStringArray(arrayOrValue) { - if (!arrayOrValue) - return []; - return [].concat(arrayOrValue).map(String); - } - - return { - id: String(uuid()), - contentScript: makeStringArray(options.contentScript), - contentScriptFile: makeStringArray(options.contentScriptFile), - contentScriptOptions: options.contentScriptOptions ? - JSON.stringify(options.contentScriptOptions) : - null, - } -} -exports.makeChildOptions = makeChildOptions; diff --git a/addon-sdk/source/lib/sdk/content/worker-child.js b/addon-sdk/source/lib/sdk/content/worker-child.js deleted file mode 100644 index dbf65a933..000000000 --- a/addon-sdk/source/lib/sdk/content/worker-child.js +++ /dev/null @@ -1,158 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { merge } = require('../util/object'); -const { Class } = require('../core/heritage'); -const { emit } = require('../event/core'); -const { EventTarget } = require('../event/target'); -const { getInnerId, getByInnerId } = require('../window/utils'); -const { instanceOf, isObject } = require('../lang/type'); -const system = require('../system/events'); -const { when } = require('../system/unload'); -const { WorkerSandbox } = require('./sandbox'); -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); -}); diff --git a/addon-sdk/source/lib/sdk/content/worker.js b/addon-sdk/source/lib/sdk/content/worker.js deleted file mode 100644 index 39b940a88..000000000 --- a/addon-sdk/source/lib/sdk/content/worker.js +++ /dev/null @@ -1,180 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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('../event/core'); -const { omit, merge } = require('../util/object'); -const { Class } = require('../core/heritage'); -const { method } = require('../lang/functional'); -const { getInnerId } = require('../window/utils'); -const { EventTarget } = require('../event/target'); -const { isPrivate } = require('../private-browsing/utils'); -const { getTabForBrowser, getTabForContentWindowNoShim, getBrowserForTab } = require('../tabs/utils'); -const { attach, connect, detach, destroy, makeChildOptions } = require('./utils'); -const { ensure } = require('../system/unload'); -const { on: observe } = require('../system/events'); -const { Ci, Cu } = require('chrome'); -const { modelFor: tabFor } = require('sdk/model/core'); -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); -}); diff --git a/addon-sdk/source/lib/sdk/context-menu.js b/addon-sdk/source/lib/sdk/context-menu.js deleted file mode 100644 index 004c642d4..000000000 --- a/addon-sdk/source/lib/sdk/context-menu.js +++ /dev/null @@ -1,1188 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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", - "engines": { - // TODO Fennec support Bug 788334 - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Class, mix } = require("./core/heritage"); -const { addCollectionProperty } = require("./util/collection"); -const { ns } = require("./core/namespace"); -const { validateOptions, getTypeOf } = require("./deprecated/api-utils"); -const { URL, isValidURI } = require("./url"); -const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils"); -const { isBrowser, getInnerId } = require("./window/utils"); -const { MatchPattern } = require("./util/match-pattern"); -const { EventTarget } = require("./event/target"); -const { emit } = require('./event/core'); -const { when } = require('./system/unload'); -const { contract: loaderContract } = require('./content/loader'); -const { omit } = require('./util/object'); -const self = require('./self') -const { remoteRequire, processes } = require('./remote/parent'); -remoteRequire('sdk/content/context-menu'); - -// All user items we add have this class. -const ITEM_CLASS = "addon-context-menu-item"; - -// Items in the top-level context menu also have this class. -const TOPLEVEL_ITEM_CLASS = "addon-context-menu-item-toplevel"; - -// Items in the overflow submenu also have this class. -const OVERFLOW_ITEM_CLASS = "addon-context-menu-item-overflow"; - -// The class of the menu separator that separates standard context menu items -// from our user items. -const SEPARATOR_CLASS = "addon-context-menu-separator"; - -// If more than this number of items are added to the context menu, all items -// overflow into a "Jetpack" submenu. -const OVERFLOW_THRESH_DEFAULT = 10; -const OVERFLOW_THRESH_PREF = - "extensions.addon-sdk.context-menu.overflowThreshold"; - -// The label of the overflow sub-xul:menu. -// -// TODO: Localize these. -const OVERFLOW_MENU_LABEL = "Add-ons"; -const OVERFLOW_MENU_ACCESSKEY = "A"; - -// The class of the overflow sub-xul:menu. -const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu"; - -// The class of the overflow submenu's xul:menupopup. -const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup"; - -// Holds private properties for API objects -var internal = ns(); - -// A little hacky but this is the last process ID that last opened the context -// menu -var lastContextProcessId = null; - -var uuidModule = require('./util/uuid'); -function uuid() { - return uuidModule.uuid().toString(); -} - -function getScheme(spec) { - try { - return URL(spec).scheme; - } - catch(e) { - return null; - } -} - -var Context = Class({ - initialize: function() { - internal(this).id = uuid(); - }, - - // Returns the node that made this context current - adjustPopupNode: function adjustPopupNode(popupNode) { - return popupNode; - }, - - // Returns whether this context is current for the current node - isCurrent: function isCurrent(state) { - return state; - } -}); - -// Matches when the context-clicked node doesn't have any of -// NON_PAGE_CONTEXT_ELTS in its ancestors -var PageContext = Class({ - extends: Context, - - serialize: function() { - return { - id: internal(this).id, - type: "PageContext", - args: [] - } - } -}); -exports.PageContext = PageContext; - -// Matches when there is an active selection in the window -var SelectionContext = Class({ - extends: Context, - - serialize: function() { - return { - id: internal(this).id, - type: "SelectionContext", - args: [] - } - } -}); -exports.SelectionContext = SelectionContext; - -// Matches when the context-clicked node or any of its ancestors matches the -// selector given -var SelectorContext = Class({ - extends: Context, - - initialize: function initialize(selector) { - Context.prototype.initialize.call(this); - let options = validateOptions({ selector: selector }, { - selector: { - is: ["string"], - msg: "selector must be a string." - } - }); - internal(this).selector = options.selector; - }, - - serialize: function() { - return { - id: internal(this).id, - type: "SelectorContext", - args: [internal(this).selector] - } - } -}); -exports.SelectorContext = SelectorContext; - -// Matches when the page url matches any of the patterns given -var URLContext = Class({ - extends: Context, - - initialize: function initialize(patterns) { - Context.prototype.initialize.call(this); - patterns = Array.isArray(patterns) ? patterns : [patterns]; - - try { - internal(this).patterns = patterns.map(p => new MatchPattern(p)); - } - catch (err) { - throw new Error("Patterns must be a string, regexp or an array of " + - "strings or regexps: " + err); - } - }, - - isCurrent: function isCurrent(url) { - return internal(this).patterns.some(p => p.test(url)); - }, - - serialize: function() { - return { - id: internal(this).id, - type: "URLContext", - args: [] - } - } -}); -exports.URLContext = URLContext; - -// Matches when the user-supplied predicate returns true -var PredicateContext = Class({ - extends: Context, - - initialize: function initialize(predicate) { - Context.prototype.initialize.call(this); - let options = validateOptions({ predicate: predicate }, { - predicate: { - is: ["function"], - msg: "predicate must be a function." - } - }); - internal(this).predicate = options.predicate; - }, - - isCurrent: function isCurrent(state) { - return internal(this).predicate(state); - }, - - serialize: function() { - return { - id: internal(this).id, - type: "PredicateContext", - args: [] - } - } -}); -exports.PredicateContext = PredicateContext; - -function removeItemFromArray(array, item) { - return array.filter(i => i !== item); -} - -// Converts anything that isn't false, null or undefined into a string -function stringOrNull(val) { - return val ? String(val) : val; -} - -// Shared option validation rules for Item, Menu, and Separator -var baseItemRules = { - parentMenu: { - is: ["object", "undefined"], - ok: function (v) { - if (!v) - return true; - return (v instanceof ItemContainer) || (v instanceof Menu); - }, - msg: "parentMenu must be a Menu or not specified." - }, - context: { - is: ["undefined", "object", "array"], - ok: function (v) { - if (!v) - return true; - let arr = Array.isArray(v) ? v : [v]; - return arr.every(o => o instanceof Context); - }, - msg: "The 'context' option must be a Context object or an array of " + - "Context objects." - }, - onMessage: { - is: ["function", "undefined"] - }, - contentScript: loaderContract.rules.contentScript, - contentScriptFile: loaderContract.rules.contentScriptFile -}; - -var labelledItemRules = mix(baseItemRules, { - label: { - map: stringOrNull, - is: ["string"], - ok: v => !!v, - msg: "The item must have a non-empty string label." - }, - accesskey: { - map: stringOrNull, - is: ["string", "undefined", "null"], - ok: (v) => { - if (!v) { - return true; - } - return typeof v == "string" && v.length === 1; - }, - msg: "The item must have a single character accesskey, or no accesskey." - }, - image: { - map: stringOrNull, - is: ["string", "undefined", "null"], - ok: function (url) { - if (!url) - return true; - return isValidURI(url); - }, - msg: "Image URL validation failed" - } -}); - -// Additional validation rules for Item -var itemRules = mix(labelledItemRules, { - data: { - map: stringOrNull, - is: ["string", "undefined", "null"] - } -}); - -// Additional validation rules for Menu -var menuRules = mix(labelledItemRules, { - items: { - is: ["array", "undefined"], - ok: function (v) { - if (!v) - return true; - return v.every(function (item) { - return item instanceof BaseItem; - }); - }, - msg: "items must be an array, and each element in the array must be an " + - "Item, Menu, or Separator." - } -}); - -// Returns true if any contexts match. If there are no contexts then a -// PageContext is tested instead -function hasMatchingContext(contexts, addonInfo) { - for (let context of contexts) { - if (!(internal(context).id in addonInfo.contextStates)) { - console.error("Missing state for context " + internal(context).id + " this is an error in the SDK modules."); - return false; - } - if (!context.isCurrent(addonInfo.contextStates[internal(context).id])) - return false; - } - - return true; -} - -// Tests whether an item should be visible or not based on its contexts and -// content scripts -function isItemVisible(item, addonInfo, usePageWorker) { - if (!item.context.length) { - if (!addonInfo.hasWorker) - return usePageWorker ? addonInfo.pageContext : true; - } - - if (!hasMatchingContext(item.context, addonInfo)) - return false; - - let context = addonInfo.workerContext; - if (typeof(context) === "string" && context != "") - item.label = context; - - return !!context; -} - -// Called when an item is clicked to send out click events to the content -// scripts -function itemActivated(item, clickedNode) { - let items = [internal(item).id]; - let data = item.data; - - while (item.parentMenu) { - item = item.parentMenu; - items.push(internal(item).id); - } - - let process = processes.getById(lastContextProcessId); - if (process) - process.port.emit('sdk/contextmenu/activateitems', items, data); -} - -function serializeItem(item) { - return { - id: internal(item).id, - contexts: item.context.map(c => c.serialize()), - contentScript: item.contentScript, - contentScriptFile: item.contentScriptFile, - }; -} - -// All things that appear in the context menu extend this -var BaseItem = Class({ - initialize: function initialize() { - internal(this).id = uuid(); - - internal(this).contexts = []; - if ("context" in internal(this).options && internal(this).options.context) { - let contexts = internal(this).options.context; - if (Array.isArray(contexts)) { - for (let context of contexts) - internal(this).contexts.push(context); - } - else { - internal(this).contexts.push(contexts); - } - } - - let parentMenu = internal(this).options.parentMenu; - if (!parentMenu) - parentMenu = contentContextMenu; - - parentMenu.addItem(this); - - Object.defineProperty(this, "contentScript", { - enumerable: true, - value: internal(this).options.contentScript - }); - - // Resolve URIs here as tests may have overriden self - let files = internal(this).options.contentScriptFile; - if (files) { - if (!Array.isArray(files)) - files = [files]; - files = files.map(self.data.url); - } - internal(this).options.contentScriptFile = files; - Object.defineProperty(this, "contentScriptFile", { - enumerable: true, - value: internal(this).options.contentScriptFile - }); - - // Notify all frames of this new item - sendItems([serializeItem(this)]); - }, - - destroy: function destroy() { - if (internal(this).destroyed) - return; - - // Tell all existing frames that this item has been destroyed - processes.port.emit("sdk/contextmenu/destroyitems", [internal(this).id]); - - if (this.parentMenu) - this.parentMenu.removeItem(this); - - internal(this).destroyed = true; - }, - - get context() { - let contexts = internal(this).contexts.slice(0); - contexts.add = (context) => { - internal(this).contexts.push(context); - // Notify all frames that this item has changed - sendItems([serializeItem(this)]); - }; - contexts.remove = (context) => { - internal(this).contexts = internal(this).contexts.filter(c => { - return c != context; - }); - // Notify all frames that this item has changed - sendItems([serializeItem(this)]); - }; - return contexts; - }, - - set context(val) { - internal(this).contexts = val.slice(0); - // Notify all frames that this item has changed - sendItems([serializeItem(this)]); - }, - - get parentMenu() { - return internal(this).parentMenu; - }, -}); - -function workerMessageReceived(process, id, args) { - if (internal(this).id != id) - return; - - emit(this, ...JSON.parse(args)); -} - -// All things that have a label on the context menu extend this -var LabelledItem = Class({ - extends: BaseItem, - implements: [ EventTarget ], - - initialize: function initialize(options) { - BaseItem.prototype.initialize.call(this); - EventTarget.prototype.initialize.call(this, options); - - internal(this).messageListener = workerMessageReceived.bind(this); - processes.port.on('sdk/worker/event', internal(this).messageListener); - }, - - destroy: function destroy() { - if (internal(this).destroyed) - return; - - processes.port.off('sdk/worker/event', internal(this).messageListener); - - BaseItem.prototype.destroy.call(this); - }, - - get label() { - return internal(this).options.label; - }, - - set label(val) { - internal(this).options.label = val; - - MenuManager.updateItem(this); - }, - - get accesskey() { - return internal(this).options.accesskey; - }, - - set accesskey(val) { - internal(this).options.accesskey = val; - - MenuManager.updateItem(this); - }, - - get image() { - return internal(this).options.image; - }, - - set image(val) { - internal(this).options.image = val; - - MenuManager.updateItem(this); - }, - - get data() { - return internal(this).options.data; - }, - - set data(val) { - internal(this).options.data = val; - } -}); - -var Item = Class({ - extends: LabelledItem, - - initialize: function initialize(options) { - internal(this).options = validateOptions(options, itemRules); - - LabelledItem.prototype.initialize.call(this, options); - }, - - toString: function toString() { - return "[object Item \"" + this.label + "\"]"; - }, - - get data() { - return internal(this).options.data; - }, - - set data(val) { - internal(this).options.data = val; - - MenuManager.updateItem(this); - }, -}); -exports.Item = Item; - -var ItemContainer = Class({ - initialize: function initialize() { - internal(this).children = []; - }, - - destroy: function destroy() { - // Destroys the entire hierarchy - for (let item of internal(this).children) - item.destroy(); - }, - - addItem: function addItem(item) { - let oldParent = item.parentMenu; - - // Don't just call removeItem here as that would remove the corresponding - // UI element which is more costly than just moving it to the right place - if (oldParent) - internal(oldParent).children = removeItemFromArray(internal(oldParent).children, item); - - let after = null; - let children = internal(this).children; - if (children.length > 0) - after = children[children.length - 1]; - - children.push(item); - internal(item).parentMenu = this; - - // If there was an old parent then we just have to move the item, otherwise - // it needs to be created - if (oldParent) - MenuManager.moveItem(item, after); - else - MenuManager.createItem(item, after); - }, - - removeItem: function removeItem(item) { - // If the item isn't a child of this menu then ignore this call - if (item.parentMenu !== this) - return; - - MenuManager.removeItem(item); - - internal(this).children = removeItemFromArray(internal(this).children, item); - internal(item).parentMenu = null; - }, - - get items() { - return internal(this).children.slice(0); - }, - - set items(val) { - // Validate the arguments before making any changes - if (!Array.isArray(val)) - throw new Error(menuOptionRules.items.msg); - - for (let item of val) { - if (!(item instanceof BaseItem)) - throw new Error(menuOptionRules.items.msg); - } - - // Remove the old items and add the new ones - for (let item of internal(this).children) - this.removeItem(item); - - for (let item of val) - this.addItem(item); - }, -}); - -var Menu = Class({ - extends: LabelledItem, - implements: [ItemContainer], - - initialize: function initialize(options) { - internal(this).options = validateOptions(options, menuRules); - - LabelledItem.prototype.initialize.call(this, options); - ItemContainer.prototype.initialize.call(this); - - if (internal(this).options.items) { - for (let item of internal(this).options.items) - this.addItem(item); - } - }, - - destroy: function destroy() { - ItemContainer.prototype.destroy.call(this); - LabelledItem.prototype.destroy.call(this); - }, - - toString: function toString() { - return "[object Menu \"" + this.label + "\"]"; - }, -}); -exports.Menu = Menu; - -var Separator = Class({ - extends: BaseItem, - - initialize: function initialize(options) { - internal(this).options = validateOptions(options, baseItemRules); - - BaseItem.prototype.initialize.call(this); - }, - - toString: function toString() { - return "[object Separator]"; - } -}); -exports.Separator = Separator; - -// Holds items for the content area context menu -var contentContextMenu = ItemContainer(); -exports.contentContextMenu = contentContextMenu; - -function getContainerItems(container) { - let items = []; - for (let item of internal(container).children) { - items.push(serializeItem(item)); - if (item instanceof Menu) - items = items.concat(getContainerItems(item)); - } - return items; -} - -// Notify all frames of these new or changed items -function sendItems(items) { - processes.port.emit("sdk/contextmenu/createitems", items); -} - -// Called when a new process is created and needs to get the current list of items -function remoteItemRequest(process) { - let items = getContainerItems(contentContextMenu); - if (items.length == 0) - return; - - process.port.emit("sdk/contextmenu/createitems", items); -} -processes.forEvery(remoteItemRequest); - -when(function() { - contentContextMenu.destroy(); -}); - -// App specific UI code lives here, it should handle populating the context -// menu and passing clicks etc. through to the items. - -function countVisibleItems(nodes) { - return Array.reduce(nodes, function(sum, node) { - return node.hidden ? sum : sum + 1; - }, 0); -} - -var MenuWrapper = Class({ - initialize: function initialize(winWrapper, items, contextMenu) { - this.winWrapper = winWrapper; - this.window = winWrapper.window; - this.items = items; - this.contextMenu = contextMenu; - this.populated = false; - this.menuMap = new Map(); - - // updateItemVisibilities will run first, updateOverflowState will run after - // all other instances of this module have run updateItemVisibilities - this._updateItemVisibilities = this.updateItemVisibilities.bind(this); - this.contextMenu.addEventListener("popupshowing", this._updateItemVisibilities, true); - this._updateOverflowState = this.updateOverflowState.bind(this); - this.contextMenu.addEventListener("popupshowing", this._updateOverflowState, false); - }, - - destroy: function destroy() { - this.contextMenu.removeEventListener("popupshowing", this._updateOverflowState, false); - this.contextMenu.removeEventListener("popupshowing", this._updateItemVisibilities, true); - - if (!this.populated) - return; - - // If we're getting unloaded at runtime then we must remove all the - // generated XUL nodes - let oldParent = null; - for (let item of internal(this.items).children) { - let xulNode = this.getXULNodeForItem(item); - oldParent = xulNode.parentNode; - oldParent.removeChild(xulNode); - } - - if (oldParent) - this.onXULRemoved(oldParent); - }, - - get separator() { - return this.contextMenu.querySelector("." + SEPARATOR_CLASS); - }, - - get overflowMenu() { - return this.contextMenu.querySelector("." + OVERFLOW_MENU_CLASS); - }, - - get overflowPopup() { - return this.contextMenu.querySelector("." + OVERFLOW_POPUP_CLASS); - }, - - get topLevelItems() { - return this.contextMenu.querySelectorAll("." + TOPLEVEL_ITEM_CLASS); - }, - - get overflowItems() { - return this.contextMenu.querySelectorAll("." + OVERFLOW_ITEM_CLASS); - }, - - getXULNodeForItem: function getXULNodeForItem(item) { - return this.menuMap.get(item); - }, - - // Recurses through the item hierarchy creating XUL nodes for everything - populate: function populate(menu) { - for (let i = 0; i < internal(menu).children.length; i++) { - let item = internal(menu).children[i]; - let after = i === 0 ? null : internal(menu).children[i - 1]; - this.createItem(item, after); - - if (item instanceof Menu) - this.populate(item); - } - }, - - // Recurses through the menu setting the visibility of items. Returns true - // if any of the items in this menu were visible - setVisibility: function setVisibility(menu, addonInfo, usePageWorker) { - let anyVisible = false; - - for (let item of internal(menu).children) { - let visible = isItemVisible(item, addonInfo[internal(item).id], usePageWorker); - - // Recurse through Menus, if none of the sub-items were visible then the - // menu is hidden too. - if (visible && (item instanceof Menu)) - visible = this.setVisibility(item, addonInfo, false); - - let xulNode = this.getXULNodeForItem(item); - xulNode.hidden = !visible; - - anyVisible = anyVisible || visible; - } - - return anyVisible; - }, - - // Works out where to insert a XUL node for an item in a browser window - insertIntoXUL: function insertIntoXUL(item, node, after) { - let menupopup = null; - let before = null; - - let menu = item.parentMenu; - if (menu === this.items) { - // Insert into the overflow popup if it exists, otherwise the normal - // context menu - menupopup = this.overflowPopup; - if (!menupopup) - menupopup = this.contextMenu; - } - else { - let xulNode = this.getXULNodeForItem(menu); - menupopup = xulNode.firstChild; - } - - if (after) { - let afterNode = this.getXULNodeForItem(after); - before = afterNode.nextSibling; - } - else if (menupopup === this.contextMenu) { - let topLevel = this.topLevelItems; - if (topLevel.length > 0) - before = topLevel[topLevel.length - 1].nextSibling; - else - before = this.separator.nextSibling; - } - - menupopup.insertBefore(node, before); - }, - - // Sets the right class for XUL nodes - updateXULClass: function updateXULClass(xulNode) { - if (xulNode.parentNode == this.contextMenu) - xulNode.classList.add(TOPLEVEL_ITEM_CLASS); - else - xulNode.classList.remove(TOPLEVEL_ITEM_CLASS); - - if (xulNode.parentNode == this.overflowPopup) - xulNode.classList.add(OVERFLOW_ITEM_CLASS); - else - xulNode.classList.remove(OVERFLOW_ITEM_CLASS); - }, - - // Creates a XUL node for an item - createItem: function createItem(item, after) { - if (!this.populated) - return; - - // Create the separator if it doesn't already exist - if (!this.separator) { - let separator = this.window.document.createElement("menuseparator"); - separator.setAttribute("class", SEPARATOR_CLASS); - - // Insert before the separator created by the old context-menu if it - // exists to avoid bug 832401 - let oldSeparator = this.window.document.getElementById("jetpack-context-menu-separator"); - if (oldSeparator && oldSeparator.parentNode != this.contextMenu) - oldSeparator = null; - this.contextMenu.insertBefore(separator, oldSeparator); - } - - let type = "menuitem"; - if (item instanceof Menu) - type = "menu"; - else if (item instanceof Separator) - type = "menuseparator"; - - let xulNode = this.window.document.createElement(type); - xulNode.setAttribute("class", ITEM_CLASS); - if (item instanceof LabelledItem) { - xulNode.setAttribute("label", item.label); - if (item.accesskey) - xulNode.setAttribute("accesskey", item.accesskey); - if (item.image) { - xulNode.setAttribute("image", item.image); - if (item instanceof Menu) - xulNode.classList.add("menu-iconic"); - else - xulNode.classList.add("menuitem-iconic"); - } - if (item.data) - xulNode.setAttribute("value", item.data); - - let self = this; - xulNode.addEventListener("command", function(event) { - // Only care about clicks directly on this item - if (event.target !== xulNode) - return; - - itemActivated(item, xulNode); - }, false); - } - - this.insertIntoXUL(item, xulNode, after); - this.updateXULClass(xulNode); - xulNode.data = item.data; - - if (item instanceof Menu) { - let menupopup = this.window.document.createElement("menupopup"); - xulNode.appendChild(menupopup); - } - - this.menuMap.set(item, xulNode); - }, - - // Updates the XUL node for an item in this window - updateItem: function updateItem(item) { - if (!this.populated) - return; - - let xulNode = this.getXULNodeForItem(item); - - // TODO figure out why this requires setAttribute - xulNode.setAttribute("label", item.label); - xulNode.setAttribute("accesskey", item.accesskey || ""); - - if (item.image) { - xulNode.setAttribute("image", item.image); - if (item instanceof Menu) - xulNode.classList.add("menu-iconic"); - else - xulNode.classList.add("menuitem-iconic"); - } - else { - xulNode.removeAttribute("image"); - xulNode.classList.remove("menu-iconic"); - xulNode.classList.remove("menuitem-iconic"); - } - - if (item.data) - xulNode.setAttribute("value", item.data); - else - xulNode.removeAttribute("value"); - }, - - // Moves the XUL node for an item in this window to its new place in the - // hierarchy - moveItem: function moveItem(item, after) { - if (!this.populated) - return; - - let xulNode = this.getXULNodeForItem(item); - let oldParent = xulNode.parentNode; - - this.insertIntoXUL(item, xulNode, after); - this.updateXULClass(xulNode); - this.onXULRemoved(oldParent); - }, - - // Removes the XUL nodes for an item in every window we've ever populated. - removeItem: function removeItem(item) { - if (!this.populated) - return; - - let xulItem = this.getXULNodeForItem(item); - - let oldParent = xulItem.parentNode; - - oldParent.removeChild(xulItem); - this.menuMap.delete(item); - - this.onXULRemoved(oldParent); - }, - - // Called when any XUL nodes have been removed from a menupopup. This handles - // making sure the separator and overflow are correct - onXULRemoved: function onXULRemoved(parent) { - if (parent == this.contextMenu) { - let toplevel = this.topLevelItems; - - // If there are no more items then remove the separator - if (toplevel.length == 0) { - let separator = this.separator; - if (separator) - separator.parentNode.removeChild(separator); - } - } - else if (parent == this.overflowPopup) { - // If there are no more items then remove the overflow menu and separator - if (parent.childNodes.length == 0) { - let separator = this.separator; - separator.parentNode.removeChild(separator); - this.contextMenu.removeChild(parent.parentNode); - } - } - }, - - // Recurses through all the items owned by this module and sets their hidden - // state - updateItemVisibilities: function updateItemVisibilities(event) { - try { - if (event.type != "popupshowing") - return; - if (event.target != this.contextMenu) - return; - - if (internal(this.items).children.length == 0) - return; - - if (!this.populated) { - this.populated = true; - this.populate(this.items); - } - - let mainWindow = event.target.ownerDocument.defaultView; - this.contextMenuContentData = mainWindow.gContextMenuContentData - if (!(self.id in this.contextMenuContentData.addonInfo)) { - console.warn("No context menu state data was provided."); - return; - } - let addonInfo = this.contextMenuContentData.addonInfo[self.id]; - lastContextProcessId = addonInfo.processID; - this.setVisibility(this.items, addonInfo.items, true); - } - catch (e) { - console.exception(e); - } - }, - - // Counts the number of visible items across all modules and makes sure they - // are in the right place between the top level context menu and the overflow - // menu - updateOverflowState: function updateOverflowState(event) { - try { - if (event.type != "popupshowing") - return; - if (event.target != this.contextMenu) - return; - - // The main items will be in either the top level context menu or the - // overflow menu at this point. Count the visible ones and if they are in - // the wrong place move them - let toplevel = this.topLevelItems; - let overflow = this.overflowItems; - let visibleCount = countVisibleItems(toplevel) + - countVisibleItems(overflow); - - if (visibleCount == 0) { - let separator = this.separator; - if (separator) - separator.hidden = true; - let overflowMenu = this.overflowMenu; - if (overflowMenu) - overflowMenu.hidden = true; - } - else if (visibleCount > MenuManager.overflowThreshold) { - this.separator.hidden = false; - let overflowPopup = this.overflowPopup; - if (overflowPopup) - overflowPopup.parentNode.hidden = false; - - if (toplevel.length > 0) { - // The overflow menu shouldn't exist here but let's play it safe - if (!overflowPopup) { - let overflowMenu = this.window.document.createElement("menu"); - overflowMenu.setAttribute("class", OVERFLOW_MENU_CLASS); - overflowMenu.setAttribute("label", OVERFLOW_MENU_LABEL); - overflowMenu.setAttribute("accesskey", OVERFLOW_MENU_ACCESSKEY); - this.contextMenu.insertBefore(overflowMenu, this.separator.nextSibling); - - overflowPopup = this.window.document.createElement("menupopup"); - overflowPopup.setAttribute("class", OVERFLOW_POPUP_CLASS); - overflowMenu.appendChild(overflowPopup); - } - - for (let xulNode of toplevel) { - overflowPopup.appendChild(xulNode); - this.updateXULClass(xulNode); - } - } - } - else { - this.separator.hidden = false; - - if (overflow.length > 0) { - // Move all the overflow nodes out of the overflow menu and position - // them immediately before it - for (let xulNode of overflow) { - this.contextMenu.insertBefore(xulNode, xulNode.parentNode.parentNode); - this.updateXULClass(xulNode); - } - this.contextMenu.removeChild(this.overflowMenu); - } - } - } - catch (e) { - console.exception(e); - } - } -}); - -// This wraps every window that we've seen -var WindowWrapper = Class({ - initialize: function initialize(window) { - this.window = window; - this.menus = [ - new MenuWrapper(this, contentContextMenu, window.document.getElementById("contentAreaContextMenu")), - ]; - }, - - destroy: function destroy() { - for (let menuWrapper of this.menus) - menuWrapper.destroy(); - }, - - getMenuWrapperForItem: function getMenuWrapperForItem(item) { - let root = item.parentMenu; - while (root.parentMenu) - root = root.parentMenu; - - for (let wrapper of this.menus) { - if (wrapper.items === root) - return wrapper; - } - - return null; - } -}); - -var MenuManager = { - windowMap: new Map(), - - get overflowThreshold() { - let prefs = require("./preferences/service"); - return prefs.get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); - }, - - // When a new window is added start watching it for context menu shows - onTrack: function onTrack(window) { - if (!isBrowser(window)) - return; - - // Generally shouldn't happen, but just in case - if (this.windowMap.has(window)) { - console.warn("Already seen this window"); - return; - } - - let winWrapper = WindowWrapper(window); - this.windowMap.set(window, winWrapper); - }, - - onUntrack: function onUntrack(window) { - if (!isBrowser(window)) - return; - - let winWrapper = this.windowMap.get(window); - // This shouldn't happen but protect against it anyway - if (!winWrapper) - return; - winWrapper.destroy(); - - this.windowMap.delete(window); - }, - - // Creates a XUL node for an item in every window we've already populated - createItem: function createItem(item, after) { - for (let [window, winWrapper] of this.windowMap) { - let menuWrapper = winWrapper.getMenuWrapperForItem(item); - if (menuWrapper) - menuWrapper.createItem(item, after); - } - }, - - // Updates the XUL node for an item in every window we've already populated - updateItem: function updateItem(item) { - for (let [window, winWrapper] of this.windowMap) { - let menuWrapper = winWrapper.getMenuWrapperForItem(item); - if (menuWrapper) - menuWrapper.updateItem(item); - } - }, - - // Moves the XUL node for an item in every window we've ever populated to its - // new place in the hierarchy - moveItem: function moveItem(item, after) { - for (let [window, winWrapper] of this.windowMap) { - let menuWrapper = winWrapper.getMenuWrapperForItem(item); - if (menuWrapper) - menuWrapper.moveItem(item, after); - } - }, - - // Removes the XUL nodes for an item in every window we've ever populated. - removeItem: function removeItem(item) { - for (let [window, winWrapper] of this.windowMap) { - let menuWrapper = winWrapper.getMenuWrapperForItem(item); - if (menuWrapper) - menuWrapper.removeItem(item); - } - } -}; - -WindowTracker(MenuManager); diff --git a/addon-sdk/source/lib/sdk/context-menu/context.js b/addon-sdk/source/lib/sdk/context-menu/context.js deleted file mode 100644 index fc5aea500..000000000 --- a/addon-sdk/source/lib/sdk/context-menu/context.js +++ /dev/null @@ -1,147 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const { Class } = require("../core/heritage"); -const { extend } = require("../util/object"); -const { MatchPattern } = require("../util/match-pattern"); -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; diff --git a/addon-sdk/source/lib/sdk/context-menu/core.js b/addon-sdk/source/lib/sdk/context-menu/core.js deleted file mode 100644 index c64cddfe8..000000000 --- a/addon-sdk/source/lib/sdk/context-menu/core.js +++ /dev/null @@ -1,384 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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, false); - }, - 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); diff --git a/addon-sdk/source/lib/sdk/context-menu/readers.js b/addon-sdk/source/lib/sdk/context-menu/readers.js deleted file mode 100644 index 5078f8f29..000000000 --- a/addon-sdk/source/lib/sdk/context-menu/readers.js +++ /dev/null @@ -1,112 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; diff --git a/addon-sdk/source/lib/sdk/context-menu@2.js b/addon-sdk/source/lib/sdk/context-menu@2.js deleted file mode 100644 index 45ad804e9..000000000 --- a/addon-sdk/source/lib/sdk/context-menu@2.js +++ /dev/null @@ -1,32 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const 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); diff --git a/addon-sdk/source/lib/sdk/core/disposable.js b/addon-sdk/source/lib/sdk/core/disposable.js deleted file mode 100644 index 19f7eaa9f..000000000 --- a/addon-sdk/source/lib/sdk/core/disposable.js +++ /dev/null @@ -1,186 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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("./heritage"); -const { Observer, subscribe, unsubscribe, observe } = require("./observer"); -const { isWeak } = require("./reference"); -const SDKWeakSet = require("../lang/weak-set"); - -const method = require("../../method/core"); - -const unloadSubject = require('@loader/unload'); -const addonUnloadTopic = "sdk:loader:destroy"; - -const uninstall = method("disposable/uninstall"); -exports.uninstall = uninstall; - -const shutdown = method("disposable/shutdown"); -exports.shutdown = shutdown; - -const disable = method("disposable/disable"); -exports.disable = disable; - -const upgrade = method("disposable/upgrade"); -exports.upgrade = upgrade; - -const downgrade = method("disposable/downgrade"); -exports.downgrade = downgrade; - -const unload = method("disposable/unload"); -exports.unload = unload; - -const dispose = method("disposable/dispose"); -exports.dispose = dispose; -dispose.define(Object, object => object.dispose()); - -const setup = method("disposable/setup"); -exports.setup = setup; -setup.define(Object, (object, ...args) => object.setup(...args)); - -// DisposablesUnloadObserver is the class which subscribe the -// Observer Service to be notified when the add-on loader is -// unloading to be able to dispose all the existent disposables. -const DisposablesUnloadObserver = Class({ - implements: [Observer], - initialize: function(...args) { - // Set of the non-weak disposables registered to be disposed. - this.disposables = new Set(); - // Target of the weak disposables registered to be disposed - // (and tracked on this target using the SDK weak-set module). - this.weakDisposables = {}; - }, - subscribe(disposable) { - if (isWeak(disposable)) { - SDKWeakSet.add(this.weakDisposables, disposable); - } else { - this.disposables.add(disposable); - } - }, - unsubscribe(disposable) { - if (isWeak(disposable)) { - SDKWeakSet.remove(this.weakDisposables, disposable); - } else { - this.disposables.delete(disposable); - } - }, - tryUnloadDisposable(disposable) { - try { - if (disposable) { - unload(disposable); - } - } catch(e) { - console.error("Error unloading a", - isWeak(disposable) ? "weak disposable" : "disposable", - disposable, e); - } - }, - unloadAll() { - // Remove all the subscribed disposables. - for (let disposable of this.disposables) { - this.tryUnloadDisposable(disposable); - } - - this.disposables.clear(); - - // Remove all the subscribed weak disposables. - for (let disposable of SDKWeakSet.iterator(this.weakDisposables)) { - this.tryUnloadDisposable(disposable); - } - - SDKWeakSet.clear(this.weakDisposables); - } -}); -const disposablesUnloadObserver = new DisposablesUnloadObserver(); - -// The DisposablesUnloadObserver instance is the only object which subscribes -// the Observer Service directly, it observes add-on unload notifications in -// order to trigger `unload` on all its subscribed disposables. -observe.define(DisposablesUnloadObserver, (obj, subject, topic, data) => { - const isUnloadTopic = topic === addonUnloadTopic; - const isUnloadSubject = subject.wrappedJSObject === unloadSubject; - if (isUnloadTopic && isUnloadSubject) { - unsubscribe(disposablesUnloadObserver, addonUnloadTopic); - disposablesUnloadObserver.unloadAll(); - } -}); - -subscribe(disposablesUnloadObserver, addonUnloadTopic, false); - -// Set's up disposable instance. -const setupDisposable = disposable => { - disposablesUnloadObserver.subscribe(disposable); -}; -exports.setupDisposable = setupDisposable; - -// Tears down disposable instance. -const disposeDisposable = disposable => { - disposablesUnloadObserver.unsubscribe(disposable); -}; -exports.disposeDisposable = disposeDisposable; - -// Base type that takes care of disposing it's instances on add-on unload. -// Also makes sure to remove unload listener if it's already being disposed. -const Disposable = Class({ - initialize: function(...args) { - // First setup instance before initializing it's disposal. If instance - // fails to initialize then there is no instance to be disposed at the - // unload. - setup(this, ...args); - setupDisposable(this); - }, - destroy: function(reason) { - // Destroying disposable removes unload handler so that attempt to dispose - // won't be made at unload & delegates to dispose. - disposeDisposable(this); - unload(this, reason); - }, - setup: function() { - // Implement your initialize logic here. - }, - dispose: function() { - // Implement your cleanup logic here. - } -}); -exports.Disposable = Disposable; - -const unloaders = { - destroy: dispose, - uninstall: uninstall, - shutdown: shutdown, - disable: disable, - upgrade: upgrade, - downgrade: downgrade -}; - -const unloaded = new WeakMap(); -unload.define(Disposable, (disposable, reason) => { - if (!unloaded.get(disposable)) { - unloaded.set(disposable, true); - // Pick an unload handler associated with an unload - // reason (falling back to destroy if not found) and - // delegate unloading to it. - const unload = unloaders[reason] || unloaders.destroy; - unload(disposable); - } -}); - -// If add-on is disabled manually, it's being upgraded, downgraded -// or uninstalled `dispose` is invoked to undo any changes that -// has being done by it in this session. -disable.define(Disposable, dispose); -downgrade.define(Disposable, dispose); -upgrade.define(Disposable, dispose); -uninstall.define(Disposable, dispose); - -// If application is shut down no dispose is invoked as undo-ing -// changes made by instance is likely to just waste of resources & -// increase shutdown time. Although specefic components may choose -// to implement shutdown handler that does something better. -shutdown.define(Disposable, disposable => {}); diff --git a/addon-sdk/source/lib/sdk/core/heritage.js b/addon-sdk/source/lib/sdk/core/heritage.js deleted file mode 100644 index fc87ba1f5..000000000 --- a/addon-sdk/source/lib/sdk/core/heritage.js +++ /dev/null @@ -1,184 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 getPrototypeOf = Object.getPrototypeOf; -var getNames = x => [...Object.getOwnPropertyNames(x), - ...Object.getOwnPropertySymbols(x)]; -var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -var create = Object.create; -var freeze = Object.freeze; -var unbind = Function.call.bind(Function.bind, Function.call); - -// This shortcut makes sure that we do perform desired operations, even if -// associated methods have being overridden on the used object. -var owns = unbind(Object.prototype.hasOwnProperty); -var apply = unbind(Function.prototype.apply); -var slice = Array.slice || unbind(Array.prototype.slice); -var reduce = Array.reduce || unbind(Array.prototype.reduce); -var map = Array.map || unbind(Array.prototype.map); -var concat = Array.concat || unbind(Array.prototype.concat); - -// Utility function to get own properties descriptor map. -function getOwnPropertyDescriptors(object) { - return reduce(getNames(object), function(descriptor, name) { - descriptor[name] = getOwnPropertyDescriptor(object, name); - return descriptor; - }, {}); -} - -function isDataProperty(property) { - var value = property.value; - var type = typeof(property.value); - return "value" in property && - (type !== "object" || value === null) && - type !== "function"; -} - -function getDataProperties(object) { - var properties = getOwnPropertyDescriptors(object); - return getNames(properties).reduce(function(result, name) { - var property = properties[name]; - if (isDataProperty(property)) { - result[name] = { - value: property.value, - writable: true, - configurable: true, - enumerable: false - }; - } - return result; - }, {}) -} - -/** - * Takes `source` object as an argument and returns identical object - * with the difference that all own properties will be non-enumerable - */ -function obscure(source) { - var descriptor = reduce(getNames(source), function(descriptor, name) { - var property = getOwnPropertyDescriptor(source, name); - property.enumerable = false; - descriptor[name] = property; - return descriptor; - }, {}); - return create(getPrototypeOf(source), descriptor); -} -exports.obscure = obscure; - -/** - * Takes arbitrary number of source objects and returns fresh one, that - * inherits from the same prototype as a first argument and implements all - * own properties of all argument objects. If two or more argument objects - * have own properties with the same name, the property is overridden, with - * precedence from right to left, implying, that properties of the object on - * the left are overridden by a same named property of the object on the right. - */ -var mix = function(source) { - var descriptor = reduce(slice(arguments), function(descriptor, source) { - return reduce(getNames(source), function(descriptor, name) { - descriptor[name] = getOwnPropertyDescriptor(source, name); - return descriptor; - }, descriptor); - }, {}); - - return create(getPrototypeOf(source), descriptor); -}; -exports.mix = mix; - -/** - * Returns a frozen object with that inherits from the given `prototype` and - * implements all own properties of the given `properties` object. - */ -function extend(prototype, properties) { - return create(prototype, getOwnPropertyDescriptors(properties)); -} -exports.extend = extend; - -/** - * Returns a constructor function with a proper `prototype` setup. Returned - * constructor's `prototype` inherits from a given `options.extends` or - * `Class.prototype` if omitted and implements all the properties of the - * given `option`. If `options.implemens` array is passed, it's elements - * will be mixed into prototype as well. Also, `options.extends` can be - * a function or a prototype. If function than it's prototype is used as - * an ancestor of the prototype, if it's an object that it's used directly. - * Also `options.implements` may contain functions or objects, in case of - * functions their prototypes are used for mixing. - */ -var Class = new function() { - function prototypeOf(input) { - return typeof(input) === 'function' ? input.prototype : input; - } - var none = freeze([]); - - return function Class(options) { - // Create descriptor with normalized `options.extends` and - // `options.implements`. - var descriptor = { - // Normalize extends property of `options.extends` to a prototype object - // in case it's constructor. If property is missing that fallback to - // `Type.prototype`. - extends: owns(options, 'extends') ? - prototypeOf(options.extends) : Class.prototype, - // Normalize `options.implements` to make sure that it's array of - // prototype objects instead of constructor functions. - implements: owns(options, 'implements') ? - freeze(map(options.implements, prototypeOf)) : none - }; - - // Create array of property descriptors who's properties will be defined - // on the resulting prototype. Note: Using reflection `concat` instead of - // method as it may be overridden. - var descriptors = concat(descriptor.implements, options, descriptor, { - constructor: constructor - }); - - // Note: we use reflection `apply` in the constructor instead of method - // call since later may be overridden. - function constructor() { - var instance = create(prototype, attributes); - if (initialize) apply(initialize, instance, arguments); - return instance; - } - // Create `prototype` that inherits from given ancestor passed as - // `options.extends`, falling back to `Type.prototype`, implementing all - // properties of given `options.implements` and `options` itself. - var prototype = extend(descriptor.extends, mix.apply(mix, descriptors)); - var initialize = prototype.initialize; - - // Combine ancestor attributes with prototype's attributes so that - // ancestors attributes also become initializeable. - var attributes = mix(descriptor.extends.constructor.attributes || {}, - getDataProperties(prototype)); - - constructor.attributes = attributes; - Object.defineProperty(constructor, 'prototype', { - configurable: false, - writable: false, - value: prototype - }); - return constructor; - }; -} -Class.prototype = extend(null, obscure({ - constructor: function constructor() { - this.initialize.apply(this, arguments); - return this; - }, - initialize: function initialize() { - // Do your initialization logic here - }, - // Copy useful properties from `Object.prototype`. - toString: Object.prototype.toString, - toLocaleString: Object.prototype.toLocaleString, - toSource: Object.prototype.toSource, - valueOf: Object.prototype.valueOf, - isPrototypeOf: Object.prototype.isPrototypeOf -})); -exports.Class = freeze(Class); diff --git a/addon-sdk/source/lib/sdk/core/namespace.js b/addon-sdk/source/lib/sdk/core/namespace.js deleted file mode 100644 index 3ceb73b72..000000000 --- a/addon-sdk/source/lib/sdk/core/namespace.js +++ /dev/null @@ -1,43 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 create = Object.create; -const prototypeOf = Object.getPrototypeOf; - -/** - * Returns a new namespace, function that may can be used to access an - * namespaced object of the argument argument. Namespaced object are associated - * with owner objects via weak references. Namespaced objects inherit from the - * owners ancestor namespaced object. If owner's ancestor is `null` then - * namespaced object inherits from given `prototype`. Namespaces can be used - * to define internal APIs that can be shared via enclosing `namespace` - * function. - * @examples - * const internals = ns(); - * internals(object).secret = secret; - */ -function ns() { - const map = new WeakMap(); - return function namespace(target) { - if (!target) // If `target` is not an object return `target` itself. - return target; - // If target has no namespaced object yet, create one that inherits from - // the target prototype's namespaced object. - if (!map.has(target)) - map.set(target, create(namespace(prototypeOf(target) || null))); - - return map.get(target); - }; -}; - -// `Namespace` is a e4x function in the scope, so we export the function also as -// `ns` as alias to avoid clashing. -exports.ns = ns; -exports.Namespace = ns; diff --git a/addon-sdk/source/lib/sdk/core/observer.js b/addon-sdk/source/lib/sdk/core/observer.js deleted file mode 100644 index 7e11bf8f9..000000000 --- a/addon-sdk/source/lib/sdk/core/observer.js +++ /dev/null @@ -1,89 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - - -const { Cc, Ci, Cr, Cu } = require("chrome"); -const { Class } = require("./heritage"); -const { isWeak } = require("./reference"); -const method = require("../../method/core"); - -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"); - -// This is a method that will be invoked when notification observer -// subscribed to occurs. -const observe = method("observer/observe"); -exports.observe = observe; - -// Method to subscribe to the observer notification. -const subscribe = method("observe/subscribe"); -exports.subscribe = subscribe; - - -// Method to unsubscribe from the observer notifications. -const unsubscribe = method("observer/unsubscribe"); -exports.unsubscribe = unsubscribe; - - -// This is wrapper class that takes a `delegate` and produces -// instance of `nsIObserver` which will delegate to a given -// object when observer notification occurs. -const ObserverDelegee = Class({ - initialize: function(delegate) { - this.delegate = delegate; - }, - QueryInterface: function(iid) { - if (!iid.equals(Ci.nsIObserver) && - !iid.equals(Ci.nsISupportsWeakReference) && - !iid.equals(Ci.nsISupports)) - throw Cr.NS_ERROR_NO_INTERFACE; - - return this; - }, - observe: function(subject, topic, data) { - observe(this.delegate, subject, topic, data); - } -}); - - -// Class that can be either mixed in or inherited from in -// order to subscribe / unsubscribe for observer notifications. -const Observer = Class({}); -exports.Observer = Observer; - -// Weak maps that associates instance of `ObserverDelegee` with -// an actual observer. It ensures that `ObserverDelegee` instance -// won't be GC-ed until given `observer` is. -const subscribers = new WeakMap(); - -// Implementation of `subscribe` for `Observer` type just registers -// observer for an observer service. If `isWeak(observer)` is `true` -// observer service won't hold strong reference to a given `observer`. -subscribe.define(Observer, (observer, topic) => { - if (!subscribers.has(observer)) { - const delegee = new ObserverDelegee(observer); - subscribers.set(observer, delegee); - addObserver(delegee, topic, isWeak(observer)); - } -}); - -// Unsubscribes `observer` from observer notifications for the -// given `topic`. -unsubscribe.define(Observer, (observer, topic) => { - const delegee = subscribers.get(observer); - if (delegee) { - subscribers.delete(observer); - removeObserver(delegee, topic); - } -}); diff --git a/addon-sdk/source/lib/sdk/core/promise.js b/addon-sdk/source/lib/sdk/core/promise.js deleted file mode 100644 index f4bd7b0f5..000000000 --- a/addon-sdk/source/lib/sdk/core/promise.js +++ /dev/null @@ -1,118 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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'; - -/* - * Uses `Promise.jsm` as a core implementation, with additional sugar - * from previous implementation, with inspiration from `Q` and `when` - * - * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm - * https://github.com/cujojs/when - * https://github.com/kriskowal/q - */ -const PROMISE_URI = 'resource://gre/modules/Promise.jsm'; - -getEnvironment.call(this, function ({ require, exports, module, Cu }) { - -const Promise = Cu.import(PROMISE_URI, {}).Promise; -const { Debugging, defer, resolve, all, reject, race } = Promise; - -module.metadata = { - 'stability': 'unstable' -}; - -var promised = (function() { - // Note: Define shortcuts and utility functions here in order to avoid - // slower property accesses and unnecessary closure creations on each - // call of this popular function. - - var call = Function.call; - var concat = Array.prototype.concat; - - // Utility function that does following: - // execute([ f, self, args...]) => f.apply(self, args) - function execute (args) { - return call.apply(call, args); - } - - // Utility function that takes promise of `a` array and maybe promise `b` - // as arguments and returns promise for `a.concat(b)`. - function promisedConcat(promises, unknown) { - return promises.then(function (values) { - return resolve(unknown) - .then(value => values.concat([value])); - }); - } - - return function promised(f, prototype) { - /** - Returns a wrapped `f`, which when called returns a promise that resolves to - `f(...)` passing all the given arguments to it, which by the way may be - promises. Optionally second `prototype` argument may be provided to be used - a prototype for a returned promise. - - ## Example - - var promise = promised(Array)(1, promise(2), promise(3)) - promise.then(console.log) // => [ 1, 2, 3 ] - **/ - - return function promised(...args) { - // create array of [ f, this, args... ] - return [f, this, ...args]. - // reduce it via `promisedConcat` to get promised array of fulfillments - reduce(promisedConcat, resolve([], prototype)). - // finally map that to promise of `f.apply(this, args...)` - then(execute); - }; - }; -})(); - -exports.promised = promised; -exports.all = all; -exports.defer = defer; -exports.resolve = resolve; -exports.reject = reject; -exports.race = race; -exports.Promise = Promise; -exports.Debugging = Debugging; -}); - -function getEnvironment (callback) { - let Cu, _exports, _module, _require; - - // CommonJS / SDK - if (typeof(require) === 'function') { - Cu = require('chrome').Cu; - _exports = exports; - _module = module; - _require = require; - } - // JSM - else if (String(this).indexOf('BackstagePass') >= 0) { - Cu = this['Components'].utils; - _exports = this.Promise = {}; - _module = { uri: __URI__, id: 'promise/core' }; - _require = uri => { - let imports = {}; - Cu.import(uri, imports); - return imports; - }; - this.EXPORTED_SYMBOLS = ['Promise']; - // mozIJSSubScriptLoader.loadSubscript - } else if (~String(this).indexOf('Sandbox')) { - Cu = this['Components'].utils; - _exports = this; - _module = { id: 'promise/core' }; - _require = uri => {}; - } - - callback({ - Cu: Cu, - exports: _exports, - module: _module, - require: _require - }); -} - diff --git a/addon-sdk/source/lib/sdk/core/reference.js b/addon-sdk/source/lib/sdk/core/reference.js deleted file mode 100644 index 04549cd0f..000000000 --- a/addon-sdk/source/lib/sdk/core/reference.js +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -const method = require("../../method/core"); -const { Class } = require("./heritage"); - -// Object that inherit or mix WeakRefence inn will register -// weak observes for system notifications. -const WeakReference = Class({}); -exports.WeakReference = WeakReference; - - -// If `isWeak(object)` is `true` observer installed -// for such `object` will be weak, meaning that it will -// be GC-ed if nothing else but observer is observing it. -// By default everything except `WeakReference` will return -// `false`. -const isWeak = method("reference/weak?"); -exports.isWeak = isWeak; - -isWeak.define(Object, _ => false); -isWeak.define(WeakReference, _ => true); diff --git a/addon-sdk/source/lib/sdk/deprecated/api-utils.js b/addon-sdk/source/lib/sdk/deprecated/api-utils.js deleted file mode 100644 index 856fc50cb..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/api-utils.js +++ /dev/null @@ -1,197 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; diff --git a/addon-sdk/source/lib/sdk/deprecated/events/assembler.js b/addon-sdk/source/lib/sdk/deprecated/events/assembler.js deleted file mode 100644 index bb297c24f..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/events/assembler.js +++ /dev/null @@ -1,54 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"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); - } -}); diff --git a/addon-sdk/source/lib/sdk/deprecated/sync-worker.js b/addon-sdk/source/lib/sdk/deprecated/sync-worker.js deleted file mode 100644 index 71cadac36..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/sync-worker.js +++ /dev/null @@ -1,288 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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)); -} diff --git a/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js b/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js deleted file mode 100644 index e38629f45..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js +++ /dev/null @@ -1,199 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 file = require("../io/file"); -const { Loader } = require("../test/loader"); - -const { isNative } = require('@loader/options'); - -const cuddlefish = isNative ? require("toolkit/loader") : require("../loader/cuddlefish"); - -const { defer, resolve } = require("../core/promise"); -const { getAddon } = require("../addon/installer"); -const { id } = require("sdk/self"); -const { newURI } = require('sdk/url/utils'); -const { getZipReader } = require("../zip/utils"); - -const { Cc, Ci, Cu } = require("chrome"); -const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); -var ios = Cc['@mozilla.org/network/io-service;1'] - .getService(Ci.nsIIOService); - -const CFX_TEST_REGEX = /(([^\/]+\/)(?:lib\/)?)?(tests?\/test-[^\.\/]+)\.js$/; -const JPM_TEST_REGEX = /^()(tests?\/test-[^\.\/]+)\.js$/; - -const { mapcat, map, filter, fromEnumerator } = require("sdk/util/sequence"); - -const toFile = x => x.QueryInterface(Ci.nsIFile); -const isTestFile = ({leafName}) => leafName.substr(0, 5) == "test-" && leafName.substr(-3, 3) == ".js"; -const getFileURI = x => ios.newFileURI(x).spec; - -const getDirectoryEntries = file => map(toFile, fromEnumerator(_ => file.directoryEntries)); -const getTestFiles = directory => filter(isTestFile, getDirectoryEntries(directory)); -const getTestURIs = directory => map(getFileURI, getTestFiles(directory)); - -const isDirectory = x => x.isDirectory(); -const getTestEntries = directory => mapcat(entry => - /^tests?$/.test(entry.leafName) ? getTestURIs(entry) : getTestEntries(entry), - filter(isDirectory, getDirectoryEntries(directory))); - -const removeDups = (array) => array.reduce((result, value) => { - if (value != result[result.length - 1]) { - result.push(value); - } - return result; -}, []); - -const getSuites = function getSuites({ id, filter }) { - const TEST_REGEX = isNative ? JPM_TEST_REGEX : CFX_TEST_REGEX; - - return getAddon(id).then(addon => { - let fileURI = addon.getResourceURI("tests/"); - let isPacked = fileURI.scheme == "jar"; - let xpiURI = addon.getResourceURI(); - let file = xpiURI.QueryInterface(Ci.nsIFileURL).file; - let suites = []; - let addEntry = (entry) => { - if (filter(entry) && TEST_REGEX.test(entry)) { - let suite = (isNative ? "./" : "") + (RegExp.$2 || "") + RegExp.$3; - suites.push(suite); - } - } - - if (isPacked) { - return getZipReader(file).then(zip => { - let entries = zip.findEntries(null); - while (entries.hasMore()) { - let entry = entries.getNext(); - addEntry(entry); - } - zip.close(); - - // sort and remove dups - suites = removeDups(suites.sort()); - return suites; - }) - } - else { - let tests = [...getTestEntries(file)]; - let rootURI = addon.getResourceURI("/"); - tests.forEach((entry) => { - addEntry(entry.replace(rootURI.spec, "")); - }); - } - - // sort and remove dups - suites = removeDups(suites.sort()); - return suites; - }); -} -exports.getSuites = getSuites; - -const makeFilters = function makeFilters(options) { - options = options || {}; - - // A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon - // optionally separates a regex for the test fileName from a regex for the - // testName. - if (options.filter) { - let colonPos = options.filter.indexOf(':'); - let filterFileRegex, filterNameRegex; - - if (colonPos === -1) { - filterFileRegex = new RegExp(options.filter); - filterNameRegex = { test: () => true } - } - else { - filterFileRegex = new RegExp(options.filter.substr(0, colonPos)); - filterNameRegex = new RegExp(options.filter.substr(colonPos + 1)); - } - - return { - fileFilter: (name) => filterFileRegex.test(name), - testFilter: (name) => filterNameRegex.test(name) - } - } - - return { - fileFilter: () => true, - testFilter: () => true - }; -} -exports.makeFilters = makeFilters; - -var loader = Loader(module); -const NOT_TESTS = ['setup', 'teardown']; - -var TestFinder = exports.TestFinder = function TestFinder(options) { - this.filter = options.filter; - this.testInProcess = options.testInProcess === false ? false : true; - this.testOutOfProcess = options.testOutOfProcess === true ? true : false; -}; - -TestFinder.prototype = { - findTests: function findTests() { - let { fileFilter, testFilter } = makeFilters({ filter: this.filter }); - - return getSuites({ id: id, filter: fileFilter }).then(suites => { - let testsRemaining = []; - - let getNextTest = () => { - if (testsRemaining.length) { - return testsRemaining.shift(); - } - - if (!suites.length) { - return null; - } - - let suite = suites.shift(); - - // Load each test file as a main module in its own loader instance - // `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build - let suiteModule; - - try { - suiteModule = cuddlefish.main(loader, suite); - } - catch (e) { - if (/Unsupported Application/i.test(e.message)) { - // If `Unsupported Application` error thrown during test, - // skip the test suite - suiteModule = { - 'test suite skipped': assert => assert.pass(e.message) - }; - } - else { - console.exception(e); - throw e; - } - } - - if (this.testInProcess) { - for (let name of Object.keys(suiteModule).sort()) { - if (NOT_TESTS.indexOf(name) === -1 && testFilter(name)) { - testsRemaining.push({ - setup: suiteModule.setup, - teardown: suiteModule.teardown, - testFunction: suiteModule[name], - name: suite + "." + name - }); - } - } - } - - return getNextTest(); - }; - - return { - getNext: () => resolve(getNextTest()) - }; - }); - } -}; diff --git a/addon-sdk/source/lib/sdk/deprecated/unit-test.js b/addon-sdk/source/lib/sdk/deprecated/unit-test.js deleted file mode 100644 index 32bba8f6b..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/unit-test.js +++ /dev/null @@ -1,584 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 timer = require("../timers"); -const cfxArgs = require("../test/options"); -const { getTabs, closeTab, getURI, getTabId, getSelectedTab } = require("../tabs/utils"); -const { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils"); -const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise"); -const { getInnerId } = require("../window/utils"); -const { cleanUI } = require("../test/utils"); - -const findAndRunTests = function findAndRunTests(options) { - var TestFinder = require("./unit-test-finder").TestFinder; - var finder = new TestFinder({ - filter: options.filter, - testInProcess: options.testInProcess, - testOutOfProcess: options.testOutOfProcess - }); - var runner = new TestRunner({fs: options.fs}); - finder.findTests().then(tests => { - runner.startMany({ - tests: tests, - stopOnError: options.stopOnError, - onDone: options.onDone - }); - }); -}; -exports.findAndRunTests = findAndRunTests; - -var runnerWindows = new WeakMap(); -var runnerTabs = new WeakMap(); - -const TestRunner = function TestRunner(options) { - options = 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; - this.passed = 0; - this.failed = 0; - this.testRunSummary = []; - this.expectFailNesting = 0; - this.done = TestRunner.prototype.done.bind(this); -}; - -TestRunner.prototype = { - toString: function toString() { - return "[object TestRunner]"; - }, - - DEFAULT_PAUSE_TIMEOUT: (cfxArgs.parseable ? 300000 : 15000), //Five minutes (5*60*1000ms) - PAUSE_DELAY: 500, - - _logTestFailed: function _logTestFailed(why) { - if (!(why in this.test.errors)) - this.test.errors[why] = 0; - this.test.errors[why]++; - }, - - _uncaughtErrorObserver: function({message, date, fileName, stack, lineNumber}) { - this.fail("There was an uncaught Promise rejection: " + message + " @ " + - fileName + ":" + lineNumber + "\n" + stack); - }, - - pass: function pass(message) { - if(!this.expectFailure) { - if ("testMessage" in this.console) - this.console.testMessage(true, true, this.test.name, message); - else - this.console.info("pass:", message); - this.passed++; - this.test.passed++; - this.test.last = message; - } - else { - this.expectFailure = false; - this._logTestFailed("failure"); - if ("testMessage" in this.console) { - this.console.testMessage(true, false, this.test.name, message); - } - else { - this.console.error("fail:", 'Failure Expected: ' + message) - this.console.trace(); - } - this.failed++; - this.test.failed++; - } - }, - - fail: function fail(message) { - if(!this.expectFailure) { - this._logTestFailed("failure"); - if ("testMessage" in this.console) { - this.console.testMessage(false, false, this.test.name, message); - } - else { - this.console.error("fail:", message) - this.console.trace(); - } - this.failed++; - this.test.failed++; - } - else { - this.expectFailure = false; - if ("testMessage" in this.console) - this.console.testMessage(false, true, this.test.name, message); - else - this.console.info("pass:", message); - this.passed++; - this.test.passed++; - this.test.last = message; - } - }, - - expectFail: function(callback) { - this.expectFailure = true; - callback(); - this.expectFailure = false; - }, - - exception: function exception(e) { - this._logTestFailed("exception"); - if (cfxArgs.parseable) - this.console.print("TEST-UNEXPECTED-FAIL | " + this.test.name + " | " + e + "\n"); - this.console.exception(e); - this.failed++; - this.test.failed++; - }, - - assertMatches: function assertMatches(string, regexp, message) { - if (regexp.test(string)) { - if (!message) - message = uneval(string) + " matches " + uneval(regexp); - this.pass(message); - } else { - var no = uneval(string) + " doesn't match " + uneval(regexp); - if (!message) - message = no; - else - message = message + " (" + no + ")"; - this.fail(message); - } - }, - - assertRaises: function assertRaises(func, predicate, message) { - try { - func(); - if (message) - this.fail(message + " (no exception thrown)"); - else - this.fail("function failed to throw exception"); - } catch (e) { - var errorMessage; - if (typeof(e) == "string") - errorMessage = e; - else - errorMessage = e.message; - if (typeof(predicate) == "string") - this.assertEqual(errorMessage, predicate, message); - else - this.assertMatches(errorMessage, predicate, message); - } - }, - - assert: function assert(a, message) { - if (!a) { - if (!message) - message = "assertion failed, value is " + a; - this.fail(message); - } else - this.pass(message || "assertion successful"); - }, - - assertNotEqual: function assertNotEqual(a, b, message) { - if (a != b) { - if (!message) - message = "a != b != " + uneval(a); - this.pass(message); - } else { - var equality = uneval(a) + " == " + uneval(b); - if (!message) - message = equality; - else - message += " (" + equality + ")"; - this.fail(message); - } - }, - - assertEqual: function assertEqual(a, b, message) { - if (a == b) { - if (!message) - message = "a == b == " + uneval(a); - this.pass(message); - } else { - var inequality = uneval(a) + " != " + uneval(b); - if (!message) - message = inequality; - else - message += " (" + inequality + ")"; - this.fail(message); - } - }, - - assertNotStrictEqual: function assertNotStrictEqual(a, b, message) { - if (a !== b) { - if (!message) - message = "a !== b !== " + uneval(a); - this.pass(message); - } else { - var equality = uneval(a) + " === " + uneval(b); - if (!message) - message = equality; - else - message += " (" + equality + ")"; - this.fail(message); - } - }, - - assertStrictEqual: function assertStrictEqual(a, b, message) { - if (a === b) { - if (!message) - message = "a === b === " + uneval(a); - this.pass(message); - } else { - var inequality = uneval(a) + " !== " + uneval(b); - if (!message) - message = inequality; - else - message += " (" + inequality + ")"; - this.fail(message); - } - }, - - assertFunction: function assertFunction(a, message) { - this.assertStrictEqual('function', typeof a, message); - }, - - assertUndefined: function(a, message) { - this.assertStrictEqual('undefined', typeof a, message); - }, - - assertNotUndefined: function(a, message) { - this.assertNotStrictEqual('undefined', typeof a, message); - }, - - assertNull: function(a, message) { - this.assertStrictEqual(null, a, message); - }, - - assertNotNull: function(a, message) { - this.assertNotStrictEqual(null, a, message); - }, - - assertObject: function(a, message) { - this.assertStrictEqual('[object Object]', Object.prototype.toString.apply(a), message); - }, - - assertString: function(a, message) { - this.assertStrictEqual('[object String]', Object.prototype.toString.apply(a), message); - }, - - assertArray: function(a, message) { - this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message); - }, - - assertNumber: function(a, message) { - this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message); - }, - - done: function done() { - if (this.isDone) { - return resolve(); - } - - this.isDone = true; - this.pass("This test is done."); - - if (this.test.teardown) { - this.test.teardown(this); - } - - if (this.waitTimeout !== null) { - timer.clearTimeout(this.waitTimeout); - this.waitTimeout = null; - } - - // Do not leave any callback set when calling to `waitUntil` - this.waitUntilCallback = null; - if (this.test.passed == 0 && this.test.failed == 0) { - this._logTestFailed("empty test"); - - if ("testMessage" in this.console) { - this.console.testMessage(false, false, this.test.name, "Empty test"); - } - else { - this.console.error("fail:", "Empty test") - } - - this.failed++; - this.test.failed++; - } - - let wins = windows(null, { includePrivate: true }); - let winPromises = wins.map(win => { - return new Promise(resolve => { - if (["interactive", "complete"].indexOf(win.document.readyState) >= 0) { - resolve() - } - else { - win.addEventListener("DOMContentLoaded", function onLoad() { - win.removeEventListener("DOMContentLoaded", onLoad, false); - resolve(); - }, false); - } - }); - }); - - PromiseDebugging.flushUncaughtErrors(); - PromiseDebugging.removeUncaughtErrorObserver(this._uncaughtErrorObserver); - - - 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 = getTabs(win); - console.log(win.location + " - " + tabs.map(getURI).join(", ")); - } - else { - console.log(win.location); - } - } - } - - return failure; - }). - then(failure => { - if (!failure) { - this.pass("There was a clean UI."); - return null; - } - return cleanUI().then(() => { - this.pass("There is a clean UI."); - }); - }). - then(() => { - this.testRunSummary.push({ - name: this.test.name, - passed: this.test.passed, - failed: this.test.failed, - errors: Object.keys(this.test.errors).join(", ") - }); - - if (this.onDone !== null) { - let onDone = this.onDone; - this.onDone = null; - timer.setTimeout(_ => onDone(this)); - } - }). - catch(console.exception); - }, - - // Set of assertion functions to wait for an assertion to become true - // These functions take the same arguments as the TestRunner.assert* methods. - waitUntil: function waitUntil() { - return this._waitUntil(this.assert, arguments); - }, - - waitUntilNotEqual: function waitUntilNotEqual() { - return this._waitUntil(this.assertNotEqual, arguments); - }, - - waitUntilEqual: function waitUntilEqual() { - return this._waitUntil(this.assertEqual, arguments); - }, - - waitUntilMatches: function waitUntilMatches() { - return this._waitUntil(this.assertMatches, arguments); - }, - - /** - * Internal function that waits for an assertion to become true. - * @param {Function} assertionMethod - * Reference to a TestRunner assertion method like test.assert, - * test.assertEqual, ... - * @param {Array} args - * List of arguments to give to the previous assertion method. - * All functions in this list are going to be called to retrieve current - * assertion values. - */ - _waitUntil: function waitUntil(assertionMethod, args) { - let { promise, resolve } = defer(); - let count = 0; - let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY; - - // We need to ensure that test is asynchronous - if (!this.waitTimeout) - this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT); - - let finished = false; - let test = this; - - // capture a traceback before we go async. - let traceback = require("../console/traceback"); - let stack = traceback.get(); - stack.splice(-2, 2); - let currentWaitStack = traceback.format(stack); - let timeout = null; - - function loop(stopIt) { - timeout = null; - - // Build a mockup object to fake TestRunner API and intercept calls to - // pass and fail methods, in order to retrieve nice error messages - // and assertion result - let mock = { - pass: function (msg) { - test.pass(msg); - test.waitUntilCallback = null; - if (!stopIt) - resolve(); - }, - fail: function (msg) { - // If we are called on test timeout, we stop the loop - // and print which test keeps failing: - if (stopIt) { - test.console.error("test assertion never became true:\n", - msg + "\n", - currentWaitStack); - if (timeout) - timer.clearTimeout(timeout); - return; - } - timeout = timer.setTimeout(loop, test.PAUSE_DELAY); - } - }; - - // Automatically call args closures in order to build arguments for - // assertion function - let appliedArgs = []; - for (let i = 0, l = args.length; i < l; i++) { - let a = args[i]; - if (typeof a == "function") { - try { - a = a(); - } - catch(e) { - test.fail("Exception when calling asynchronous assertion: " + e + - "\n" + e.stack); - return resolve(); - } - } - appliedArgs.push(a); - } - - // Finally call assertion function with current assertion values - assertionMethod.apply(mock, appliedArgs); - } - loop(); - this.waitUntilCallback = loop; - - return promise; - }, - - waitUntilDone: function waitUntilDone(ms) { - if (ms === undefined) - ms = this.DEFAULT_PAUSE_TIMEOUT; - - var self = this; - - function tiredOfWaiting() { - self._logTestFailed("timed out"); - if ("testMessage" in self.console) { - self.console.testMessage(false, false, self.test.name, - `Test timed out (after: ${self.test.last})`); - } - else { - self.console.error("fail:", `Timed out (after: ${self.test.last})`) - } - if (self.waitUntilCallback) { - self.waitUntilCallback(true); - self.waitUntilCallback = null; - } - self.failed++; - self.test.failed++; - self.done(); - } - - // We may already have registered a timeout callback - if (this.waitTimeout) - timer.clearTimeout(this.waitTimeout); - - this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms); - }, - - startMany: function startMany(options) { - function runNextTest(self) { - let { tests, onDone } = options; - - return tests.getNext().then((test) => { - if (options.stopOnError && self.test && self.test.failed) { - self.console.error("aborted: test failed and --stop-on-error was specified"); - onDone(self); - } - else if (test) { - self.start({test: test, onDone: runNextTest}); - } - else { - onDone(self); - } - }); - } - - return runNextTest(this).catch(console.exception); - }, - - start: function start(options) { - this.test = options.test; - this.test.passed = 0; - this.test.failed = 0; - this.test.errors = {}; - this.test.last = 'START'; - PromiseDebugging.clearUncaughtErrorObservers(); - this._uncaughtErrorObserver = this._uncaughtErrorObserver.bind(this); - PromiseDebugging.addUncaughtErrorObserver(this._uncaughtErrorObserver); - - this.isDone = false; - this.onDone = function(self) { - if (cfxArgs.parseable) - self.console.print("TEST-END | " + self.test.name + "\n"); - options.onDone(self); - } - this.waitTimeout = null; - - try { - if (cfxArgs.parseable) - this.console.print("TEST-START | " + this.test.name + "\n"); - else - this.console.info("executing '" + this.test.name + "'"); - - if(this.test.setup) { - this.test.setup(this); - } - this.test.testFunction(this); - } catch (e) { - this.exception(e); - } - if (this.waitTimeout === null) - this.done(); - } -}; -exports.TestRunner = TestRunner; diff --git a/addon-sdk/source/lib/sdk/deprecated/window-utils.js b/addon-sdk/source/lib/sdk/deprecated/window-utils.js deleted file mode 100644 index 93c0ab7b8..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/window-utils.js +++ /dev/null @@ -1,193 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { Cc, Ci } = require('chrome'); -const events = require('../system/events'); -const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser, - getMostRecentBrowserWindow, getToplevelWindow, getMostRecentWindow } = require('../window/utils'); -const { deprecateFunction } = require('../util/deprecate'); -const { ignoreWindow } = require('sdk/private-browsing/utils'); -const { isPrivateBrowsingSupported } = require('../self'); - -const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. - getService(Ci.nsIWindowWatcher); -const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. - getService(Ci.nsIAppShellService); - -// Bug 834961: ignore private windows when they are not supported -function getWindows() { - return windows(null, { includePrivate: isPrivateBrowsingSupported }); -} - -/** - * An iterator for XUL windows currently in the application. - * - * @return A generator that yields XUL windows exposing the - * nsIDOMWindow interface. - */ -function windowIterator() { - // Bug 752631: We only pass already loaded window in order to avoid - // breaking XUL windows DOM. DOM is broken when some JS code try - // to access DOM during "uninitialized" state of the related document. - let list = getWindows().filter(isDocumentLoaded); - for (let i = 0, l = list.length; i < l; i++) { - yield list[i]; - } -}; -exports.windowIterator = windowIterator; - -/** - * An iterator for browser windows currently open in the application. - * @returns {Function} - * A generator that yields browser windows exposing the `nsIDOMWindow` - * interface. - */ -function browserWindowIterator() { - for (let window of windowIterator()) { - if (isBrowser(window)) - yield window; - } -} -exports.browserWindowIterator = browserWindowIterator; - -function WindowTracker(delegate) { - if (!(this instanceof WindowTracker)) { - return new WindowTracker(delegate); - } - - this._delegate = delegate; - - for (let window of getWindows()) - this._regWindow(window); - windowWatcher.registerNotification(this); - this._onToplevelWindowReady = this._onToplevelWindowReady.bind(this); - events.on('toplevel-window-ready', this._onToplevelWindowReady); - - require('../system/unload').ensure(this); - - return this; -}; - -WindowTracker.prototype = { - _regLoadingWindow: function _regLoadingWindow(window) { - // Bug 834961: ignore private windows when they are not supported - if (ignoreWindow(window)) - return; - - window.addEventListener('load', this, true); - }, - - _unregLoadingWindow: function _unregLoadingWindow(window) { - // This may have no effect if we ignored the window in _regLoadingWindow(). - window.removeEventListener('load', this, true); - }, - - _regWindow: function _regWindow(window) { - // Bug 834961: ignore private windows when they are not supported - if (ignoreWindow(window)) - return; - - if (window.document.readyState == 'complete') { - this._unregLoadingWindow(window); - this._delegate.onTrack(window); - } else - this._regLoadingWindow(window); - }, - - _unregWindow: function _unregWindow(window) { - if (window.document.readyState == 'complete') { - if (this._delegate.onUntrack) - this._delegate.onUntrack(window); - } else { - this._unregLoadingWindow(window); - } - }, - - unload: function unload() { - windowWatcher.unregisterNotification(this); - events.off('toplevel-window-ready', this._onToplevelWindowReady); - for (let window of getWindows()) - this._unregWindow(window); - }, - - handleEvent: function handleEvent(event) { - try { - if (event.type == 'load' && event.target) { - var window = event.target.defaultView; - if (window) - this._regWindow(getToplevelWindow(window)); - } - } - catch(e) { - console.exception(e); - } - }, - - _onToplevelWindowReady: function _onToplevelWindowReady({subject}) { - let window = getToplevelWindow(subject); - // ignore private windows if they are not supported - if (ignoreWindow(window)) - return; - this._regWindow(window); - }, - - observe: function observe(subject, topic, data) { - try { - var window = subject.QueryInterface(Ci.nsIDOMWindow); - // ignore private windows if they are not supported - if (ignoreWindow(window)) - return; - if (topic == 'domwindowclosed') - this._unregWindow(window); - } - catch(e) { - console.exception(e); - } - } -}; -exports.WindowTracker = WindowTracker; - -Object.defineProperties(exports, { - activeWindow: { - enumerable: true, - get: function() { - return getMostRecentWindow(null); - }, - set: function(window) { - try { - window.focus(); - } catch (e) {} - } - }, - activeBrowserWindow: { - enumerable: true, - get: getMostRecentBrowserWindow - } -}); - - -/** - * Returns the ID of the window's current inner window. - */ -exports.getInnerId = deprecateFunction(getInnerId, - 'require("window-utils").getInnerId is deprecated, ' + - 'please use require("sdk/window/utils").getInnerId instead' -); - -exports.getOuterId = deprecateFunction(getOuterId, - 'require("window-utils").getOuterId is deprecated, ' + - 'please use require("sdk/window/utils").getOuterId instead' -); - -exports.isBrowser = deprecateFunction(isBrowser, - 'require("window-utils").isBrowser is deprecated, ' + - 'please use require("sdk/window/utils").isBrowser instead' -); - -exports.hiddenWindow = appShellService.hiddenDOMWindow; diff --git a/addon-sdk/source/lib/sdk/dom/events-shimmed.js b/addon-sdk/source/lib/sdk/dom/events-shimmed.js deleted file mode 100644 index 7a1727681..000000000 --- a/addon-sdk/source/lib/sdk/dom/events-shimmed.js +++ /dev/null @@ -1,18 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'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); diff --git a/addon-sdk/source/lib/sdk/dom/events.js b/addon-sdk/source/lib/sdk/dom/events.js deleted file mode 100644 index 502d2350f..000000000 --- a/addon-sdk/source/lib/sdk/dom/events.js +++ /dev/null @@ -1,192 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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.ownerDocument.defaultView; - 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; diff --git a/addon-sdk/source/lib/sdk/dom/events/keys.js b/addon-sdk/source/lib/sdk/dom/events/keys.js deleted file mode 100644 index e6f1483a2..000000000 --- a/addon-sdk/source/lib/sdk/dom/events/keys.js +++ /dev/null @@ -1,63 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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)); -}; - diff --git a/addon-sdk/source/lib/sdk/event/chrome.js b/addon-sdk/source/lib/sdk/event/chrome.js deleted file mode 100644 index 9044fef99..000000000 --- a/addon-sdk/source/lib/sdk/event/chrome.js +++ /dev/null @@ -1,65 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci, Cr, Cu } = require("chrome"); -const { emit, on, off } = require("./core"); -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 { when: unload } = require("../system/unload"); - -// Simple class that can be used to instantiate event channel that -// implements `nsIObserver` interface. It's will is used by `observe` -// function as observer + event target. It basically proxies observer -// notifications as to it's registered listeners. -function ObserverChannel() {} -Object.freeze(Object.defineProperties(ObserverChannel.prototype, { - QueryInterface: { - value: function(iid) { - if (!iid.equals(Ci.nsIObserver) && - !iid.equals(Ci.nsISupportsWeakReference) && - !iid.equals(Ci.nsISupports)) - throw Cr.NS_ERROR_NO_INTERFACE; - return this; - } - }, - observe: { - value: function(subject, topic, data) { - emit(this, "data", { - type: topic, - target: subject, - data: data - }); - } - } -})); - -function observe(topic) { - let observerChannel = new ObserverChannel(); - - // Note: `nsIObserverService` will not hold a weak reference to a - // observerChannel (since third argument is `true`). There for if it - // will be GC-ed with all it's event listeners once no other references - // will be held. - addObserver(observerChannel, topic, true); - - // We need to remove any observer added once the add-on is unloaded; - // otherwise we'll get a "dead object" exception. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833 - unload(() => removeObserver(observerChannel, topic)); - - return observerChannel; -} - -exports.observe = observe; diff --git a/addon-sdk/source/lib/sdk/event/core.js b/addon-sdk/source/lib/sdk/event/core.js deleted file mode 100644 index c16dd2df5..000000000 --- a/addon-sdk/source/lib/sdk/event/core.js +++ /dev/null @@ -1,193 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.'; -const BAD_LISTENER = 'The event listener must be a function.'; - -const { ns } = require('../core/namespace'); - -const event = ns(); - -const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/; -exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN; - -// Utility function to access given event `target` object's event listeners for -// the specific event `type`. If listeners for this type does not exists they -// will be created. -const observers = function observers(target, type) { - if (!target) throw TypeError("Event target must be an object"); - let listeners = event(target); - return type in listeners ? listeners[type] : listeners[type] = []; -}; - -/** - * Registers an event `listener` that is called every time events of - * specified `type` is emitted on the given event `target`. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ -function on(target, type, listener) { - if (typeof(listener) !== 'function') - throw new Error(BAD_LISTENER); - - let listeners = observers(target, type); - if (!~listeners.indexOf(listener)) - listeners.push(listener); -} -exports.on = on; - - -var onceWeakMap = new WeakMap(); - - -/** - * Registers an event `listener` that is called only the next time an event - * of the specified `type` is emitted on the given event `target`. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of the event. - * @param {Function} listener - * The listener function that processes the event. - */ -function once(target, type, listener) { - let replacement = function observer(...args) { - off(target, type, observer); - onceWeakMap.delete(listener); - listener.apply(target, args); - }; - onceWeakMap.set(listener, replacement); - on(target, type, replacement); -} -exports.once = once; - -/** - * Execute each of the listeners in order with the supplied arguments. - * All the exceptions that are thrown by listeners during the emit - * are caught and can be handled by listeners of 'error' event. Thrown - * exceptions are passed as an argument to an 'error' event listener. - * If no 'error' listener is registered exception will be logged into an - * error console. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of event. - * @params {Object|Number|String|Boolean} args - * Arguments that will be passed to listeners. - */ -function emit (target, type, ...args) { - emitOnObject(target, type, target, ...args); -} -exports.emit = emit; - -/** - * A variant of emit that allows setting the this property for event listeners - */ -function emitOnObject(target, type, thisArg, ...args) { - let all = observers(target, '*').length; - let state = observers(target, type); - let listeners = state.slice(); - let count = listeners.length; - let index = 0; - - // If error event and there are no handlers (explicit or catch-all) - // then print error message to the console. - if (count === 0 && type === 'error' && all === 0) - console.exception(args[0]); - while (index < count) { - try { - let listener = listeners[index]; - // Dispatch only if listener is still registered. - if (~state.indexOf(listener)) - listener.apply(thisArg, args); - } - catch (error) { - // If exception is not thrown by a error listener and error listener is - // registered emit `error` event. Otherwise dump exception to the console. - if (type !== 'error') emit(target, 'error', error); - else console.exception(error); - } - index++; - } - // Also emit on `"*"` so that one could listen for all events. - if (type !== '*') emit(target, '*', type, ...args); -} -exports.emitOnObject = emitOnObject; - -/** - * Removes an event `listener` for the given event `type` on the given event - * `target`. If no `listener` is passed removes all listeners of the given - * `type`. If `type` is not passed removes all the listeners of the given - * event `target`. - * @param {Object} target - * The event target object. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ -function off(target, type, listener) { - let length = arguments.length; - if (length === 3) { - if (onceWeakMap.has(listener)) { - listener = onceWeakMap.get(listener); - onceWeakMap.delete(listener); - } - - let listeners = observers(target, type); - let index = listeners.indexOf(listener); - if (~index) - listeners.splice(index, 1); - } - else if (length === 2) { - observers(target, type).splice(0); - } - else if (length === 1) { - let listeners = event(target); - Object.keys(listeners).forEach(type => delete listeners[type]); - } -} -exports.off = off; - -/** - * Returns a number of event listeners registered for the given event `type` - * on the given event `target`. - */ -function count(target, type) { - return observers(target, type).length; -} -exports.count = count; - -/** - * Registers listeners on the given event `target` from the given `listeners` - * dictionary. Iterates over the listeners and if property name matches name - * pattern `onEventType` and property is a function, then registers it as - * an `eventType` listener on `target`. - * - * @param {Object} target - * The type of event. - * @param {Object} listeners - * Dictionary of listeners. - */ -function setListeners(target, listeners) { - Object.keys(listeners || {}).forEach(key => { - let match = EVENT_TYPE_PATTERN.exec(key); - let type = match && match[1].toLowerCase(); - if (!type) return; - - let listener = listeners[key]; - if (typeof(listener) === 'function') - on(target, type, listener); - }); -} -exports.setListeners = setListeners; diff --git a/addon-sdk/source/lib/sdk/event/dom.js b/addon-sdk/source/lib/sdk/event/dom.js deleted file mode 100644 index da99dec7a..000000000 --- a/addon-sdk/source/lib/sdk/event/dom.js +++ /dev/null @@ -1,78 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { Ci } = require("chrome"); - -var { emit } = require("./core"); -var { when: unload } = require("../system/unload"); -var listeners = new WeakMap(); - -const { Cu } = require("chrome"); -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const { ThreadSafeChromeUtils } = Cu.import("resource://gre/modules/Services.jsm", {}); - -var getWindowFrom = x => - x instanceof Ci.nsIDOMWindow ? x : - x instanceof Ci.nsIDOMDocument ? x.defaultView : - x instanceof Ci.nsIDOMNode ? x.ownerDocument.defaultView : - null; - -function removeFromListeners() { - ShimWaiver.getProperty(this, "removeEventListener")("DOMWindowClose", removeFromListeners); - for (let cleaner of listeners.get(this)) - cleaner(); - - listeners.delete(this); -} - -// Simple utility function takes event target, event type and optional -// `options.capture` and returns node style event stream that emits "data" -// events every time event of that type occurs on the given `target`. -function open(target, type, options) { - let output = {}; - let capture = options && options.capture ? true : false; - let listener = (event) => emit(output, "data", event); - - // `open` is currently used only on DOM Window objects, however it was made - // to be used to any kind of `target` that supports `addEventListener`, - // therefore is safer get the `window` from the `target` instead assuming - // that `target` is the `window`. - let window = getWindowFrom(target); - - // If we're not able to get a `window` from `target`, there is something - // wrong. We cannot add listeners that can leak later, or results in - // "dead object" exception. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833 - if (!window) - throw new Error("Unable to obtain the owner window from the target given."); - - let cleaners = listeners.get(window); - if (!cleaners) { - cleaners = []; - listeners.set(window, cleaners); - - // We need to remove from our map the `window` once is closed, to prevent - // memory leak - ShimWaiver.getProperty(window, "addEventListener")("DOMWindowClose", removeFromListeners); - } - - cleaners.push(() => ShimWaiver.getProperty(target, "removeEventListener")(type, listener, capture)); - ShimWaiver.getProperty(target, "addEventListener")(type, listener, capture); - - return output; -} - -unload(() => { - let keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(listeners) - for (let window of keys) - removeFromListeners.call(window); -}); - -exports.open = open; diff --git a/addon-sdk/source/lib/sdk/event/target.js b/addon-sdk/source/lib/sdk/event/target.js deleted file mode 100644 index 3a1f5e5f0..000000000 --- a/addon-sdk/source/lib/sdk/event/target.js +++ /dev/null @@ -1,74 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -module.metadata = { - "stability": "stable" -}; - -const { on, once, off, setListeners } = require('./core'); -const { method, chainable } = require('../lang/functional/core'); -const { Class } = require('../core/heritage'); - -/** - * `EventTarget` is an exemplar for creating an objects that can be used to - * add / remove event listeners on them. Events on these objects may be emitted - * via `emit` function exported by 'event/core' module. - */ -const EventTarget = Class({ - /** - * Method initializes `this` event source. It goes through properties of a - * given `options` and registers listeners for the ones that look like an - * event listeners. - */ - /** - * Method initializes `this` event source. It goes through properties of a - * given `options` and registers listeners for the ones that look like an - * event listeners. - */ - initialize: function initialize(options) { - setListeners(this, options); - }, - /** - * Registers an event `listener` that is called every time events of - * specified `type` are emitted. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - * @example - * worker.on('message', function (data) { - * console.log('data received: ' + data) - * }) - */ - on: chainable(method(on)), - /** - * Registers an event `listener` that is called once the next time an event - * of the specified `type` is emitted. - * @param {String} type - * The type of the event. - * @param {Function} listener - * The listener function that processes the event. - */ - once: chainable(method(once)), - /** - * Removes an event `listener` for the given event `type`. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ - removeListener: function removeListener(type, listener) { - // Note: We can't just wrap `off` in `method` as we do it for other methods - // cause skipping a second or third argument will behave very differently - // than intended. This way we make sure all arguments are passed and only - // one listener is removed at most. - off(this, type, listener); - return this; - }, - // but we can wrap `off` here, as the semantics are the same - off: chainable(method(off)) - -}); -exports.EventTarget = EventTarget; diff --git a/addon-sdk/source/lib/sdk/event/utils.js b/addon-sdk/source/lib/sdk/event/utils.js deleted file mode 100644 index f193b6785..000000000 --- a/addon-sdk/source/lib/sdk/event/utils.js +++ /dev/null @@ -1,328 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { emit, on, once, off, EVENT_TYPE_PATTERN } = require("./core"); -const { Cu } = require("chrome"); - -// This module provides set of high order function for working with event -// streams (streams in a NodeJS style that dispatch data, end and error -// events). - -// Function takes a `target` object and returns set of implicit references -// (non property references) it keeps. This basically allows defining -// references between objects without storing the explicitly. See transform for -// more details. -var refs = (function() { - let refSets = new WeakMap(); - return function refs(target) { - if (!refSets.has(target)) refSets.set(target, new Set()); - return refSets.get(target); - }; -})(); - -function transform(input, f) { - let output = new Output(); - - // Since event listeners don't prevent `input` to be GC-ed we wanna presrve - // it until `output` can be GC-ed. There for we add implicit reference which - // is removed once `input` ends. - refs(output).add(input); - - const next = data => receive(output, data); - once(output, "start", () => start(input)); - on(input, "error", error => emit(output, "error", error)); - on(input, "end", function() { - refs(output).delete(input); - end(output); - }); - on(input, "data", data => f(data, next)); - return output; -} - -// High order event transformation function that takes `input` event channel -// and returns transformation containing only events on which `p` predicate -// returns `true`. -function filter(input, predicate) { - return transform(input, function(data, next) { - if (predicate(data)) - next(data); - }); -} -exports.filter = filter; - -// High order function that takes `input` and returns input of it's values -// mapped via given `f` function. -const map = (input, f) => transform(input, (data, next) => next(f(data))); -exports.map = map; - -// High order function that takes `input` stream of streams and merges them -// into single event stream. Like flatten but time based rather than order -// based. -function merge(inputs) { - let output = new Output(); - let open = 1; - let state = []; - output.state = state; - refs(output).add(inputs); - - function end(input) { - open = open - 1; - refs(output).delete(input); - if (open === 0) emit(output, "end"); - } - const error = e => emit(output, "error", e); - function forward(input) { - state.push(input); - open = open + 1; - on(input, "end", () => end(input)); - on(input, "error", error); - on(input, "data", data => emit(output, "data", data)); - } - - // If `inputs` is an array treat it as a stream. - if (Array.isArray(inputs)) { - inputs.forEach(forward); - end(inputs); - } - else { - on(inputs, "end", () => end(inputs)); - on(inputs, "error", error); - on(inputs, "data", forward); - } - - return output; -} -exports.merge = merge; - -const expand = (inputs, f) => merge(map(inputs, f)); -exports.expand = expand; - -const pipe = (from, to) => on(from, "*", emit.bind(emit, to)); -exports.pipe = pipe; - - -// Shim signal APIs so other modules can be used as is. -const receive = (input, message) => { - if (input[receive]) - input[receive](input, message); - else - emit(input, "data", message); - - // Ideally our input will extend Input and already provide a weak value - // getter. If not, opportunistically shim the weak value getter on - // other types passed as the input. - if (!("value" in input)) { - Object.defineProperty(input, "value", WeakValueGetterSetter); - } - input.value = message; -}; -receive.toString = () => "@@receive"; -exports.receive = receive; -exports.send = receive; - -const end = input => { - if (input[end]) - input[end](input); - else - emit(input, "end", input); -}; -end.toString = () => "@@end"; -exports.end = end; - -const stop = input => { - if (input[stop]) - input[stop](input); - else - emit(input, "stop", input); -}; -stop.toString = () => "@@stop"; -exports.stop = stop; - -const start = input => { - if (input[start]) - input[start](input); - else - emit(input, "start", input); -}; -start.toString = () => "@@start"; -exports.start = start; - -const lift = (step, ...inputs) => { - let args = null; - let opened = inputs.length; - let started = false; - const output = new Output(); - const init = () => { - args = [...inputs.map(input => input.value)]; - output.value = step(...args); - }; - - inputs.forEach((input, index) => { - on(input, "data", data => { - args[index] = data; - receive(output, step(...args)); - }); - on(input, "end", () => { - opened = opened - 1; - if (opened <= 0) - end(output); - }); - }); - - once(output, "start", () => { - inputs.forEach(start); - init(); - }); - - init(); - - return output; -}; -exports.lift = lift; - -const merges = inputs => { - let opened = inputs.length; - let output = new Output(); - output.value = inputs[0].value; - inputs.forEach((input, index) => { - on(input, "data", data => receive(output, data)); - on(input, "end", () => { - opened = opened - 1; - if (opened <= 0) - end(output); - }); - }); - - once(output, "start", () => { - inputs.forEach(start); - output.value = inputs[0].value; - }); - - return output; -}; -exports.merges = merges; - -const foldp = (step, initial, input) => { - let output = map(input, x => step(output.value, x)); - output.value = initial; - return output; -}; -exports.foldp = foldp; - -const keepIf = (p, base, input) => { - let output = filter(input, p); - output.value = base; - return output; -}; -exports.keepIf = keepIf; - -function Input() {} -Input.start = input => emit(input, "start", input); -Input.prototype.start = Input.start; - -Input.end = input => { - emit(input, "end", input); - stop(input); -}; -Input.prototype[end] = Input.end; - -// The event channel system caches the last event seen as input.value. -// Unfortunately, if the last event is a DOM object this is a great way -// leak windows. Mitigate this by storing input.value using a weak -// reference. This allows the system to work for normal event processing -// while also allowing the objects to be reclaimed. It means, however, -// input.value cannot be accessed long after the event was dispatched. -const WeakValueGetterSetter = { - get: function() { - return this._weakValue ? this._weakValue.get() : this._simpleValue - }, - set: function(v) { - if (v && typeof v === "object") { - try { - // Try to set a weak reference. This can throw for some values. - // For example, if the value is a native object that does not - // implement nsISupportsWeakReference. - this._weakValue = Cu.getWeakReference(v) - this._simpleValue = undefined; - return; - } catch (e) { - // Do nothing. Fall through to setting _simpleValue below. - } - } - this._simpleValue = v; - this._weakValue = undefined; - }, -} -Object.defineProperty(Input.prototype, "value", WeakValueGetterSetter); - -exports.Input = Input; - -// Define an Output type with a weak value getter for the transformation -// functions that produce new channels. -function Output() { } -Object.defineProperty(Output.prototype, "value", WeakValueGetterSetter); -exports.Output = Output; - -const $source = "@@source"; -const $outputs = "@@outputs"; -exports.outputs = $outputs; - -// NOTE: Passing DOM objects through a Reactor can cause them to leak -// when they get cached in this.value. We cannot use a weak reference -// in this case because the Reactor design expects to always have both the -// past and present value. If we allow past values to be collected the -// system breaks. - -function Reactor(options={}) { - const {onStep, onStart, onEnd} = options; - if (onStep) - this.onStep = onStep; - if (onStart) - this.onStart = onStart; - if (onEnd) - this.onEnd = onEnd; -} -Reactor.prototype.onStep = _ => void(0); -Reactor.prototype.onStart = _ => void(0); -Reactor.prototype.onEnd = _ => void(0); -Reactor.prototype.onNext = function(present, past) { - this.value = present; - this.onStep(present, past); -}; -Reactor.prototype.run = function(input) { - on(input, "data", message => this.onNext(message, input.value)); - on(input, "end", () => this.onEnd(input.value)); - start(input); - this.value = input.value; - this.onStart(input.value); -}; -exports.Reactor = Reactor; - -/** - * Takes an object used as options with potential keys like 'onMessage', - * used to be called `require('sdk/event/core').setListeners` on. - * This strips all keys that would trigger a listener to be set. - * - * @params {Object} object - * @return {Object} - */ - -function stripListeners (object) { - return Object.keys(object || {}).reduce((agg, key) => { - if (!EVENT_TYPE_PATTERN.test(key)) - agg[key] = object[key]; - return agg; - }, {}); -} -exports.stripListeners = stripListeners; - -const when = (target, type) => new Promise(resolve => { - once(target, type, resolve); -}); -exports.when = when; diff --git a/addon-sdk/source/lib/sdk/frame/hidden-frame.js b/addon-sdk/source/lib/sdk/frame/hidden-frame.js deleted file mode 100644 index 97e0b7974..000000000 --- a/addon-sdk/source/lib/sdk/frame/hidden-frame.js +++ /dev/null @@ -1,115 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -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"); -const { emit } = require("../event/core"); -const { create: makeFrame } = require("./utils"); -const { defer } = require("../core/promise"); -const { when: unload } = require("../system/unload"); -const { validateOptions, getTypeOf } = require("../deprecated/api-utils"); -const { window } = require("../addon/window"); -const { fromIterator } = require("../util/array"); - -// 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, false); - deferred.resolve(target); - } - }, false); - 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.parentNode.removeChild(element) -} -exports.remove = removeHiddenFrame; - -unload(() => fromIterator(cache).forEach(removeHiddenFrame)); diff --git a/addon-sdk/source/lib/sdk/frame/utils.js b/addon-sdk/source/lib/sdk/frame/utils.js deleted file mode 100644 index d9fccec4d..000000000 --- a/addon-sdk/source/lib/sdk/frame/utils.js +++ /dev/null @@ -1,94 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -module.metadata = { - "stability": "experimental" -}; - -const { Ci } = require("chrome"); -const XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; - -function eventTarget(frame) { - return getDocShell(frame).chromeEventHandler; -} -exports.eventTarget = eventTarget; - -function getDocShell(frame) { - let { frameLoader } = frame.QueryInterface(Ci.nsIFrameLoaderOwner); - return frameLoader && frameLoader.docShell; -} -exports.getDocShell = getDocShell; - -/** - * Creates a XUL `browser` element in a privileged document. - * @params {nsIDOMDocument} document - * @params {String} options.type - * By default is 'content' for possible values see: - * https://developer.mozilla.org/en/XUL/iframe#a-browser.type - * @params {String} options.uri - * URI of the document to be loaded into created frame. - * @params {Boolean} options.remote - * If `true` separate process will be used for this frame, also in such - * case all the following options are ignored. - * @params {Boolean} options.allowAuth - * Whether to allow auth dialogs. Defaults to `false`. - * @params {Boolean} options.allowJavascript - * Whether to allow Javascript execution. Defaults to `false`. - * @params {Boolean} options.allowPlugins - * Whether to allow plugin execution. Defaults to `false`. - */ -function create(target, options) { - target = target instanceof Ci.nsIDOMDocument ? target.documentElement : - target instanceof Ci.nsIDOMWindow ? target.document.documentElement : - target; - options = options || {}; - let remote = options.remote || false; - let namespaceURI = options.namespaceURI || XUL; - let isXUL = namespaceURI === XUL; - let nodeName = isXUL && options.browser ? 'browser' : 'iframe'; - let document = target.ownerDocument; - - let frame = document.createElementNS(namespaceURI, nodeName); - // Type="content" is mandatory to enable stuff here: - // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1776 - frame.setAttribute('type', options.type || 'content'); - frame.setAttribute('src', options.uri || 'about:blank'); - - // Must set the remote attribute before attaching the frame to the document - if (remote && isXUL) { - // We remove XBL binding to avoid execution of code that is not going to - // work because browser has no docShell attribute in remote mode - // (for example) - frame.setAttribute('style', '-moz-binding: none;'); - frame.setAttribute('remote', 'true'); - } - - target.appendChild(frame); - - // Load in separate process if `options.remote` is `true`. - // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347 - if (remote && !isXUL) { - frame.QueryInterface(Ci.nsIMozBrowserFrame); - frame.createRemoteFrameLoader(null); - } - - // If browser is remote it won't have a `docShell`. - if (!remote) { - let docShell = getDocShell(frame); - docShell.allowAuth = options.allowAuth || false; - docShell.allowJavascript = options.allowJavascript || false; - docShell.allowPlugins = options.allowPlugins || false; - docShell.allowWindowControl = options.allowWindowControl || false; - } - - return frame; -} -exports.create = create; - -function swapFrameLoaders(from, to) { - return from.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(to); -} -exports.swapFrameLoaders = swapFrameLoaders; diff --git a/addon-sdk/source/lib/sdk/fs/path.js b/addon-sdk/source/lib/sdk/fs/path.js deleted file mode 100644 index 4474b2b4a..000000000 --- a/addon-sdk/source/lib/sdk/fs/path.js +++ /dev/null @@ -1,500 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// Adapted version of: -// https://github.com/joyent/node/blob/v0.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; - }; -} \ No newline at end of file diff --git a/addon-sdk/source/lib/sdk/hotkeys.js b/addon-sdk/source/lib/sdk/hotkeys.js deleted file mode 100644 index 00081455e..000000000 --- a/addon-sdk/source/lib/sdk/hotkeys.js +++ /dev/null @@ -1,40 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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); -}; diff --git a/addon-sdk/source/lib/sdk/indexed-db.js b/addon-sdk/source/lib/sdk/indexed-db.js deleted file mode 100644 index d4d166c02..000000000 --- a/addon-sdk/source/lib/sdk/indexed-db.js +++ /dev/null @@ -1,79 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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, null, null); - -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; diff --git a/addon-sdk/source/lib/sdk/input/browser.js b/addon-sdk/source/lib/sdk/input/browser.js deleted file mode 100644 index daea875bf..000000000 --- a/addon-sdk/source/lib/sdk/input/browser.js +++ /dev/null @@ -1,73 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"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; diff --git a/addon-sdk/source/lib/sdk/input/customizable-ui.js b/addon-sdk/source/lib/sdk/input/customizable-ui.js deleted file mode 100644 index a41d0971a..000000000 --- a/addon-sdk/source/lib/sdk/input/customizable-ui.js +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -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; diff --git a/addon-sdk/source/lib/sdk/input/frame.js b/addon-sdk/source/lib/sdk/input/frame.js deleted file mode 100644 index 50efaa745..000000000 --- a/addon-sdk/source/lib/sdk/input/frame.js +++ /dev/null @@ -1,85 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { 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; diff --git a/addon-sdk/source/lib/sdk/input/system.js b/addon-sdk/source/lib/sdk/input/system.js deleted file mode 100644 index 66bc6daec..000000000 --- a/addon-sdk/source/lib/sdk/input/system.js +++ /dev/null @@ -1,113 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; diff --git a/addon-sdk/source/lib/sdk/io/buffer.js b/addon-sdk/source/lib/sdk/io/buffer.js deleted file mode 100644 index 5ea169402..000000000 --- a/addon-sdk/source/lib/sdk/io/buffer.js +++ /dev/null @@ -1,351 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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'); -const { isNumber } = require('sdk/lang/type'); -const { TextEncoder, TextDecoder } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {}); - -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 (/size and count too large/.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); - } - }); -}); diff --git a/addon-sdk/source/lib/sdk/io/byte-streams.js b/addon-sdk/source/lib/sdk/io/byte-streams.js deleted file mode 100644 index 6afab4369..000000000 --- a/addon-sdk/source/lib/sdk/io/byte-streams.js +++ /dev/null @@ -1,104 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -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; - } -}; diff --git a/addon-sdk/source/lib/sdk/io/file.js b/addon-sdk/source/lib/sdk/io/file.js deleted file mode 100644 index 47467df87..000000000 --- a/addon-sdk/source/lib/sdk/io/file.js +++ /dev/null @@ -1,196 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 {Cc,Ci,Cr} = require("chrome"); -const byteStreams = require("./byte-streams"); -const textStreams = require("./text-streams"); - -// Flags passed when opening a file. See nsprpub/pr/include/prio.h. -const OPEN_FLAGS = { - RDONLY: parseInt("0x01"), - WRONLY: parseInt("0x02"), - CREATE_FILE: parseInt("0x08"), - APPEND: parseInt("0x10"), - TRUNCATE: parseInt("0x20"), - EXCL: parseInt("0x80") -}; - -var dirsvc = Cc["@mozilla.org/file/directory_service;1"] - .getService(Ci.nsIProperties); - -function MozFile(path) { - var file = Cc['@mozilla.org/file/local;1'] - .createInstance(Ci.nsILocalFile); - file.initWithPath(path); - return file; -} - -function ensureReadable(file) { - if (!file.isReadable()) - throw new Error("path is not readable: " + file.path); -} - -function ensureDir(file) { - ensureExists(file); - if (!file.isDirectory()) - throw new Error("path is not a directory: " + file.path); -} - -function ensureFile(file) { - ensureExists(file); - if (!file.isFile()) - throw new Error("path is not a file: " + file.path); -} - -function ensureExists(file) { - if (!file.exists()) - throw friendlyError(Cr.NS_ERROR_FILE_NOT_FOUND, file.path); -} - -function friendlyError(errOrResult, filename) { - var isResult = typeof(errOrResult) === "number"; - var result = isResult ? errOrResult : errOrResult.result; - switch (result) { - case Cr.NS_ERROR_FILE_NOT_FOUND: - return new Error("path does not exist: " + filename); - } - return isResult ? new Error("XPCOM error code: " + errOrResult) : errOrResult; -} - -exports.exists = function exists(filename) { - return MozFile(filename).exists(); -}; - -exports.isFile = function isFile(filename) { - return MozFile(filename).isFile(); -}; - -exports.read = function read(filename, mode) { - if (typeof(mode) !== "string") - mode = ""; - - // Ensure mode is read-only. - mode = /b/.test(mode) ? "b" : ""; - - var stream = exports.open(filename, mode); - try { - var str = stream.read(); - } - finally { - stream.close(); - } - - return str; -}; - -exports.join = function join(base) { - if (arguments.length < 2) - throw new Error("need at least 2 args"); - base = MozFile(base); - for (var i = 1; i < arguments.length; i++) - base.append(arguments[i]); - return base.path; -}; - -exports.dirname = function dirname(path) { - var parent = MozFile(path).parent; - return parent ? parent.path : ""; -}; - -exports.basename = function basename(path) { - var leafName = MozFile(path).leafName; - - // On Windows, leafName when the path is a volume letter and colon ("c:") is - // the path itself. But such a path has no basename, so we want the empty - // string. - return leafName == path ? "" : leafName; -}; - -exports.list = function list(path) { - var file = MozFile(path); - ensureDir(file); - ensureReadable(file); - - var entries = file.directoryEntries; - var entryNames = []; - while(entries.hasMoreElements()) { - var entry = entries.getNext(); - entry.QueryInterface(Ci.nsIFile); - entryNames.push(entry.leafName); - } - return entryNames; -}; - -exports.open = function open(filename, mode) { - var file = MozFile(filename); - if (typeof(mode) !== "string") - mode = ""; - - // File opened for write only. - if (/w/.test(mode)) { - if (file.exists()) - ensureFile(file); - var stream = Cc['@mozilla.org/network/file-output-stream;1']. - createInstance(Ci.nsIFileOutputStream); - var openFlags = OPEN_FLAGS.WRONLY | - OPEN_FLAGS.CREATE_FILE | - OPEN_FLAGS.TRUNCATE; - var permFlags = 0o644; // u+rw go+r - try { - stream.init(file, openFlags, permFlags, 0); - } - catch (err) { - throw friendlyError(err, filename); - } - return /b/.test(mode) ? - new byteStreams.ByteWriter(stream) : - new textStreams.TextWriter(stream); - } - - // File opened for read only, the default. - ensureFile(file); - stream = Cc['@mozilla.org/network/file-input-stream;1']. - createInstance(Ci.nsIFileInputStream); - try { - stream.init(file, OPEN_FLAGS.RDONLY, 0, 0); - } - catch (err) { - throw friendlyError(err, filename); - } - return /b/.test(mode) ? - new byteStreams.ByteReader(stream) : - new textStreams.TextReader(stream); -}; - -exports.remove = function remove(path) { - var file = MozFile(path); - ensureFile(file); - file.remove(false); -}; - -exports.mkpath = function mkpath(path) { - var file = MozFile(path); - if (!file.exists()) - file.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); // u+rwx go+rx - else if (!file.isDirectory()) - throw new Error("The path already exists and is not a directory: " + path); -}; - -exports.rmdir = function rmdir(path) { - var file = MozFile(path); - ensureDir(file); - try { - file.remove(false); - } - catch (err) { - // Bug 566950 explains why we're not catching a specific exception here. - throw new Error("The directory is not empty: " + path); - } -}; diff --git a/addon-sdk/source/lib/sdk/io/fs.js b/addon-sdk/source/lib/sdk/io/fs.js deleted file mode 100644 index 860a884a5..000000000 --- a/addon-sdk/source/lib/sdk/io/fs.js +++ /dev/null @@ -1,984 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"); - -const { setTimeout } = require("../timers"); -const { Stream, InputStream, OutputStream } = require("./stream"); -const { emit, on } = require("../event/core"); -const { Buffer } = require("./buffer"); -const { ns } = require("../core/namespace"); -const { Class } = require("../core/heritage"); - - -const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", - "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 nsILocalFile(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 nsILocalFile(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 nsILocalFile(oldPath); - let target = new nsILocalFile(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 nsILocalFile(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 nsILocalFile(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 nsILocalFile(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 nsILocalFile(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 nsILocalFile(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 nsILocalFile(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), nsILocalFile(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; diff --git a/addon-sdk/source/lib/sdk/io/stream.js b/addon-sdk/source/lib/sdk/io/stream.js deleted file mode 100644 index 0698b8e32..000000000 --- a/addon-sdk/source/lib/sdk/io/stream.js +++ /dev/null @@ -1,440 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { emit } = require("../event/core"); -const { Buffer } = require("./buffer"); -const { Class } = require("../core/heritage"); -const { setTimeout } = require("../timers"); - - -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; diff --git a/addon-sdk/source/lib/sdk/io/text-streams.js b/addon-sdk/source/lib/sdk/io/text-streams.js deleted file mode 100644 index ed4ec4972..000000000 --- a/addon-sdk/source/lib/sdk/io/text-streams.js +++ /dev/null @@ -1,235 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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; -} diff --git a/addon-sdk/source/lib/sdk/keyboard/hotkeys.js b/addon-sdk/source/lib/sdk/keyboard/hotkeys.js deleted file mode 100644 index a179502b8..000000000 --- a/addon-sdk/source/lib/sdk/keyboard/hotkeys.js +++ /dev/null @@ -1,110 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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(); - } - } -}); diff --git a/addon-sdk/source/lib/sdk/keyboard/observer.js b/addon-sdk/source/lib/sdk/keyboard/observer.js deleted file mode 100644 index b8e32b95c..000000000 --- a/addon-sdk/source/lib/sdk/keyboard/observer.js +++ /dev/null @@ -1,58 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -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.ownerDocument ? event.target.ownerDocument.defaultView - : undefined); - } -}); - -exports.observer = new Observer(); diff --git a/addon-sdk/source/lib/sdk/keyboard/utils.js b/addon-sdk/source/lib/sdk/keyboard/utils.js deleted file mode 100644 index 1b7df4ce3..000000000 --- a/addon-sdk/source/lib/sdk/keyboard/utils.js +++ /dev/null @@ -1,189 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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); -}; diff --git a/addon-sdk/source/lib/sdk/l10n.js b/addon-sdk/source/lib/sdk/l10n.js deleted file mode 100644 index db5a9d7b6..000000000 --- a/addon-sdk/source/lib/sdk/l10n.js +++ /dev/null @@ -1,91 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 json = require("./l10n/json/core"); -const { get: getKey } = require("./l10n/core"); -const properties = require("./l10n/properties/core"); -const { getRulesForLocale } = require("./l10n/plural-rules"); - -// Retrieve the plural mapping function -var 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; -} diff --git a/addon-sdk/source/lib/sdk/l10n/core.js b/addon-sdk/source/lib/sdk/l10n/core.js deleted file mode 100644 index 2f8f84c04..000000000 --- a/addon-sdk/source/lib/sdk/l10n/core.js +++ /dev/null @@ -1,9 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const json = require("./json/core"); -const properties = require("./properties/core"); - -exports.get = json.usingJSON ? json.get : properties.get; diff --git a/addon-sdk/source/lib/sdk/l10n/html.js b/addon-sdk/source/lib/sdk/l10n/html.js deleted file mode 100644 index fa2cf9cf0..000000000 --- a/addon-sdk/source/lib/sdk/l10n/html.js +++ /dev/null @@ -1,32 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -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"); -}); diff --git a/addon-sdk/source/lib/sdk/l10n/json/core.js b/addon-sdk/source/lib/sdk/l10n/json/core.js deleted file mode 100644 index af52f956f..000000000 --- a/addon-sdk/source/lib/sdk/l10n/json/core.js +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - -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"; -} diff --git a/addon-sdk/source/lib/sdk/l10n/loader.js b/addon-sdk/source/lib/sdk/l10n/loader.js deleted file mode 100644 index 60e219e44..000000000 --- a/addon-sdk/source/lib/sdk/l10n/loader.js +++ /dev/null @@ -1,70 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci } = require("chrome"); -const { getPreferedLocales, findClosestLocale } = require("./locale"); -const { readURI } = require("../net/url"); -const { resolve } = require("../core/promise"); - -function parseJsonURI(uri) { - return readURI(uri). - then(JSON.parse). - then(null, 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 - }; - }); - }); -} diff --git a/addon-sdk/source/lib/sdk/l10n/locale.js b/addon-sdk/source/lib/sdk/l10n/locale.js deleted file mode 100644 index 950b33b20..000000000 --- a/addon-sdk/source/lib/sdk/l10n/locale.js +++ /dev/null @@ -1,127 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"); - -/** - * Gets the currently selected locale for display. - * Gets all usable locale that we can use sorted by priority of relevance - * @return Array of locales, begins with highest priority - */ -const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; -const PREF_SELECTED_LOCALE = "general.useragent.locale"; -const PREF_ACCEPT_LANGUAGES = "intl.accept_languages"; - -function getPreferedLocales(caseSensitve) { - let locales = []; - function addLocale(locale) { - locale = locale.trim(); - if (!caseSensitve) - locale = locale.toLowerCase(); - if (locales.indexOf(locale) === -1) - locales.push(locale); - } - - // Most important locale is OS one. But we use it, only if - // "intl.locale.matchOS" pref is set to `true`. - // Currently only used for multi-locales mobile builds. - // http://mxr.mozilla.org/mozilla-central/source/mobile/android/installer/Makefile.in#46 - if (prefs.get(PREF_MATCH_OS_LOCALE, false)) { - let localeService = Cc["@mozilla.org/intl/nslocaleservice;1"]. - getService(Ci.nsILocaleService); - let osLocale = localeService.getLocaleComponentForUserAgent(); - addLocale(osLocale); - } - - // In some cases, mainly on Fennec and on Linux version, - // `general.useragent.locale` is a special 'localized' value, like: - // "chrome://global/locale/intl.properties" - let browserUiLocale = prefs.getLocalized(PREF_SELECTED_LOCALE, "") || - prefs.get(PREF_SELECTED_LOCALE, ""); - if (browserUiLocale) - addLocale(browserUiLocale); - - // Third priority is the list of locales used for web content - let contentLocales = prefs.getLocalized(PREF_ACCEPT_LANGUAGES, "") || - prefs.get(PREF_ACCEPT_LANGUAGES, ""); - if (contentLocales) { - // This list is a string of locales seperated by commas. - // There is spaces after commas, so strip each item - for (let locale of contentLocales.split(",")) - addLocale(locale.replace(/(^\s+)|(\s+$)/g, "")); - } - - // Finally, we ensure that en-US is the final fallback if it wasn't added - addLocale("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; -} diff --git a/addon-sdk/source/lib/sdk/l10n/plural-rules.js b/addon-sdk/source/lib/sdk/l10n/plural-rules.js deleted file mode 100644 index a3ef48a5e..000000000 --- a/addon-sdk/source/lib/sdk/l10n/plural-rules.js +++ /dev/null @@ -1,407 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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]; -} - diff --git a/addon-sdk/source/lib/sdk/l10n/prefs.js b/addon-sdk/source/lib/sdk/l10n/prefs.js deleted file mode 100644 index 8ee26fc5b..000000000 --- a/addon-sdk/source/lib/sdk/l10n/prefs.js +++ /dev/null @@ -1,51 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { on } = require("../system/events"); -const core = require("./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; diff --git a/addon-sdk/source/lib/sdk/l10n/properties/core.js b/addon-sdk/source/lib/sdk/l10n/properties/core.js deleted file mode 100644 index 7a9081d0b..000000000 --- a/addon-sdk/source/lib/sdk/l10n/properties/core.js +++ /dev/null @@ -1,87 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { newURI } = require('../../url/utils') -const { getRulesForLocale } = require("../plural-rules"); -const { getPreferedLocales } = require('../locale'); -const { rootURI } = require("@loader/options"); -const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); - -const baseURI = rootURI + "locale/"; -const 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)); diff --git a/addon-sdk/source/lib/sdk/lang/functional.js b/addon-sdk/source/lib/sdk/lang/functional.js deleted file mode 100644 index 66e30edfa..000000000 --- a/addon-sdk/source/lib/sdk/lang/functional.js +++ /dev/null @@ -1,47 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// Disclaimer: Some of the functions in this module implement APIs from -// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for -// those goes to him. - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { defer, remit, delay, debounce, - throttle } = require("./functional/concurrent"); -const { method, invoke, partial, curry, compose, wrap, identity, memoize, once, - cache, complement, constant, when, apply, flip, field, query, - isInstance, chainable, is, isnt } = require("./functional/core"); - -exports.defer = defer; -exports.remit = remit; -exports.delay = delay; -exports.debounce = debounce; -exports.throttle = throttle; - -exports.method = method; -exports.invoke = invoke; -exports.partial = partial; -exports.curry = curry; -exports.compose = compose; -exports.wrap = wrap; -exports.identity = identity; -exports.memoize = memoize; -exports.once = once; -exports.cache = cache; -exports.complement = complement; -exports.constant = constant; -exports.when = when; -exports.apply = apply; -exports.flip = flip; -exports.field = field; -exports.query = query; -exports.isInstance = isInstance; -exports.chainable = chainable; -exports.is = is; -exports.isnt = isnt; diff --git a/addon-sdk/source/lib/sdk/lang/functional/concurrent.js b/addon-sdk/source/lib/sdk/lang/functional/concurrent.js deleted file mode 100644 index 85e8cff46..000000000 --- a/addon-sdk/source/lib/sdk/lang/functional/concurrent.js +++ /dev/null @@ -1,110 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// Disclaimer: Some of the functions in this module implement APIs from -// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for -// those goes to him. - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { arity, name, derive, invoke } = require("./helpers"); -const { setTimeout, clearTimeout, setImmediate } = require("../../timers"); - -/** - * Takes a function and returns a wrapped one instead, calling which will call - * original function in the next turn of event loop. This is basically utility - * to do `setImmediate(function() { ... })`, with a difference that returned - * function is reused, instead of creating a new one each time. This also allows - * to use this functions as event listeners. - */ -const defer = f => derive(function(...args) { - setImmediate(invoke, f, args, this); -}, f); -exports.defer = defer; -// Exporting `remit` alias as `defer` may conflict with promises. -exports.remit = defer; - -/** - * Much like setTimeout, invokes function after wait milliseconds. If you pass - * the optional arguments, they will be forwarded on to the function when it is - * invoked. - */ -const delay = function delay(f, ms, ...args) { - setTimeout(() => f.apply(this, args), ms); -}; -exports.delay = delay; - -/** - * From underscore's `_.debounce` - * http://underscorejs.org - * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Underscore may be freely distributed under the MIT license. - */ -const debounce = function debounce (fn, wait) { - let timeout, args, context, timestamp, result; - - let later = function () { - let last = Date.now() - timestamp; - if (last < wait) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - result = fn.apply(context, args); - context = args = null; - } - }; - - return function (...aArgs) { - context = this; - args = aArgs; - timestamp = Date.now(); - if (!timeout) { - timeout = setTimeout(later, wait); - } - - return result; - }; -}; -exports.debounce = debounce; - -/** - * From underscore's `_.throttle` - * http://underscorejs.org - * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Underscore may be freely distributed under the MIT license. - */ -const throttle = function throttle (func, wait, options) { - let context, args, result; - let timeout = null; - let previous = 0; - options || (options = {}); - let later = function() { - previous = options.leading === false ? 0 : Date.now(); - timeout = null; - result = func.apply(context, args); - context = args = null; - }; - return function() { - let now = Date.now(); - if (!previous && options.leading === false) previous = now; - let remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - context = args = null; - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; -}; -exports.throttle = throttle; diff --git a/addon-sdk/source/lib/sdk/lang/functional/core.js b/addon-sdk/source/lib/sdk/lang/functional/core.js deleted file mode 100644 index 0d9143364..000000000 --- a/addon-sdk/source/lib/sdk/lang/functional/core.js +++ /dev/null @@ -1,290 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// Disclaimer: Some of the functions in this module implement APIs from -// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for -// those goes to him. - -"use strict"; - -module.metadata = { - "stability": "unstable" -} -const { arity, name, derive, invoke } = require("./helpers"); - -/** - * Takes variadic numeber of functions and returns composed one. - * Returned function pushes `this` pseudo-variable to the head - * of the passed arguments and invokes all the functions from - * left to right passing same arguments to them. Composite function - * returns return value of the right most funciton. - */ -const method = (...lambdas) => { - return function method(...args) { - args.unshift(this); - return lambdas.reduce((_, lambda) => lambda.apply(this, args), - void(0)); - }; -}; -exports.method = method; - -/** - * Invokes `callee` by passing `params` as an arguments and `self` as `this` - * pseudo-variable. Returns value that is returned by a callee. - * @param {Function} callee - * Function to invoke. - * @param {Array} params - * Arguments to invoke function with. - * @param {Object} self - * Object to be passed as a `this` pseudo variable. - */ -exports.invoke = invoke; - -/** - * Takes a function and bind values to one or more arguments, returning a new - * function of smaller arity. - * - * @param {Function} fn - * The function to partial - * - * @returns The new function with binded values - */ -const partial = (f, ...curried) => { - if (typeof(f) !== "function") - throw new TypeError(String(f) + " is not a function"); - - let fn = derive(function(...args) { - return f.apply(this, curried.concat(args)); - }, f); - fn.arity = arity(f) - curried.length; - return fn; -}; -exports.partial = partial; - -/** - * Returns function with implicit currying, which will continue currying until - * expected number of argument is collected. Expected number of arguments is - * determined by `fn.length`. Using this with variadic functions is stupid, - * so don't do it. - * - * @examples - * - * var sum = curry(function(a, b) { - * return a + b - * }) - * console.log(sum(2, 2)) // 4 - * console.log(sum(2)(4)) // 6 - */ -const curry = new function() { - const currier = (fn, arity, params) => { - // Function either continues to curry arguments or executes function - // if desired arguments have being collected. - const curried = function(...input) { - // Prepend all curried arguments to the given arguments. - if (params) input.unshift.apply(input, params); - // If expected number of arguments has being collected invoke fn, - // othrewise return curried version Otherwise continue curried. - return (input.length >= arity) ? fn.apply(this, input) : - currier(fn, arity, input); - }; - curried.arity = arity - (params ? params.length : 0); - - return curried; - }; - - return fn => currier(fn, arity(fn)); -}; -exports.curry = curry; - -/** - * Returns the composition of a list of functions, where each function consumes - * the return value of the function that follows. In math terms, composing the - * functions `f()`, `g()`, and `h()` produces `f(g(h()))`. - * @example - * - * var greet = function(name) { return "hi: " + name; }; - * var exclaim = function(statement) { return statement + "!"; }; - * var welcome = compose(exclaim, greet); - * - * welcome('moe'); // => 'hi: moe!' - */ -function compose(...lambdas) { - return function composed(...args) { - let index = lambdas.length; - while (0 <= --index) - args = [lambdas[index].apply(this, args)]; - - return args[0]; - }; -} -exports.compose = compose; - -/* - * Returns the first function passed as an argument to the second, - * allowing you to adjust arguments, run code before and after, and - * conditionally execute the original function. - * @example - * - * var hello = function(name) { return "hello: " + name; }; - * hello = wrap(hello, function(f) { - * return "before, " + f("moe") + ", after"; - * }); - * - * hello(); // => 'before, hello: moe, after' - */ -const wrap = (f, wrapper) => derive(function wrapped(...args) { - return wrapper.apply(this, [f].concat(args)); -}, f); -exports.wrap = wrap; - -/** - * Returns the same value that is used as the argument. In math: f(x) = x - */ -const identity = value => value; -exports.identity = identity; - -/** - * Memoizes a given function by caching the computed result. Useful for - * speeding up slow-running computations. If passed an optional hashFunction, - * it will be used to compute the hash key for storing the result, based on - * the arguments to the original function. The default hashFunction just uses - * the first argument to the memoized function as the key. - */ -const memoize = (f, hasher) => { - let memo = Object.create(null); - let cache = new WeakMap(); - hasher = hasher || identity; - return derive(function memoizer(...args) { - const key = hasher.apply(this, args); - const type = typeof(key); - if (key && (type === "object" || type === "function")) { - if (!cache.has(key)) - cache.set(key, f.apply(this, args)); - return cache.get(key); - } - else { - if (!(key in memo)) - memo[key] = f.apply(this, args); - return memo[key]; - } - }, f); -}; -exports.memoize = memoize; - -/* - * Creates a version of the function that can only be called one time. Repeated - * calls to the modified function will have no effect, returning the value from - * the original call. Useful for initialization functions, instead of having to - * set a boolean flag and then check it later. - */ -const once = f => { - let ran = false, cache; - return derive(function(...args) { - return ran ? cache : (ran = true, cache = f.apply(this, args)); - }, f); -}; -exports.once = once; -// export cache as once will may be conflicting with event once a lot. -exports.cache = once; - -// Takes a `f` function and returns a function that takes the same -// arguments as `f`, has the same effects, if any, and returns the -// opposite truth value. -const complement = f => derive(function(...args) { - return args.length < arity(f) ? complement(partial(f, ...args)) : - !f.apply(this, args); -}, f); -exports.complement = complement; - -// Constructs function that returns `x` no matter what is it -// invoked with. -const constant = x => _ => x; -exports.constant = constant; - -// Takes `p` predicate, `consequent` function and an optional -// `alternate` function and composes function that returns -// application of arguments over `consequent` if application over -// `p` is `true` otherwise returns application over `alternate`. -// If `alternate` is not a function returns `undefined`. -const when = (p, consequent, alternate) => { - if (typeof(alternate) !== "function" && alternate !== void(0)) - throw TypeError("alternate must be a function"); - if (typeof(consequent) !== "function") - throw TypeError("consequent must be a function"); - - return function(...args) { - return p.apply(this, args) ? - consequent.apply(this, args) : - alternate && alternate.apply(this, args); - }; -}; -exports.when = when; - -// Apply function that behaves as `apply` does in lisp: -// apply(f, x, [y, z]) => f.apply(f, [x, y, z]) -// apply(f, x) => f.apply(f, [x]) -const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop())); -exports.apply = apply; - -// Returns function identical to given `f` but with flipped order -// of arguments. -const flip = f => derive(function(...args) { - return f.apply(this, args.reverse()); -}, f); -exports.flip = flip; - -// Takes field `name` and `target` and returns value of that field. -// If `target` is `null` or `undefined` it would be returned back -// instead of attempt to access it's field. Function is implicitly -// curried, this allows accessor function generation by calling it -// with only `name` argument. -const field = curry((name, target) => - // Note: Permisive `==` is intentional. - target == null ? target : target[name]); -exports.field = field; - -// Takes `.` delimited string representing `path` to a nested field -// and a `target` to get it from. For convinience function is -// implicitly curried, there for accessors can be created by invoking -// it with just a `path` argument. -const query = curry((path, target) => { - const names = path.split("."); - const count = names.length; - let index = 0; - let result = target; - // Note: Permisive `!=` is intentional. - while (result != null && index < count) { - result = result[names[index]]; - index = index + 1; - } - return result; -}); -exports.query = query; - -// Takes `Type` (constructor function) and a `value` and returns -// `true` if `value` is instance of the given `Type`. Function is -// implicitly curried this allows predicate generation by calling -// function with just first argument. -const isInstance = curry((Type, value) => value instanceof Type); -exports.isInstance = isInstance; - -/* - * Takes a funtion and returns a wrapped function that returns `this` - */ -const chainable = f => derive(function(...args) { - f.apply(this, args); - return this; -}, f); -exports.chainable = chainable; - -// Functions takes `expected` and `actual` values and returns `true` if -// `expected === actual`. Returns curried function if called with less then -// two arguments. -// -// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ] -const is = curry((expected, actual) => actual === expected); -exports.is = is; - -const isnt = complement(is); -exports.isnt = isnt; diff --git a/addon-sdk/source/lib/sdk/lang/functional/helpers.js b/addon-sdk/source/lib/sdk/lang/functional/helpers.js deleted file mode 100644 index 60f4e3300..000000000 --- a/addon-sdk/source/lib/sdk/lang/functional/helpers.js +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// Disclaimer: Some of the functions in this module implement APIs from -// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for -// those goes to him. - -"use strict"; - -module.metadata = { - "stability": "unstable" -} - -const arity = f => f.arity || f.length; -exports.arity = arity; - -const name = f => f.displayName || f.name; -exports.name = name; - -const derive = (f, source) => { - f.displayName = name(source); - f.arity = arity(source); - return f; -}; -exports.derive = derive; - -const invoke = (callee, params, self) => callee.apply(self, params); -exports.invoke = invoke; diff --git a/addon-sdk/source/lib/sdk/lang/type.js b/addon-sdk/source/lib/sdk/lang/type.js deleted file mode 100644 index b50e6be4c..000000000 --- a/addon-sdk/source/lib/sdk/lang/type.js +++ /dev/null @@ -1,388 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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" -}; - -/** - * Returns `true` if `value` is `undefined`. - * @examples - * var foo; isUndefined(foo); // true - * isUndefined(0); // false - */ -function isUndefined(value) { - return value === undefined; -} -exports.isUndefined = isUndefined; - -/** - * Returns `true` if value is `null`. - * @examples - * isNull(null); // true - * isNull(undefined); // false - */ -function isNull(value) { - return value === null; -} -exports.isNull = isNull; - -/** - * Returns `true` if value is `null` or `undefined`. - * It's equivalent to `== null`, but resolve the ambiguity of the writer - * intention, makes clear that he's clearly checking both `null` and `undefined` - * values, and it's not a typo for `=== null`. - */ -function isNil(value) { - return value === null || value === undefined; -} -exports.isNil = isNil; - -function isBoolean(value) { - return typeof value === "boolean"; -} -exports.isBoolean = isBoolean; -/** - * Returns `true` if value is a string. - * @examples - * isString("moe"); // true - */ -function isString(value) { - return typeof value === "string"; -} -exports.isString = isString; - -/** - * Returns `true` if `value` is a number. - * @examples - * isNumber(8.4 * 5); // true - */ -function isNumber(value) { - return typeof value === "number"; -} -exports.isNumber = isNumber; - -/** - * Returns `true` if `value` is a `RegExp`. - * @examples - * isRegExp(/moe/); // true - */ -function isRegExp(value) { - return isObject(value) && instanceOf(value, RegExp); -} -exports.isRegExp = isRegExp; - -/** - * Returns true if `value` is a `Date`. - * @examples - * isDate(new Date()); // true - */ -function isDate(value) { - return isObject(value) && instanceOf(value, Date); -} -exports.isDate = isDate; - -/** - * Returns true if object is a Function. - * @examples - * isFunction(function foo(){}) // true - */ -function isFunction(value) { - return typeof value === "function"; -} -exports.isFunction = isFunction; - -/** - * Returns `true` if `value` is an object (please note that `null` is considered - * to be an atom and not an object). - * @examples - * isObject({}) // true - * isObject(null) // false - */ -function isObject(value) { - return typeof value === "object" && value !== null; -} -exports.isObject = isObject; - -/** - * Detect whether a value is a generator. - * - * @param aValue - * The value to identify. - * @return A boolean indicating whether the value is a generator. - */ -function isGenerator(aValue) { - return !!(aValue && aValue.isGenerator && aValue.isGenerator()); -} -exports.isGenerator = isGenerator; - -/** - * Returns true if `value` is an Array. - * @examples - * isArray([1, 2, 3]) // true - * isArray({ 0: 'foo', length: 1 }) // false - */ -var isArray = Array.isArray; -exports.isArray = isArray; - -/** - * Returns `true` if `value` is an Arguments object. - * @examples - * (function(){ return isArguments(arguments); })(1, 2, 3); // true - * isArguments([1,2,3]); // false - */ -function isArguments(value) { - return Object.prototype.toString.call(value) === "[object Arguments]"; -} -exports.isArguments = isArguments; - -var isMap = value => Object.prototype.toString.call(value) === "[object Map]" -exports.isMap = isMap; - -var isSet = value => Object.prototype.toString.call(value) === "[object Set]" -exports.isSet = isSet; - -/** - * Returns true if it is a primitive `value`. (null, undefined, number, - * boolean, string) - * @examples - * isPrimitive(3) // true - * isPrimitive('foo') // true - * isPrimitive({ bar: 3 }) // false - */ -function isPrimitive(value) { - return !isFunction(value) && !isObject(value); -} -exports.isPrimitive = isPrimitive; - -/** - * Returns `true` if given `object` is flat (it is direct decedent of - * `Object.prototype` or `null`). - * @examples - * isFlat({}) // true - * isFlat(new Type()) // false - */ -function isFlat(object) { - return isObject(object) && (isNull(Object.getPrototypeOf(object)) || - isNull(Object.getPrototypeOf( - Object.getPrototypeOf(object)))); -} -exports.isFlat = isFlat; - -/** - * Returns `true` if object contains no values. - */ -function isEmpty(object) { - if (isObject(object)) { - for (var key in object) - return false; - return true; - } - return false; -} -exports.isEmpty = isEmpty; - -/** - * Returns `true` if `value` is an array / flat object containing only atomic - * values and other flat objects. - */ -function isJSON(value, visited) { - // Adding value to array of visited values. - (visited || (visited = [])).push(value); - // If `value` is an atom return `true` cause it's valid JSON. - return isPrimitive(value) || - // If `value` is an array of JSON values that has not been visited - // yet. - (isArray(value) && value.every(function(element) { - return isJSON(element, visited); - })) || - // If `value` is a plain object containing properties with a JSON - // values it's a valid JSON. - (isFlat(value) && Object.keys(value).every(function(key) { - var $ = Object.getOwnPropertyDescriptor(value, key); - // Check every proprety of a plain object to verify that - // it's neither getter nor setter, but a JSON value, that - // has not been visited yet. - return ((!isObject($.value) || !~visited.indexOf($.value)) && - !('get' in $) && !('set' in $) && - isJSON($.value, visited)); - })); -} -exports.isJSON = function (value) { - return isJSON(value); -}; - -/** - * Returns `true` if `value` is JSONable - */ -const isJSONable = (value) => { - try { - JSON.parse(JSON.stringify(value)); - } - catch (e) { - return false; - } - return true; -}; -exports.isJSONable = isJSONable; - -/** - * Returns if `value` is an instance of a given `Type`. This is exactly same as - * `value instanceof Type` with a difference that `Type` can be from a scope - * that has a different top level object. (Like in case where `Type` is a - * function from different iframe / jetpack module / sandbox). - */ -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; -} -exports.instanceOf = instanceOf; - -/** - * Function returns textual representation of a value passed to it. Function - * takes additional `indent` argument that is used for indentation. Also - * optional `limit` argument may be passed to limit amount of detail returned. - * @param {Object} value - * @param {String} [indent=" "] - * @param {Number} [limit] - */ -function source(value, indent, limit, offset, visited) { - var result; - var names; - var nestingIndex; - var isCompact = !isUndefined(limit); - - indent = indent || " "; - offset = (offset || ""); - result = ""; - visited = visited || []; - - if (isUndefined(value)) { - result += "undefined"; - } - else if (isNull(value)) { - result += "null"; - } - else if (isString(value)) { - result += '"' + value + '"'; - } - else if (isFunction(value)) { - value = String(value).split("\n"); - if (isCompact && value.length > 2) { - value = value.splice(0, 2); - value.push("...}"); - } - result += value.join("\n" + offset); - } - else if (isArray(value)) { - if ((nestingIndex = (visited.indexOf(value) + 1))) { - result = "#" + nestingIndex + "#"; - } - else { - visited.push(value); - - if (isCompact) - value = value.slice(0, limit); - - result += "[\n"; - result += value.map(function(value) { - return offset + indent + source(value, indent, limit, offset + indent, - visited); - }).join(",\n"); - result += isCompact && value.length > limit ? - ",\n" + offset + "...]" : "\n" + offset + "]"; - } - } - else if (isObject(value)) { - if ((nestingIndex = (visited.indexOf(value) + 1))) { - result = "#" + nestingIndex + "#" - } - else { - visited.push(value) - - names = Object.keys(value); - - result += "{ // " + value + "\n"; - result += (isCompact ? names.slice(0, limit) : names).map(function(name) { - var _limit = isCompact ? limit - 1 : limit; - var descriptor = Object.getOwnPropertyDescriptor(value, name); - var result = offset + indent + "// "; - var accessor; - if (0 <= name.indexOf(" ")) - name = '"' + name + '"'; - - if (descriptor.writable) - result += "writable "; - if (descriptor.configurable) - result += "configurable "; - if (descriptor.enumerable) - result += "enumerable "; - - result += "\n"; - if ("value" in descriptor) { - result += offset + indent + name + ": "; - result += source(descriptor.value, indent, _limit, indent + offset, - visited); - } - else { - - if (descriptor.get) { - result += offset + indent + "get " + name + " "; - accessor = source(descriptor.get, indent, _limit, indent + offset, - visited); - result += accessor.substr(accessor.indexOf("{")); - } - - if (descriptor.set) { - result += offset + indent + "set " + name + " "; - accessor = source(descriptor.set, indent, _limit, indent + offset, - visited); - result += accessor.substr(accessor.indexOf("{")); - } - } - return result; - }).join(",\n"); - - if (isCompact) { - if (names.length > limit && limit > 0) { - result += ",\n" + offset + indent + "//..."; - } - } - else { - if (names.length) - result += ","; - - result += "\n" + offset + indent + '"__proto__": '; - result += source(Object.getPrototypeOf(value), indent, 0, - offset + indent); - } - - result += "\n" + offset + "}"; - } - } - else { - result += String(value); - } - return result; -} -exports.source = function (value, indentation, limit) { - return source(value, indentation, limit); -}; diff --git a/addon-sdk/source/lib/sdk/lang/weak-set.js b/addon-sdk/source/lib/sdk/lang/weak-set.js deleted file mode 100644 index 8972602a5..000000000 --- a/addon-sdk/source/lib/sdk/lang/weak-set.js +++ /dev/null @@ -1,75 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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" -}; - -"use strict"; - -const { Cu } = require("chrome"); - -function makeGetterFor(Type) { - let cache = new WeakMap(); - - return { - getFor(target) { - if (!cache.has(target)) - cache.set(target, new Type()); - - return cache.get(target); - }, - clearFor(target) { - return cache.delete(target) - } - } -} - -var {getFor: getLookupFor, clearFor: clearLookupFor} = makeGetterFor(WeakMap); -var {getFor: getRefsFor, clearFor: clearRefsFor} = makeGetterFor(Set); - -function add(target, value) { - if (has(target, value)) - return; - - getLookupFor(target).set(value, true); - getRefsFor(target).add(Cu.getWeakReference(value)); -} -exports.add = add; - -function remove(target, value) { - getLookupFor(target).delete(value); -} -exports.remove = remove; - -function has(target, value) { - return getLookupFor(target).has(value); -} -exports.has = has; - -function clear(target) { - clearLookupFor(target); - clearRefsFor(target); -} -exports.clear = clear; - -function iterator(target) { - let refs = getRefsFor(target); - - for (let ref of refs) { - let value = ref.get(); - - // If `value` is already gc'ed, it would be `null`. - // The `has` function is using a WeakMap as lookup table, so passing `null` - // would raise an exception because WeakMap accepts as value only non-null - // object. - // Plus, if `value` is already gc'ed, we do not have to take it in account - // during the iteration, and remove it from the references. - if (value !== null && has(target, value)) - yield value; - else - refs.delete(ref); - } -} -exports.iterator = iterator; diff --git a/addon-sdk/source/lib/sdk/loader/cuddlefish.js b/addon-sdk/source/lib/sdk/loader/cuddlefish.js deleted file mode 100644 index 6ba19157b..000000000 --- a/addon-sdk/source/lib/sdk/loader/cuddlefish.js +++ /dev/null @@ -1,102 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "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 -}); diff --git a/addon-sdk/source/lib/sdk/loader/sandbox.js b/addon-sdk/source/lib/sdk/loader/sandbox.js deleted file mode 100644 index 791dbc086..000000000 --- a/addon-sdk/source/lib/sdk/loader/sandbox.js +++ /dev/null @@ -1,74 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "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'); - -const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {}); -const { require: devtoolsRequire } = devtools; -const { addContentGlobal, removeContentGlobal } = devtoolsRequire("devtools/server/content-globals"); - -/** - * 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); - let innerWindowID = options.metadata['inner-window-id'] - if (innerWindowID) { - addContentGlobal({ - global: sandbox, - 'inner-window-id': innerWindowID - }); - } - 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.loadSubScript(uri, sandbox, 'UTF-8'); - } -} -exports.load = load; - -/** - * Forces the given `sandbox` to be freed immediately. - */ -exports.nuke = Cu.nukeSandbox diff --git a/addon-sdk/source/lib/sdk/messaging.js b/addon-sdk/source/lib/sdk/messaging.js deleted file mode 100644 index 07580eb33..000000000 --- a/addon-sdk/source/lib/sdk/messaging.js +++ /dev/null @@ -1,12 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { window } = require("sdk/addon/window"); -exports.MessageChannel = window.MessageChannel; -exports.MessagePort = window.MessagePort; diff --git a/addon-sdk/source/lib/sdk/model/core.js b/addon-sdk/source/lib/sdk/model/core.js deleted file mode 100644 index 315f8b1cd..000000000 --- a/addon-sdk/source/lib/sdk/model/core.js +++ /dev/null @@ -1,23 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"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; diff --git a/addon-sdk/source/lib/sdk/net/url.js b/addon-sdk/source/lib/sdk/net/url.js deleted file mode 100644 index 5502171ee..000000000 --- a/addon-sdk/source/lib/sdk/net/url.js +++ /dev/null @@ -1,94 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -const { Ci, Cu, components } = require("chrome"); - -const { defer } = require("../core/promise"); -const { merge } = require("../util/object"); - -const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); -const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); - -/** - * Reads a URI and returns a promise. - * - * @param uri {string} The URI to read - * @param [options] {object} This parameter can have any or all of the following - * fields: `charset`. By default the `charset` is set to 'UTF-8'. - * - * @returns {promise} The promise that will be resolved with the content of the - * URL given. - * - * @example - * let promise = readURI('resource://gre/modules/NetUtil.jsm', { - * charset: 'US-ASCII' - * }); - */ -function readURI(uri, options) { - options = options || {}; - let charset = options.charset || 'UTF-8'; - - let channel = NetUtil.newChannel({ - uri: NetUtil.newURI(uri, charset), - loadUsingSystemPrincipal: true}); - - let { promise, resolve, reject } = defer(); - - try { - NetUtil.asyncFetch(channel, function (stream, result) { - if (components.isSuccessCode(result)) { - let count = stream.available(); - let data = NetUtil.readInputStreamToString(stream, count, { charset : charset }); - - resolve(data); - } else { - reject("Failed to read: '" + uri + "' (Error Code: " + result + ")"); - } - }); - } - catch (e) { - reject("Failed to read: '" + uri + "' (Error: " + e.message + ")"); - } - - return promise; -} - -exports.readURI = readURI; - -/** - * Reads a URI synchronously. - * This function is intentionally undocumented to favorites the `readURI` usage. - * - * @param uri {string} The URI to read - * @param [charset] {string} The character set to use when read the content of - * the `uri` given. By default is set to 'UTF-8'. - * - * @returns {string} The content of the URI given. - * - * @example - * let data = readURISync('resource://gre/modules/NetUtil.jsm'); - */ -function readURISync(uri, charset) { - charset = typeof charset === "string" ? charset : "UTF-8"; - - let channel = NetUtil.newChannel({ - uri: NetUtil.newURI(uri, charset), - loadUsingSystemPrincipal: true}); - let stream = channel.open2(); - - let count = stream.available(); - let data = NetUtil.readInputStreamToString(stream, count, { charset : charset }); - - stream.close(); - - return data; -} - -exports.readURISync = readURISync; diff --git a/addon-sdk/source/lib/sdk/net/xhr.js b/addon-sdk/source/lib/sdk/net/xhr.js deleted file mode 100644 index 415b9cbf4..000000000 --- a/addon-sdk/source/lib/sdk/net/xhr.js +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "stability": "stable" -}; - -const { deprecateFunction } = require("../util/deprecate"); -const { Cc, Ci } = require("chrome"); -const XMLHttpRequest = require("../addon/window").window.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. diff --git a/addon-sdk/source/lib/sdk/notifications.js b/addon-sdk/source/lib/sdk/notifications.js deleted file mode 100644 index 752e08fb1..000000000 --- a/addon-sdk/source/lib/sdk/notifications.js +++ /dev/null @@ -1,112 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"] - } - }); -} diff --git a/addon-sdk/source/lib/sdk/output/system.js b/addon-sdk/source/lib/sdk/output/system.js deleted file mode 100644 index 4fb16dcd5..000000000 --- a/addon-sdk/source/lib/sdk/output/system.js +++ /dev/null @@ -1,71 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { 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; diff --git a/addon-sdk/source/lib/sdk/page-mod.js b/addon-sdk/source/lib/sdk/page-mod.js deleted file mode 100644 index 538be2732..000000000 --- a/addon-sdk/source/lib/sdk/page-mod.js +++ /dev/null @@ -1,190 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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'); -const { Worker } = require('./content/worker'); -const { EventTarget } = require('./event/target'); -const { on, emit, once, setListeners } = require('./event/core'); -const { isRegExp, isUndefined } = require('./lang/type'); -const { merge, omit } = require('./util/object'); -const { remove, has, hasAny } = require("./util/array"); -const { Rules } = require("./util/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); -}); diff --git a/addon-sdk/source/lib/sdk/page-mod/match-pattern.js b/addon-sdk/source/lib/sdk/page-mod/match-pattern.js deleted file mode 100644 index afbbd401e..000000000 --- a/addon-sdk/source/lib/sdk/page-mod/match-pattern.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - -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"); diff --git a/addon-sdk/source/lib/sdk/page-worker.js b/addon-sdk/source/lib/sdk/page-worker.js deleted file mode 100644 index 837cf774b..000000000 --- a/addon-sdk/source/lib/sdk/page-worker.js +++ /dev/null @@ -1,194 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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'); -const { pipe, stripListeners } = require('./event/utils'); -const { connect, destroy, WorkerHost } = require('./content/utils'); -const { Worker } = require('./content/worker'); -const { Disposable } = require('./core/disposable'); -const { EventTarget } = require('./event/target'); -const { setListeners } = require('./event/core'); -const { window } = require('./addon/window'); -const { create: makeFrame, getDocShell } = require('./frame/utils'); -const { contract } = require('./util/contract'); -const { contract: loaderContract } = require('./content/loader'); -const { Rules } = require('./util/rules'); -const { merge } = require('./util/object'); -const { uuid } = require('./util/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); -}); diff --git a/addon-sdk/source/lib/sdk/panel.js b/addon-sdk/source/lib/sdk/panel.js deleted file mode 100644 index 4b625799d..000000000 --- a/addon-sdk/source/lib/sdk/panel.js +++ /dev/null @@ -1,427 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"); -const { setTimeout } = require('./timers'); -const { Class } = require("./core/heritage"); -const { merge } = require("./util/object"); -const { WorkerHost } = require("./content/utils"); -const { Worker } = require("./deprecated/sync-worker"); -const { Disposable } = require("./core/disposable"); -const { WeakReference } = require('./core/reference'); -const { contract: loaderContract } = require("./content/loader"); -const { contract } = require("./util/contract"); -const { on, off, emit, setListeners } = require("./event/core"); -const { EventTarget } = require("./event/target"); -const domPanel = require("./panel/utils"); -const { getDocShell } = require('./frame/utils'); -const { events } = require("./panel/events"); -const systemEvents = require("./system/events"); -const { filter, pipe, stripListeners } = require("./event/utils"); -const { getNodeView, getActiveView } = require("./view/core"); -const { isNil, isObject, isNumber } = require("./lang/type"); -const { getAttachEventType } = require("./content/utils"); -const { number, boolean, object } = require('./deprecated/api-utils'); -const { Style } = require("./stylesheet/style"); -const { attach, detach } = require("./content/mod"); - -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 panels = new WeakMap(); -var models = new WeakMap(); -var views = new WeakMap(); -var workers = new WeakMap(); -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, false); - 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, false); - 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 - })); - } - - // Setup view - let viewOptions = {allowJavascript: !model.allow || (model.allow.script !== false)}; - let view = domPanel.make(null, viewOptions); - panels.set(view, this); - views.set(this, view); - - // Load panel content. - domPanel.setURL(view, model.contentURL); - - // Allow context menu - domPanel.allowContextMenu(view, model.contextMenu); - - // Setup listeners. - setListeners(this, options); - let worker = new Worker(stripListeners(options)); - workers.set(this, worker); - - // pipe events from worker to a panel. - pipe(worker, this); - }, - dispose: function dispose() { - this.hide(); - off(this); - - workerFor(this).destroy(); - detach(styleFor(this)); - - domPanel.dispose(viewFor(this)); - - // Release circular reference between view and panel instance. This - // way view will be GC-ed. And panel as well once all the other refs - // will be removed from it. - 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) { - 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 view = viewFor(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); -}); diff --git a/addon-sdk/source/lib/sdk/panel/events.js b/addon-sdk/source/lib/sdk/panel/events.js deleted file mode 100644 index f3040a11d..000000000 --- a/addon-sdk/source/lib/sdk/panel/events.js +++ /dev/null @@ -1,27 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"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"); -const { emit } = require("../event/core"); - -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; diff --git a/addon-sdk/source/lib/sdk/panel/utils.js b/addon-sdk/source/lib/sdk/panel/utils.js deleted file mode 100644 index c85b274bc..000000000 --- a/addon-sdk/source/lib/sdk/panel/utils.js +++ /dev/null @@ -1,451 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"); -const { setTimeout } = require("../timers"); -const { platform } = require("../system"); -const { getMostRecentBrowserWindow, getOwnerBrowserWindow, - getHiddenWindow, getScreenPixelsPerCSSPixel } = require("../window/utils"); - -const { create: createFrame, swapFrameLoaders, getDocShell } = require("../frame/utils"); -const { window: addonWindow } = require("../addon/window"); -const { isNil } = require("../lang/type"); -const { data } = require('../self'); - -const events = require("../system/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.ownerDocument.defaultView; - - // 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, false); - - // 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.parentNode.removeChild(panel); -} -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; diff --git a/addon-sdk/source/lib/sdk/passwords.js b/addon-sdk/source/lib/sdk/passwords.js deleted file mode 100644 index 70f0aa4da..000000000 --- a/addon-sdk/source/lib/sdk/passwords.js +++ /dev/null @@ -1,61 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -module.metadata = { - "stability": "stable" -}; - -const { search, remove, store } = require("./passwords/utils"); -const { defer, delay } = require("./lang/functional"); - -/** - * Utility function that returns `onComplete` and `onError` callbacks form the - * given `options` objects. Also properties are removed from the passed - * `options` objects. - * @param {Object} options - * Object that is passed to the exported functions of this module. - * @returns {Function[]} - * Array with two elements `onComplete` and `onError` functions. - */ -function getCallbacks(options) { - let value = [ - 'onComplete' in options ? options.onComplete : null, - 'onError' in options ? defer(options.onError) : console.exception - ]; - - delete options.onComplete; - delete options.onError; - - return value; -}; - -/** - * Creates a wrapper function that tries to call `onComplete` with a return - * value of the wrapped function or falls back to `onError` if wrapped function - * throws an exception. - */ -function createWrapperMethod(wrapped) { - return function (options) { - let [ onComplete, onError ] = getCallbacks(options); - try { - let value = wrapped(options); - if (onComplete) { - delay(function() { - try { - onComplete(value); - } catch (exception) { - onError(exception); - } - }); - } - } catch (exception) { - onError(exception); - } - }; -} - -exports.search = createWrapperMethod(search); -exports.store = createWrapperMethod(store); -exports.remove = createWrapperMethod(remove); diff --git a/addon-sdk/source/lib/sdk/passwords/utils.js b/addon-sdk/source/lib/sdk/passwords/utils.js deleted file mode 100644 index 334efa490..000000000 --- a/addon-sdk/source/lib/sdk/passwords/utils.js +++ /dev/null @@ -1,107 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci, CC } = require("chrome"); -const { uri: ADDON_URI } = require("../self"); -const loginManager = Cc["@mozilla.org/login-manager;1"]. - getService(Ci.nsILoginManager); -const { URL: parseURL } = require("../url"); -const LoginInfo = CC("@mozilla.org/login-manager/loginInfo;1", - "nsILoginInfo", "init"); - -function filterMatchingLogins(loginInfo) { - return Object.keys(this).every(key => loginInfo[key] === this[key], this); -} - -/** - * Removes `user`, `password` and `path` fields from the given `url` if it's - * 'http', 'https' or 'ftp'. All other URLs are returned unchanged. - * @example - * http://user:pass@www.site.com/foo/?bar=baz#bang -> http://www.site.com - */ -function normalizeURL(url) { - let { scheme, host, port } = parseURL(url); - // We normalize URL only if it's `http`, `https` or `ftp`. All other types of - // URLs (`resource`, `chrome`, etc..) should not be normalized as they are - // used with add-on associated credentials path. - return scheme === "http" || scheme === "https" || scheme === "ftp" ? - scheme + "://" + (host || "") + (port ? ":" + port : "") : - url -} - -function Login(options) { - let login = Object.create(Login.prototype); - Object.keys(options || {}).forEach(function(key) { - if (key === 'url') - login.hostname = normalizeURL(options.url); - else if (key === 'formSubmitURL') - login.formSubmitURL = options.formSubmitURL ? - normalizeURL(options.formSubmitURL) : null; - else if (key === 'realm') - login.httpRealm = options.realm; - else - login[key] = options[key]; - }); - - return login; -} -Login.prototype.toJSON = function toJSON() { - return { - url: this.hostname || ADDON_URI, - realm: this.httpRealm || null, - formSubmitURL: this.formSubmitURL || null, - username: this.username || null, - password: this.password || null, - usernameField: this.usernameField || '', - passwordField: this.passwordField || '', - } -}; -Login.prototype.toLoginInfo = function toLoginInfo() { - let { url, realm, formSubmitURL, username, password, usernameField, - passwordField } = this.toJSON(); - - return new LoginInfo(url, formSubmitURL, realm, username, password, - usernameField, passwordField); -}; - -function loginToJSON(value) { - return Login(value).toJSON(); -} - -/** - * Returns array of `nsILoginInfo` objects that are stored in the login manager - * and have all the properties with matching values as a given `options` object. - * @param {Object} options - * @returns {nsILoginInfo[]} - */ -exports.search = function search(options) { - return loginManager.getAllLogins() - .filter(filterMatchingLogins, Login(options)) - .map(loginToJSON); -}; - -/** - * Stores login info created from the given `options` to the applications - * built-in login management system. - * @param {Object} options. - */ -exports.store = function store(options) { - loginManager.addLogin(Login(options).toLoginInfo()); -}; - -/** - * Removes login info from the applications built-in login management system. - * _Please note: When removing a login info the specified properties must - * exactly match to the one that is already stored or exception will be thrown._ - * @param {Object} options. - */ -exports.remove = function remove(options) { - loginManager.removeLogin(Login(options).toLoginInfo()); -}; diff --git a/addon-sdk/source/lib/sdk/places/bookmarks.js b/addon-sdk/source/lib/sdk/places/bookmarks.js deleted file mode 100644 index c4f9528f1..000000000 --- a/addon-sdk/source/lib/sdk/places/bookmarks.js +++ /dev/null @@ -1,395 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -/* - * Requiring hosts so they can subscribe to client messages - */ -require('./host/host-bookmarks'); -require('./host/host-tags'); -require('./host/host-query'); - -const { Cc, Ci } = require('chrome'); -const { Class } = require('../core/heritage'); -const { send } = require('../addon/events'); -const { defer, reject, all, resolve, promised } = require('../core/promise'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const { identity, defer:async } = require('../lang/functional'); -const { extend, merge } = require('../util/object'); -const { fromIterator } = require('../util/array'); -const { - constructTree, fetchItem, createQuery, - isRootGroup, createQueryOptions -} = require('./utils'); -const { - bookmarkContract, groupContract, separatorContract -} = require('./contract'); -const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); - -/* - * Mapping of uncreated bookmarks with their created - * counterparts - */ -const itemMap = new WeakMap(); - -/* - * Constant used by nsIHistoryQuery; 1 is a bookmark query - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions - */ -const BOOKMARK_QUERY = 1; - -/* - * Bookmark Item classes - */ - -const Bookmark = Class({ - extends: [ - bookmarkContract.properties(identity) - ], - initialize: function initialize (options) { - merge(this, bookmarkContract(extend(defaults, options))); - }, - type: 'bookmark', - toString: () => '[object Bookmark]' -}); -exports.Bookmark = Bookmark; - -const Group = Class({ - extends: [ - groupContract.properties(identity) - ], - initialize: function initialize (options) { - // Don't validate if root group - if (isRootGroup(options)) - merge(this, options); - else - merge(this, groupContract(extend(defaults, options))); - }, - type: 'group', - toString: () => '[object Group]' -}); -exports.Group = Group; - -const Separator = Class({ - extends: [ - separatorContract.properties(identity) - ], - initialize: function initialize (options) { - merge(this, separatorContract(extend(defaults, options))); - }, - type: 'separator', - toString: () => '[object Separator]' -}); -exports.Separator = Separator; - -/* - * Functions - */ - -function save (items, options) { - items = [].concat(items); - options = options || {}; - let emitter = EventTarget(); - let results = []; - let errors = []; - let root = constructTree(items); - let cache = new Map(); - - let isExplicitSave = item => !!~items.indexOf(item); - // `walk` returns an aggregate promise indicating the completion - // of the `commitItem` on each node, not whether or not that - // commit was successful - - // Force this to be async, as if a ducktype fails validation, - // the promise implementation will fire an error event, which will - // not trigger the handler as it's not yet bound - // - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => root.walk(preCommitItem).then(commitComplete))(); - - function preCommitItem ({value:item}) { - // Do nothing if tree root, default group (unsavable), - // or if it's a dependency and not explicitly saved (in the list - // of items to be saved), and not needed to be saved - if (item === null || // node is the tree root - isRootGroup(item) || - (getId(item) && !isExplicitSave(item))) - return; - - return promised(validate)(item) - .then(() => commitItem(item, options)) - .then(data => construct(data, cache)) - .then(savedItem => { - // If item was just created, make a map between - // the creation object and created object, - // so we can reference the item that doesn't have an id - if (!getId(item)) - saveId(item, savedItem.id); - - // Emit both the processed item, and original item - // so a mapping can be understood in handler - emit(emitter, 'data', savedItem, item); - - // Push to results iff item was explicitly saved - if (isExplicitSave(item)) - results[items.indexOf(item)] = savedItem; - }, reason => { - // Force reason to be a string for consistency - reason = reason + ''; - // Emit both the reason, and original item - // so a mapping can be understood in handler - emit(emitter, 'error', reason + '', item); - // Store unsaved item in results list - results[items.indexOf(item)] = item; - errors.push(reason); - }); - } - - // Called when traversal of the node tree is completed and all - // items have been committed - function commitComplete () { - emit(emitter, 'end', results); - } - - return emitter; -} -exports.save = save; - -function search (queries, options) { - queries = [].concat(queries); - let emitter = EventTarget(); - let cache = new Map(); - let queryObjs = queries.map(createQuery.bind(null, BOOKMARK_QUERY)); - let optionsObj = createQueryOptions(BOOKMARK_QUERY, options); - - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => { - send('sdk-places-query', { queries: queryObjs, options: optionsObj }) - .then(handleQueryResponse); - })(); - - function handleQueryResponse (data) { - let deferreds = data.map(item => { - return construct(item, cache).then(bookmark => { - emit(emitter, 'data', bookmark); - return bookmark; - }, reason => { - emit(emitter, 'error', reason); - errors.push(reason); - }); - }); - - all(deferreds).then(data => { - emit(emitter, 'end', data); - }, () => emit(emitter, 'end', [])); - } - - return emitter; -} -exports.search = search; - -function remove (items) { - return [].concat(items).map(item => { - item.remove = true; - return item; - }); -} - -exports.remove = remove; - -/* - * Internal Utilities - */ - -function commitItem (item, options) { - // Get the item's ID, or getId it's saved version if it exists - let id = getId(item); - let data = normalize(item); - let promise; - - data.id = id; - - if (!id) { - promise = send('sdk-places-bookmarks-create', data); - } else if (item.remove) { - promise = send('sdk-places-bookmarks-remove', { id: id }); - } else { - promise = send('sdk-places-bookmarks-last-updated', { - id: id - }).then(function (updated) { - // If attempting to save an item that is not the - // latest snapshot of a bookmark item, execute - // the resolution function - if (updated !== item.updated && options.resolve) - return fetchItem(id) - .then(options.resolve.bind(null, data)); - else - return data; - }).then(send.bind(null, 'sdk-places-bookmarks-save')); - } - - return promise; -} - -/* - * Turns a bookmark item into a plain object, - * converts `tags` from Set to Array, group instance to an id - */ -function normalize (item) { - let data = merge({}, item); - // Circumvent prototype property of `type` - delete data.type; - data.type = item.type; - data.tags = []; - if (item.tags) { - data.tags = fromIterator(item.tags); - } - data.group = getId(data.group) || exports.UNSORTED.id; - - return data; -} - -/* - * Takes a data object and constructs a BookmarkItem instance - * of it, recursively generating parent instances as well. - * - * Pass in a `cache` Map to reuse instances of - * bookmark items to reduce overhead; - * The cache object is a map of id to a deferred with a - * promise that resolves to the bookmark item. - */ -function construct (object, cache, forced) { - let item = instantiate(object); - let deferred = defer(); - - // Item could not be instantiated - if (!item) - return resolve(null); - - // Return promise for item if found in the cache, - // and not `forced`. `forced` indicates that this is the construct - // call that should not read from cache, but should actually perform - // the construction, as it was set before several async calls - if (cache.has(item.id) && !forced) - return cache.get(item.id).promise; - else if (cache.has(item.id)) - deferred = cache.get(item.id); - else - cache.set(item.id, deferred); - - // When parent group is found in cache, use - // the same deferred value - if (item.group && cache.has(item.group)) { - cache.get(item.group).promise.then(group => { - item.group = group; - deferred.resolve(item); - }); - - // If not in the cache, and a root group, return - // the premade instance - } else if (rootGroups.get(item.group)) { - item.group = rootGroups.get(item.group); - deferred.resolve(item); - - // If not in the cache or a root group, fetch the parent - } else { - cache.set(item.group, defer()); - fetchItem(item.group).then(group => { - return construct(group, cache, true); - }).then(group => { - item.group = group; - deferred.resolve(item); - }, deferred.reject); - } - - return deferred.promise; -} - -function instantiate (object) { - if (object.type === 'bookmark') - return Bookmark(object); - if (object.type === 'group') - return Group(object); - if (object.type === 'separator') - return Separator(object); - return null; -} - -/** - * Validates a bookmark item; will throw an error if ininvalid, - * to be used with `promised`. As bookmark items check on their class, - * this only checks ducktypes - */ -function validate (object) { - if (!isDuckType(object)) return true; - let contract = object.type === 'bookmark' ? bookmarkContract : - object.type === 'group' ? groupContract : - object.type === 'separator' ? separatorContract : - null; - if (!contract) { - throw Error('No type specified'); - } - - // If object has a property set, and undefined, - // manually override with default as it'll fail otherwise - let withDefaults = Object.keys(defaults).reduce((obj, prop) => { - if (obj[prop] == null) obj[prop] = defaults[prop]; - return obj; - }, extend(object)); - - contract(withDefaults); -} - -function isDuckType (item) { - return !(item instanceof Bookmark) && - !(item instanceof Group) && - !(item instanceof Separator); -} - -function saveId (unsaved, id) { - itemMap.set(unsaved, id); -} - -// Fetches an item's ID from itself, or from the mapped items -function getId (item) { - return typeof item === 'number' ? item : - item ? item.id || itemMap.get(item) : - null; -} - -/* - * Set up the default, root groups - */ - -var defaultGroupMap = { - MENU: bmsrv.bookmarksMenuFolder, - TOOLBAR: bmsrv.toolbarFolder, - UNSORTED: bmsrv.unfiledBookmarksFolder -}; - -var rootGroups = new Map(); - -for (let i in defaultGroupMap) { - let group = Object.freeze(Group({ title: i, id: defaultGroupMap[i] })); - rootGroups.set(defaultGroupMap[i], group); - exports[i] = group; -} - -var defaults = { - group: exports.UNSORTED, - index: -1 -}; diff --git a/addon-sdk/source/lib/sdk/places/contract.js b/addon-sdk/source/lib/sdk/places/contract.js deleted file mode 100644 index a3541c34d..000000000 --- a/addon-sdk/source/lib/sdk/places/contract.js +++ /dev/null @@ -1,73 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci } = require('chrome'); -const { isValidURI, URL } = require('../url'); -const { contract } = require('../util/contract'); -const { extend } = require('../util/object'); - -// map of property validations -const validItem = { - id: { - is: ['number', 'undefined', 'null'], - }, - group: { - is: ['object', 'number', 'undefined', 'null'], - ok: function (value) { - return value && - (value.toString && value.toString() === '[object Group]') || - typeof value === 'number' || - value.type === 'group'; - }, - msg: 'The `group` property must be a valid Group object' - }, - index: { - is: ['undefined', 'null', 'number'], - map: value => value == null ? -1 : value, - msg: 'The `index` property must be a number.' - }, - updated: { - is: ['number', 'undefined'] - } -}; - -const validTitle = { - title: { - is: ['string'], - msg: 'The `title` property must be defined.' - } -}; - -const validURL = { - url: { - is: ['string'], - ok: isValidURI, - msg: 'The `url` property must be a valid URL.' - } -}; - -const validTags = { - tags: { - is: ['object'], - ok: tags => tags instanceof Set, - map: function (tags) { - if (Array.isArray(tags)) - return new Set(tags); - if (tags == null) - return new Set(); - return tags; - }, - msg: 'The `tags` property must be a Set, or an array' - } -}; - -exports.bookmarkContract = contract( - extend(validItem, validTitle, validURL, validTags)); -exports.separatorContract = contract(validItem); -exports.groupContract = contract(extend(validItem, validTitle)); diff --git a/addon-sdk/source/lib/sdk/places/events.js b/addon-sdk/source/lib/sdk/places/events.js deleted file mode 100644 index a3f95ee03..000000000 --- a/addon-sdk/source/lib/sdk/places/events.js +++ /dev/null @@ -1,128 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -module.metadata = { - 'stability': 'experimental', - 'engines': { - 'Firefox': '*', - "SeaMonkey": '*' - } -}; - -const { Cc, Ci } = require('chrome'); -const { Unknown } = require('../platform/xpcom'); -const { Class } = require('../core/heritage'); -const { merge } = require('../util/object'); -const bookmarkService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] - .getService(Ci.nsINavBookmarksService); -const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] - .getService(Ci.nsINavHistoryService); -const { mapBookmarkItemType } = require('./utils'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const { when } = require('../system/unload'); - -const emitter = EventTarget(); - -var HISTORY_ARGS = { - onBeginUpdateBatch: [], - onEndUpdateBatch: [], - onClearHistory: [], - onDeleteURI: ['url'], - onDeleteVisits: ['url', 'visitTime'], - onPageChanged: ['url', 'property', 'value'], - onTitleChanged: ['url', 'title'], - onVisit: [ - 'url', 'visitId', 'time', 'sessionId', 'referringId', 'transitionType' - ] -}; - -var HISTORY_EVENTS = { - onBeginUpdateBatch: 'history-start-batch', - onEndUpdateBatch: 'history-end-batch', - onClearHistory: 'history-start-clear', - onDeleteURI: 'history-delete-url', - onDeleteVisits: 'history-delete-visits', - onPageChanged: 'history-page-changed', - onTitleChanged: 'history-title-changed', - onVisit: 'history-visit' -}; - -var BOOKMARK_ARGS = { - onItemAdded: [ - 'id', 'parentId', 'index', 'type', 'url', 'title', 'dateAdded' - ], - onItemChanged: [ - 'id', 'property', null, 'value', 'lastModified', 'type', 'parentId' - ], - onItemMoved: [ - 'id', 'previousParentId', 'previousIndex', 'currentParentId', - 'currentIndex', 'type' - ], - onItemRemoved: ['id', 'parentId', 'index', 'type', 'url'], - onItemVisited: ['id', 'visitId', 'time', 'transitionType', 'url', 'parentId'] -}; - -var BOOKMARK_EVENTS = { - onItemAdded: 'bookmark-item-added', - onItemChanged: 'bookmark-item-changed', - onItemMoved: 'bookmark-item-moved', - onItemRemoved: 'bookmark-item-removed', - onItemVisited: 'bookmark-item-visited', -}; - -function createHandler (type, propNames) { - propNames = propNames || []; - return function (...args) { - let data = propNames.reduce((acc, prop, i) => { - if (prop) - acc[prop] = formatValue(prop, args[i]); - return acc; - }, {}); - - emit(emitter, 'data', { - type: type, - data: data - }); - }; -} - -/* - * Creates an observer, creating handlers based off of - * the `events` names, and ordering arguments from `propNames` hash - */ -function createObserverInstance (events, propNames) { - let definition = Object.keys(events).reduce((prototype, eventName) => { - prototype[eventName] = createHandler(events[eventName], propNames[eventName]); - return prototype; - }, {}); - - return Class(merge(definition, { extends: Unknown }))(); -} - -/* - * Formats `data` based off of the value of `type` - */ -function formatValue (type, data) { - if (type === 'type') - return mapBookmarkItemType(data); - if (type === 'url' && data) - return data.spec; - return data; -} - -var historyObserver = createObserverInstance(HISTORY_EVENTS, HISTORY_ARGS); -historyService.addObserver(historyObserver, false); - -var bookmarkObserver = createObserverInstance(BOOKMARK_EVENTS, BOOKMARK_ARGS); -bookmarkService.addObserver(bookmarkObserver, false); - -when(() => { - historyService.removeObserver(historyObserver); - bookmarkService.removeObserver(bookmarkObserver); -}); - -exports.events = emitter; diff --git a/addon-sdk/source/lib/sdk/places/favicon.js b/addon-sdk/source/lib/sdk/places/favicon.js deleted file mode 100644 index 05b057db1..000000000 --- a/addon-sdk/source/lib/sdk/places/favicon.js +++ /dev/null @@ -1,49 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "unstable", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci, Cu } = require("chrome"); -const { defer, reject } = require("../core/promise"); -const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"]. - getService(Ci.nsIFaviconService); -const AsyncFavicons = FaviconService.QueryInterface(Ci.mozIAsyncFavicons); -const { isValidURI } = require("../url"); -const { newURI, getURL } = require("../url/utils"); - -/** - * Takes an object of several possible types and - * returns a promise that resolves to the page's favicon URI. - * @param {String|Tab} object - * @param {Function} (callback) - * @returns {Promise} - */ - -function getFavicon (object, callback) { - let url = getURL(object); - let deferred = defer(); - - if (url && isValidURI(url)) { - AsyncFavicons.getFaviconURLForPage(newURI(url), function (aURI) { - if (aURI && aURI.spec) - deferred.resolve(aURI.spec.toString()); - else - deferred.reject(null); - }); - } else { - deferred.reject(null); - } - - if (callback) deferred.promise.then(callback, callback); - return deferred.promise; -} -exports.getFavicon = getFavicon; diff --git a/addon-sdk/source/lib/sdk/places/history.js b/addon-sdk/source/lib/sdk/places/history.js deleted file mode 100644 index b243b024c..000000000 --- a/addon-sdk/source/lib/sdk/places/history.js +++ /dev/null @@ -1,65 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "unstable", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -/* - * Requiring hosts so they can subscribe to client messages - */ -require('./host/host-bookmarks'); -require('./host/host-tags'); -require('./host/host-query'); - -const { Cc, Ci } = require('chrome'); -const { Class } = require('../core/heritage'); -const { events, send } = require('../addon/events'); -const { defer, reject, all } = require('../core/promise'); -const { uuid } = require('../util/uuid'); -const { flatten } = require('../util/array'); -const { has, extend, merge, pick } = require('../util/object'); -const { emit } = require('../event/core'); -const { defer: async } = require('../lang/functional'); -const { EventTarget } = require('../event/target'); -const { - urlQueryParser, createQuery, createQueryOptions -} = require('./utils'); - -/* - * Constant used by nsIHistoryQuery; 0 is a history query - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions - */ -const HISTORY_QUERY = 0; - -var search = function query (queries, options) { - queries = [].concat(queries); - let emitter = EventTarget(); - let queryObjs = queries.map(createQuery.bind(null, HISTORY_QUERY)); - let optionsObj = createQueryOptions(HISTORY_QUERY, options); - - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => { - send('sdk-places-query', { - query: queryObjs, - options: optionsObj - }).then(results => { - results.map(item => emit(emitter, 'data', item)); - emit(emitter, 'end', results); - }, reason => { - emit(emitter, 'error', reason); - emit(emitter, 'end', []); - }); - })(); - - return emitter; -}; -exports.search = search; diff --git a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js b/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js deleted file mode 100644 index 3245c4070..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js +++ /dev/null @@ -1,238 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci } = require('chrome'); -const browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"]. - getService(Ci.nsIBrowserHistory); -const asyncHistory = Cc["@mozilla.org/browser/history;1"]. - getService(Ci.mozIAsyncHistory); -const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); -const taggingService = Cc["@mozilla.org/browser/tagging-service;1"]. - getService(Ci.nsITaggingService); -const ios = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); -const { query } = require('./host-query'); -const { - defer, all, resolve, promised, reject -} = require('../../core/promise'); -const { request, response } = require('../../addon/host'); -const { send } = require('../../addon/events'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); -const { URL, isValidURI } = require('../../url'); -const { newURI } = require('../../url/utils'); - -const DEFAULT_INDEX = bmsrv.DEFAULT_INDEX; -const UNSORTED_ID = bmsrv.unfiledBookmarksFolder; -const ROOT_FOLDERS = [ - bmsrv.unfiledBookmarksFolder, bmsrv.toolbarFolder, - bmsrv.tagsFolder, bmsrv.bookmarksMenuFolder -]; - -const EVENT_MAP = { - 'sdk-places-bookmarks-create': createBookmarkItem, - 'sdk-places-bookmarks-save': saveBookmarkItem, - 'sdk-places-bookmarks-last-updated': getBookmarkLastUpdated, - 'sdk-places-bookmarks-get': getBookmarkItem, - 'sdk-places-bookmarks-remove': removeBookmarkItem, - 'sdk-places-bookmarks-get-all': getAllBookmarks, - 'sdk-places-bookmarks-get-children': getChildren -}; - -function typeMap (type) { - if (typeof type === 'number') { - if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; - if (bmsrv.TYPE_FOLDER === type) return 'group'; - if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; - } else { - if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; - if ('group' === type) return bmsrv.TYPE_FOLDER; - if ('separator' === type) return bmsrv.TYPE_SEPARATOR; - } -} - -function getBookmarkLastUpdated ({id}) { - return resolve(bmsrv.getItemLastModified(id)); -} -exports.getBookmarkLastUpdated; - -function createBookmarkItem (data) { - let error; - - if (data.group == null) data.group = UNSORTED_ID; - if (data.index == null) data.index = DEFAULT_INDEX; - - if (data.type === 'group') - data.id = bmsrv.createFolder( - data.group, data.title, data.index - ); - else if (data.type === 'separator') - data.id = bmsrv.insertSeparator( - data.group, data.index - ); - else - data.id = bmsrv.insertBookmark( - data.group, newURI(data.url), data.index, data.title - ); - - // In the event where default or no index is provided (-1), - // query the actual index for the response - if (data.index === -1) - data.index = bmsrv.getItemIndex(data.id); - - try { - data.updated = bmsrv.getItemLastModified(data.id); - } - catch (e) { - console.exception(e); - } - - return tag(data, true).then(() => data); -} -exports.createBookmarkItem = createBookmarkItem; - -function saveBookmarkItem (data) { - let id = data.id; - if (!id) - reject('Item is missing id'); - - let group = bmsrv.getFolderIdForItem(id); - let index = bmsrv.getItemIndex(id); - let type = bmsrv.getItemType(id); - let title = typeMap(type) !== 'separator' ? - bmsrv.getItemTitle(id) : - undefined; - let url = typeMap(type) === 'bookmark' ? - bmsrv.getBookmarkURI(id).spec : - undefined; - - if (url != data.url) - bmsrv.changeBookmarkURI(id, newURI(data.url)); - else if (typeMap(type) === 'bookmark') - data.url = url; - - if (title != data.title) - bmsrv.setItemTitle(id, data.title); - else if (typeMap(type) !== 'separator') - data.title = title; - - if (data.group && data.group !== group) - bmsrv.moveItem(id, data.group, data.index || -1); - else if (data.index != null && data.index !== index) { - // We use moveItem here instead of setItemIndex - // so we don't have to manage the indicies of the siblings - bmsrv.moveItem(id, group, data.index); - } else if (data.index == null) - data.index = index; - - data.updated = bmsrv.getItemLastModified(data.id); - - return tag(data).then(() => data); -} -exports.saveBookmarkItem = saveBookmarkItem; - -function removeBookmarkItem (data) { - let id = data.id; - - if (!id) - reject('Item is missing id'); - - bmsrv.removeItem(id); - return resolve(null); -} -exports.removeBookmarkItem = removeBookmarkItem; - -function getBookmarkItem (data) { - let id = data.id; - - if (!id) - reject('Item is missing id'); - - let type = bmsrv.getItemType(id); - - data.type = typeMap(type); - - if (type === bmsrv.TYPE_BOOKMARK || type === bmsrv.TYPE_FOLDER) - data.title = bmsrv.getItemTitle(id); - - if (type === bmsrv.TYPE_BOOKMARK) { - data.url = bmsrv.getBookmarkURI(id).spec; - // Should be moved into host-tags as a method - data.tags = taggingService.getTagsForURI(newURI(data.url), {}); - } - - data.group = bmsrv.getFolderIdForItem(id); - data.index = bmsrv.getItemIndex(id); - data.updated = bmsrv.getItemLastModified(data.id); - - return resolve(data); -} -exports.getBookmarkItem = getBookmarkItem; - -function getAllBookmarks () { - return query({}, { queryType: 1 }).then(bookmarks => - all(bookmarks.map(getBookmarkItem))); -} -exports.getAllBookmarks = getAllBookmarks; - -function getChildren ({ id }) { - if (typeMap(bmsrv.getItemType(id)) !== 'group') return []; - let ids = []; - for (let i = 0; ids[ids.length - 1] !== -1; i++) - ids.push(bmsrv.getIdForItemAt(id, i)); - ids.pop(); - return all(ids.map(id => getBookmarkItem({ id: id }))); -} -exports.getChildren = getChildren; - -/* - * Hook into host - */ - -var reqStream = filter(request, (data) => /sdk-places-bookmarks/.test(data.event)); -on(reqStream, 'data', ({ event, id, data }) => { - if (!EVENT_MAP[event]) return; - - let resData = { id: id, event: event }; - - promised(EVENT_MAP[event])(data). - then(res => resData.data = res, e => resData.error = e). - then(() => emit(response, 'data', resData)); -}); - -function tag (data, isNew) { - // If a new item, we can skip checking what other tags - // are on the item - if (data.type !== 'bookmark') { - return resolve(); - } - else if (!isNew) { - return send('sdk-places-tags-get-tags-by-url', { url: data.url }) - .then(tags => { - return send('sdk-places-tags-untag', { - tags: tags.filter(tag => !~data.tags.indexOf(tag)), - url: data.url - }); - }).then(() => send('sdk-places-tags-tag', { - url: data.url, tags: data.tags - })); - } - else if (data.tags && data.tags.length) { - return send('sdk-places-tags-tag', { url: data.url, tags: data.tags }); - } - else - return resolve(); -} - diff --git a/addon-sdk/source/lib/sdk/places/host/host-query.js b/addon-sdk/source/lib/sdk/places/host/host-query.js deleted file mode 100644 index f2dbd6550..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-query.js +++ /dev/null @@ -1,179 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci } = require('chrome'); -const { all } = require('../../core/promise'); -const { safeMerge, omit } = require('../../util/object'); -const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] - .getService(Ci.nsINavHistoryService); -const bookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] - .getService(Ci.nsINavBookmarksService); -const { request, response } = require('../../addon/host'); -const { newURI } = require('../../url/utils'); -const { send } = require('../../addon/events'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); - -const ROOT_FOLDERS = [ - bookmarksService.unfiledBookmarksFolder, bookmarksService.toolbarFolder, - bookmarksService.bookmarksMenuFolder -]; - -const EVENT_MAP = { - 'sdk-places-query': queryReceiver -}; - -// Properties that need to be manually -// copied into a nsINavHistoryQuery object -const MANUAL_QUERY_PROPERTIES = [ - 'uri', 'folder', 'tags', 'url', 'folder' -]; - -const PLACES_PROPERTIES = [ - 'uri', 'title', 'accessCount', 'time' -]; - -function execute (queries, options) { - return new Promise(resolve => { - let root = historyService - .executeQueries(queries, queries.length, options).root; - // Let's extract an eventual uri wildcard, if both domain and uri are set. - // See utils.js::urlQueryParser() for more details. - // In case of multiple queries, we only retain the first found wildcard. - let uriWildcard = queries.reduce((prev, query) => { - if (query.uri && query.domain) { - if (!prev) - prev = query.uri.spec; - query.uri = null; - } - return prev; - }, ""); - resolve(collect([], root, uriWildcard)); - }); -} - -function collect (acc, node, uriWildcard) { - node.containerOpen = true; - for (let i = 0; i < node.childCount; i++) { - let child = node.getChild(i); - - if (!uriWildcard || child.uri.startsWith(uriWildcard)) { - acc.push(child); - } - if (child.type === child.RESULT_TYPE_FOLDER) { - let container = child.QueryInterface(Ci.nsINavHistoryContainerResultNode); - collect(acc, container, uriWildcard); - } - } - node.containerOpen = false; - return acc; -} - -function query (queries, options) { - return new Promise((resolve, reject) => { - queries = queries || []; - options = options || {}; - let optionsObj, queryObjs; - - optionsObj = historyService.getNewQueryOptions(); - queryObjs = [].concat(queries).map(createQuery); - if (!queryObjs.length) { - queryObjs = [historyService.getNewQuery()]; - } - safeMerge(optionsObj, options); - - /* - * Currently `places:` queries are not supported - */ - optionsObj.excludeQueries = true; - - execute(queryObjs, optionsObj).then((results) => { - if (optionsObj.queryType === 0) { - return results.map(normalize); - } - else if (optionsObj.queryType === 1) { - // Formats query results into more standard - // data structures for returning - return all(results.map(({itemId}) => - send('sdk-places-bookmarks-get', { id: itemId }))); - } - }).then(resolve, reject); - }); -} -exports.query = query; - -function createQuery (query) { - query = query || {}; - let queryObj = historyService.getNewQuery(); - - safeMerge(queryObj, omit(query, MANUAL_QUERY_PROPERTIES)); - - if (query.tags && Array.isArray(query.tags)) - queryObj.tags = query.tags; - if (query.uri || query.url) - queryObj.uri = newURI(query.uri || query.url); - if (query.folder) - queryObj.setFolders([query.folder], 1); - return queryObj; -} - -function queryReceiver (message) { - let queries = message.data.queries || message.data.query; - let options = message.data.options; - let resData = { - id: message.id, - event: message.event - }; - - query(queries, options).then(results => { - resData.data = results; - respond(resData); - }, reason => { - resData.error = reason; - respond(resData); - }); -} - -/* - * Converts a nsINavHistoryResultNode into a plain object - * - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode - */ -function normalize (historyObj) { - return PLACES_PROPERTIES.reduce((obj, prop) => { - if (prop === 'uri') - obj.url = historyObj.uri; - else if (prop === 'time') { - // Cast from microseconds to milliseconds - obj.time = Math.floor(historyObj.time / 1000) - } - else if (prop === 'accessCount') - obj.visitCount = historyObj[prop]; - else - obj[prop] = historyObj[prop]; - return obj; - }, {}); -} - -/* - * Hook into host - */ - -var reqStream = filter(request, data => /sdk-places-query/.test(data.event)); -on(reqStream, 'data', function (e) { - if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e); -}); - -function respond (data) { - emit(response, 'data', data); -} diff --git a/addon-sdk/source/lib/sdk/places/host/host-tags.js b/addon-sdk/source/lib/sdk/places/host/host-tags.js deleted file mode 100644 index 929a5d5af..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-tags.js +++ /dev/null @@ -1,92 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.metadata = { - "stability": "experimental", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci } = require('chrome'); -const taggingService = Cc["@mozilla.org/browser/tagging-service;1"]. - getService(Ci.nsITaggingService); -const ios = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); -const { URL } = require('../../url'); -const { newURI } = require('../../url/utils'); -const { request, response } = require('../../addon/host'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); - -const EVENT_MAP = { - 'sdk-places-tags-tag': tag, - 'sdk-places-tags-untag': untag, - 'sdk-places-tags-get-tags-by-url': getTagsByURL, - 'sdk-places-tags-get-urls-by-tag': getURLsByTag -}; - -function tag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.tagURI(newURI(data.url), data.tags); - respond(resData); -} - -function untag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.untagURI(newURI(data.url), data.tags); - respond(resData); -} - -function getURLsByTag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService - .getURIsForTag(data.tag).map(uri => uri.spec); - respond(resData); -} - -function getTagsByURL (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.getTagsForURI(newURI(data.url), {}); - respond(resData); -} - -/* - * Hook into host - */ - -var reqStream = filter(request, function (data) { - return /sdk-places-tags/.test(data.event); -}); - -on(reqStream, 'data', function (e) { - if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e); -}); - -function respond (data) { - emit(response, 'data', data); -} diff --git a/addon-sdk/source/lib/sdk/places/utils.js b/addon-sdk/source/lib/sdk/places/utils.js deleted file mode 100644 index 44366d2aa..000000000 --- a/addon-sdk/source/lib/sdk/places/utils.js +++ /dev/null @@ -1,268 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci, Cu } = require('chrome'); -const { Class } = require('../core/heritage'); -const { method } = require('../lang/functional'); -const { defer, promised, all } = require('../core/promise'); -const { send } = require('../addon/events'); -const { EventTarget } = require('../event/target'); -const { merge } = require('../util/object'); -const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); - -Cu.importGlobalProperties(["URL"]); - -/* - * TreeNodes are used to construct dependency trees - * for BookmarkItems - */ -var TreeNode = Class({ - initialize: function (value) { - this.value = value; - this.children = []; - }, - add: function (values) { - [].concat(values).forEach(value => { - this.children.push(value instanceof TreeNode ? value : TreeNode(value)); - }); - }, - get length () { - let count = 0; - this.walk(() => count++); - // Do not count the current node - return --count; - }, - get: method(get), - walk: method(walk), - toString: () => '[object TreeNode]' -}); -exports.TreeNode = TreeNode; - -/* - * Descends down from `node` applying `fn` to each in order. - * `fn` can return values or promises -- if promise returned, - * children are not processed until resolved. `fn` is passed - * one argument, the current node, `curr`. - */ -function walk (curr, fn) { - return promised(fn)(curr).then(val => { - return all(curr.children.map(child => walk(child, fn))); - }); -} - -/* - * Descends from the TreeNode `node`, returning - * the node with value `value` if found or `null` - * otherwise - */ -function get (node, value) { - if (node.value === value) return node; - for (let child of node.children) { - let found = get(child, value); - if (found) return found; - } - return null; -} - -/* - * Constructs a tree of bookmark nodes - * returning the root (value: null); - */ - -function constructTree (items) { - let root = TreeNode(null); - items.forEach(treeify.bind(null, root)); - - function treeify (root, item) { - // If node already exists, skip - let node = root.get(item); - if (node) return node; - node = TreeNode(item); - - let parentNode = item.group ? treeify(root, item.group) : root; - parentNode.add(node); - - return node; - } - - return root; -} -exports.constructTree = constructTree; - -/* - * Shortcut for converting an id, or an object with an id, into - * an object with corresponding bookmark data - */ -function fetchItem (item) { - return send('sdk-places-bookmarks-get', { id: item.id || item }); -} -exports.fetchItem = fetchItem; - -/* - * Takes an ID or an object with ID and checks it against - * the root bookmark folders - */ -function isRootGroup (id) { - id = id && id.id; - return ~[bmsrv.bookmarksMenuFolder, bmsrv.toolbarFolder, - bmsrv.unfiledBookmarksFolder - ].indexOf(id); -} -exports.isRootGroup = isRootGroup; - -/* - * Merges appropriate options into query based off of url - * 4 scenarios: - * - * 'moz.com' // domain: moz.com, domainIsHost: true - * --> 'http://moz.com', 'http://moz.com/thunderbird' - * '*.moz.com' // domain: moz.com, domainIsHost: false - * --> 'http://moz.com', 'http://moz.com/index', 'http://ff.moz.com/test' - * 'http://moz.com' // uri: http://moz.com/ - * --> 'http://moz.com/' - * 'http://moz.com/*' // uri: http://moz.com/, domain: moz.com, domainIsHost: true - * --> 'http://moz.com/', 'http://moz.com/thunderbird' - */ - -function urlQueryParser (query, url) { - if (!url) return; - if (/^https?:\/\//.test(url)) { - query.uri = url.charAt(url.length - 1) === '/' ? url : url + '/'; - if (/\*$/.test(url)) { - // Wildcard searches on URIs are not supported, so try to extract a - // domain and filter the data later. - url = url.replace(/\*$/, ''); - try { - query.domain = new URL(url).hostname; - query.domainIsHost = true; - // Unfortunately here we cannot use an expando to store the wildcard, - // cause the query is a wrapped native XPCOM object, so we reuse uri. - // We clearly don't want to query for both uri and domain, thus we'll - // have to handle this in host-query.js::execute() - query.uri = url; - } catch (ex) { - // Cannot extract an host cause it's not a valid uri, the query will - // just return nothing. - } - } - } else { - if (/^\*/.test(url)) { - query.domain = url.replace(/^\*\./, ''); - query.domainIsHost = false; - } else { - query.domain = url; - query.domainIsHost = true; - } - } -} -exports.urlQueryParser = urlQueryParser; - -/* - * Takes an EventEmitter and returns a promise that - * aggregates results and handles a bulk resolve and reject - */ - -function promisedEmitter (emitter) { - let { promise, resolve, reject } = defer(); - let errors = []; - emitter.on('error', error => errors.push(error)); - emitter.on('end', (items) => { - if (errors.length) reject(errors[0]); - else resolve(items); - }); - return promise; -} -exports.promisedEmitter = promisedEmitter; - - -// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions -function createQuery (type, query) { - query = query || {}; - let qObj = { - searchTerms: query.query - }; - - urlQueryParser(qObj, query.url); - - // 0 === history - if (type === 0) { - // PRTime used by query is in microseconds, not milliseconds - qObj.beginTime = (query.from || 0) * 1000; - qObj.endTime = (query.to || new Date()) * 1000; - - // Set reference time to Epoch - qObj.beginTimeReference = 0; - qObj.endTimeReference = 0; - } - // 1 === bookmarks - else if (type === 1) { - qObj.tags = query.tags; - qObj.folder = query.group && query.group.id; - } - // 2 === unified (not implemented on platform) - else if (type === 2) { - - } - - return qObj; -} -exports.createQuery = createQuery; - -// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions - -const SORT_MAP = { - title: 1, - date: 3, // sort by visit date - url: 5, - visitCount: 7, - // keywords currently unsupported - // keyword: 9, - dateAdded: 11, // bookmarks only - lastModified: 13 // bookmarks only -}; - -function createQueryOptions (type, options) { - options = options || {}; - let oObj = {}; - oObj.sortingMode = SORT_MAP[options.sort] || 0; - if (options.descending && options.sort) - oObj.sortingMode++; - - // Resolve to default sort if ineligible based on query type - if (type === 0 && // history - (options.sort === 'dateAdded' || options.sort === 'lastModified')) - oObj.sortingMode = 0; - - oObj.maxResults = typeof options.count === 'number' ? options.count : 0; - - oObj.queryType = type; - - return oObj; -} -exports.createQueryOptions = createQueryOptions; - - -function mapBookmarkItemType (type) { - if (typeof type === 'number') { - if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; - if (bmsrv.TYPE_FOLDER === type) return 'group'; - if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; - } else { - if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; - if ('group' === type) return bmsrv.TYPE_FOLDER; - if ('separator' === type) return bmsrv.TYPE_SEPARATOR; - } -} -exports.mapBookmarkItemType = mapBookmarkItemType; diff --git a/addon-sdk/source/lib/sdk/platform/xpcom.js b/addon-sdk/source/lib/sdk/platform/xpcom.js deleted file mode 100644 index 383baf67a..000000000 --- a/addon-sdk/source/lib/sdk/platform/xpcom.js +++ /dev/null @@ -1,241 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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, Cr, Cm, components: { classesByID } } = require('chrome'); -const { registerFactory, unregisterFactory, isCIDRegistered } = - Cm.QueryInterface(Ci.nsIComponentRegistrar); - -const { merge } = require('../util/object'); -const { Class, extend, mix } = require('../core/heritage'); -const { uuid } = require('../util/uuid'); - -// This is a base prototype, that provides bare bones of XPCOM. JS based -// components can be easily implement by extending it. -const Unknown = new function() { - function hasInterface(component, iid) { - return component && component.interfaces && - ( component.interfaces.some(id => iid.equals(Ci[id])) || - component.implements.some($ => hasInterface($, iid)) || - hasInterface(Object.getPrototypeOf(component), iid)); - } - - return Class({ - /** - * The `QueryInterface` method provides runtime type discovery used by XPCOM. - * This method return queried instance of `this` if given `iid` is listed in - * the `interfaces` property or in equivalent properties of objects in it's - * prototype chain. In addition it will look up in the prototypes under - * `implements` array property, this ways compositions made via `Class` - * utility will carry interfaces implemented by composition components. - */ - QueryInterface: function QueryInterface(iid) { - // For some reason there are cases when `iid` is `null`. In such cases we - // just return `this`. Otherwise we verify that component implements given - // `iid` interface. This will be no longer necessary once Bug 748003 is - // fixed. - if (iid && !hasInterface(this, iid)) - throw Cr.NS_ERROR_NO_INTERFACE; - - return this; - }, - /** - * Array of `XPCOM` interfaces (as strings) implemented by this component. - * All components implement `nsISupports` by default which is default value - * here. Provide array of interfaces implemented by an object when - * extending, to append them to this list (Please note that there is no - * need to repeat interfaces implemented by super as they will be added - * automatically). - */ - interfaces: Object.freeze([ 'nsISupports' ]) - }); -} -exports.Unknown = Unknown; - -// Base exemplar for creating instances implementing `nsIFactory` interface, -// that maybe registered into runtime via `register` function. Instances of -// this factory create instances of enclosed component on `createInstance`. -const Factory = Class({ - extends: Unknown, - interfaces: [ 'nsIFactory' ], - /** - * All the descendants will get auto generated `id` (also known as `classID` - * in XPCOM world) unless one is manually provided. - */ - get id() { throw Error('Factory must implement `id` property') }, - /** - * XPCOM `contractID` may optionally be provided to associate this factory - * with it. `contract` is a unique string that has a following format: - * '@vendor.com/unique/id;1'. - */ - contract: null, - /** - * Class description that is being registered. This value is intended as a - * human-readable description for the given class and does not needs to be - * globally unique. - */ - description: 'Jetpack generated factory', - /** - * This method is required by `nsIFactory` interfaces, but as in most - * implementations it does nothing interesting. - */ - lockFactory: function lockFactory(lock) { - return undefined; - }, - /** - * If property is `true` XPCOM service / factory will be registered - * automatically on creation. - */ - register: true, - /** - * If property is `true` XPCOM factory will be unregistered prior to add-on - * unload. - */ - unregister: true, - /** - * Method is called on `Service.new(options)` passing given `options` to - * it. Options is expected to have `component` property holding XPCOM - * component implementation typically decedent of `Unknown` or any custom - * implementation with a `new` method and optional `register`, `unregister` - * flags. Unless `register` is `false` Service / Factory will be - * automatically registered. Unless `unregister` is `false` component will - * be automatically unregistered on add-on unload. - */ - initialize: function initialize(options) { - merge(this, { - id: 'id' in options ? options.id : uuid(), - register: 'register' in options ? options.register : this.register, - unregister: 'unregister' in options ? options.unregister : this.unregister, - contract: 'contract' in options ? options.contract : null, - Component: options.Component - }); - - // If service / factory has auto registration enabled then register. - if (this.register) - register(this); - }, - /** - * Creates an instance of the class associated with this factory. - */ - createInstance: function createInstance(outer, iid) { - try { - if (outer) - throw Cr.NS_ERROR_NO_AGGREGATION; - return this.create().QueryInterface(iid); - } - catch (error) { - throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE; - } - }, - create: function create() { - return this.Component(); - } -}); -exports.Factory = Factory; - -// Exemplar for creating services that implement `nsIFactory` interface, that -// can be registered into runtime via call to `register`. This services return -// enclosed `component` on `getService`. -const Service = Class({ - extends: Factory, - initialize: function initialize(options) { - this.component = options.Component(); - Factory.prototype.initialize.call(this, options); - }, - description: 'Jetpack generated service', - /** - * Creates an instance of the class associated with this factory. - */ - create: function create() { - return this.component; - } -}); -exports.Service = Service; - -function isRegistered({ id }) { - return isCIDRegistered(id); -} -exports.isRegistered = isRegistered; - -/** - * Registers given `component` object to be used to instantiate a particular - * class identified by `component.id`, and creates an association of class - * name and `component.contract` with the class. - */ -function register(factory) { - if (!(factory instanceof Factory)) { - throw new Error("xpcom.register() expect a Factory instance.\n" + - "Please refactor your code to new xpcom module if you" + - " are repacking an addon from SDK <= 1.5:\n" + - "https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/platform_xpcom"); - } - - registerFactory(factory.id, factory.description, factory.contract, factory); - - if (factory.unregister) - require('../system/unload').when(unregister.bind(null, factory)); -} -exports.register = register; - -/** - * Unregister a factory associated with a particular class identified by - * `factory.classID`. - */ -function unregister(factory) { - if (isRegistered(factory)) - unregisterFactory(factory.id, factory); -} -exports.unregister = unregister; - -function autoRegister(path) { - // TODO: This assumes that the url points to a directory - // that contains subdirectories corresponding to OS/ABI and then - // further subdirectories corresponding to Gecko platform version. - // we should probably either behave intelligently here or allow - // the caller to pass-in more options if e.g. there aren't - // Gecko-specific binaries for a component (which will be the case - // if only frozen interfaces are used). - - var runtime = require("../system/runtime"); - var osDirName = runtime.OS + "_" + runtime.XPCOMABI; - var platformVersion = require("../system/xul-app").platformVersion.substring(0, 5); - - var file = Cc['@mozilla.org/file/local;1'] - .createInstance(Ci.nsILocalFile); - file.initWithPath(path); - file.append(osDirName); - file.append(platformVersion); - - if (!(file.exists() && file.isDirectory())) - throw new Error("component not available for OS/ABI " + - osDirName + " and platform " + platformVersion); - - Cm.QueryInterface(Ci.nsIComponentRegistrar); - Cm.autoRegister(file); -} -exports.autoRegister = autoRegister; - -/** - * Returns registered factory that has a given `id` or `null` if not found. - */ -function factoryByID(id) { - return classesByID[id] || null; -} -exports.factoryByID = factoryByID; - -/** - * Returns factory registered with a given `contract` or `null` if not found. - * In contrast to `Cc[contract]` that does ignores new factory registration - * with a given `contract` this will return a factory currently associated - * with a `contract`. - */ -function factoryByContract(contract) { - return factoryByID(Cm.contractIDToCID(contract)); -} -exports.factoryByContract = factoryByContract; diff --git a/addon-sdk/source/lib/sdk/preferences/event-target.js b/addon-sdk/source/lib/sdk/preferences/event-target.js deleted file mode 100644 index b64ba303c..000000000 --- a/addon-sdk/source/lib/sdk/preferences/event-target.js +++ /dev/null @@ -1,61 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci } = require('chrome'); -const { Class } = require('../core/heritage'); -const { EventTarget } = require('../event/target'); -const { Branch } = require('./service'); -const { emit, off } = require('../event/core'); -const { when: unload } = require('../system/unload'); - -const prefTargetNS = require('../core/namespace').ns(); - -const PrefsTarget = Class({ - extends: EventTarget, - initialize: function(options) { - options = options || {}; - EventTarget.prototype.initialize.call(this, options); - - let branchName = options.branchName || ''; - let branch = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService). - getBranch(branchName). - QueryInterface(Ci.nsIPrefBranch2); - prefTargetNS(this).branch = branch; - - // provides easy access to preference values - this.prefs = Branch(branchName); - - // start listening to preference changes - let observer = prefTargetNS(this).observer = onChange.bind(this); - branch.addObserver('', observer, false); - - // Make sure to destroy this on unload - unload(destroy.bind(this)); - } -}); -exports.PrefsTarget = PrefsTarget; - -/* HELPERS */ - -function onChange(subject, topic, name) { - if (topic === 'nsPref:changed') { - emit(this, name, name); - emit(this, '', name); - } -} - -function destroy() { - off(this); - - // stop listening to preference changes - let branch = prefTargetNS(this).branch; - branch.removeObserver('', prefTargetNS(this).observer, false); - prefTargetNS(this).observer = null; -} diff --git a/addon-sdk/source/lib/sdk/preferences/native-options.js b/addon-sdk/source/lib/sdk/preferences/native-options.js deleted file mode 100644 index 840997df9..000000000 --- a/addon-sdk/source/lib/sdk/preferences/native-options.js +++ /dev/null @@ -1,193 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { on } = require('../system/events'); -const { id, preferencesBranch } = require('../self'); -const { localizeInlineOptions } = require('../l10n/prefs'); -const { Services } = require("resource://gre/modules/Services.jsm"); -const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm"); -const { defer } = require("sdk/core/promise"); - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";; -const DEFAULT_OPTIONS_URL = 'data:text/xml,'; - -const VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color', - 'file', 'directory', 'control', 'menulist', 'radio']; - -const isFennec = require("sdk/system/xul-app").is("Fennec"); - -function enable({ preferences, id }) { - let enabled = defer(); - - validate(preferences); - - setDefaults(preferences, preferencesBranch); - - // allow the use of custom options.xul - AddonManager.getAddonByID(id, (addon) => { - on('addon-options-displayed', onAddonOptionsDisplayed, true); - enabled.resolve({ id: id }); - }); - - function onAddonOptionsDisplayed({ subject: doc, data }) { - if (data === id) { - let parent; - - if (isFennec) { - parent = doc.querySelector('.options-box'); - - // NOTE: This disable the CSS rule that makes the options invisible - let item = doc.querySelector('#addons-details .addon-item'); - item.removeAttribute("optionsURL"); - } else { - parent = doc.getElementById('detail-downloads').parentNode; - } - - if (parent) { - injectOptions({ - preferences: preferences, - preferencesBranch: preferencesBranch, - document: doc, - parent: parent, - id: id - }); - localizeInlineOptions(doc); - } else { - throw Error("Preferences parent node not found in Addon Details. The configured custom preferences will not be visible."); - } - } - } - - return enabled.promise; -} -exports.enable = enable; - -// centralized sanity checks -function validate(preferences) { - for (let { name, title, type, label, options } of preferences) { - // make sure the title is set and non-empty - if (!title) - throw Error("The '" + name + "' pref requires a title"); - - // make sure that pref type is a valid inline option type - if (!~VALID_PREF_TYPES.indexOf(type)) - throw Error("The '" + name + "' pref must be of valid type"); - - // if it's a control, make sure it has a label - if (type === 'control' && !label) - throw Error("The '" + name + "' control requires a label"); - - // if it's a menulist or radio, make sure it has options - if (type === 'menulist' || type === 'radio') { - if (!options) - throw Error("The '" + name + "' pref requires options"); - - // make sure each option has a value and a label - for (let item of options) { - if (!('value' in item) || !('label' in item)) - throw Error("Each option requires both a value and a label"); - } - } - - // TODO: check that pref type matches default value type - } -} -exports.validate = validate; - -// initializes default preferences, emulates defaults/prefs.js -function setDefaults(preferences, preferencesBranch) { - const branch = Cc['@mozilla.org/preferences-service;1']. - getService(Ci.nsIPrefService). - getDefaultBranch('extensions.' + preferencesBranch + '.'); - for (let { name, value } of preferences) { - switch (typeof value) { - case 'boolean': - branch.setBoolPref(name, value); - break; - case 'number': - // must be integer, ignore otherwise - if (value % 1 === 0) { - branch.setIntPref(name, value); - } - break; - case 'string': - let str = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - str.data = value; - branch.setComplexValue(name, Ci.nsISupportsString, str); - break; - } - } -} -exports.setDefaults = setDefaults; - -// dynamically injects inline options into about:addons page at runtime -// NOTE: on Firefox Desktop the about:addons page is a xul page document, -// on Firefox for Android the about:addons page is an xhtml page, to support both -// the XUL xml namespace have to be enforced. -function injectOptions({ preferences, preferencesBranch, document, parent, id }) { - preferences.forEach(({name, type, hidden, title, description, label, options, on, off}) => { - if (hidden) { - return; - } - - let setting = document.createElementNS(XUL_NS, 'setting'); - setting.setAttribute('pref-name', name); - setting.setAttribute('data-jetpack-id', id); - setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name); - setting.setAttribute('type', type); - setting.setAttribute('title', title); - if (description) - setting.setAttribute('desc', description); - - if (type === 'file' || type === 'directory') { - setting.setAttribute('fullpath', 'true'); - } - else if (type === 'control') { - let button = document.createElementNS(XUL_NS, 'button'); - button.setAttribute('pref-name', name); - button.setAttribute('data-jetpack-id', id); - button.setAttribute('label', label); - button.addEventListener('command', function() { - Services.obs.notifyObservers(null, `${id}-cmdPressed`, name); - }, true); - setting.appendChild(button); - } - else if (type === 'boolint') { - setting.setAttribute('on', on); - setting.setAttribute('off', off); - } - else if (type === 'menulist') { - let menulist = document.createElementNS(XUL_NS, 'menulist'); - let menupopup = document.createElementNS(XUL_NS, 'menupopup'); - for (let { value, label } of options) { - let menuitem = document.createElementNS(XUL_NS, 'menuitem'); - menuitem.setAttribute('value', value); - menuitem.setAttribute('label', label); - menupopup.appendChild(menuitem); - } - menulist.appendChild(menupopup); - setting.appendChild(menulist); - } - else if (type === 'radio') { - let radiogroup = document.createElementNS(XUL_NS, 'radiogroup'); - for (let { value, label } of options) { - let radio = document.createElementNS(XUL_NS, 'radio'); - radio.setAttribute('value', value); - radio.setAttribute('label', label); - radiogroup.appendChild(radio); - } - setting.appendChild(radiogroup); - } - - parent.appendChild(setting); - }); -} -exports.injectOptions = injectOptions; diff --git a/addon-sdk/source/lib/sdk/preferences/service.js b/addon-sdk/source/lib/sdk/preferences/service.js deleted file mode 100644 index 231cd8e14..000000000 --- a/addon-sdk/source/lib/sdk/preferences/service.js +++ /dev/null @@ -1,137 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -// The minimum and maximum integers that can be set as preferences. -// The range of valid values is narrower than the range of valid JS values -// because the native preferences code treats integers as NSPR PRInt32s, -// which are 32-bit signed integers on all platforms. -const MAX_INT = 0x7FFFFFFF; -const MIN_INT = -0x80000000; - -const {Cc,Ci,Cr} = require("chrome"); - -const prefService = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService); -const prefSvc = prefService.getBranch(null); -const defaultBranch = prefService.getDefaultBranch(null); - -const { Preferences } = require("resource://gre/modules/Preferences.jsm"); -const prefs = new Preferences({}); - -const branchKeys = branchName => - keys(branchName).map($ => $.replace(branchName, "")); - -const Branch = function(branchName) { - return new Proxy(Branch.prototype, { - getOwnPropertyDescriptor(target, name, receiver) { - return { - configurable: true, - enumerable: true, - writable: false, - value: this.get(target, name, receiver) - }; - }, - ownKeys(target) { - return branchKeys(branchName); - }, - get(target, name, receiver) { - return get(`${branchName}${name}`); - }, - set(target, name, value, receiver) { - set(`${branchName}${name}`, value); - return true; - }, - has(target, name) { - return this.hasOwn(target, name); - }, - hasOwn(target, name) { - return has(`${branchName}${name}`); - }, - deleteProperty(target, name) { - reset(`${branchName}${name}`); - return true; - } - }); -} - - -function get(name, defaultValue) { - return prefs.get(name, defaultValue); -} -exports.get = get; - - -function set(name, value) { - var prefType; - if (typeof value != "undefined" && value != null) - prefType = value.constructor.name; - - switch (prefType) { - case "Number": - if (value % 1 != 0) - throw new Error("cannot store non-integer number: " + value); - } - - prefs.set(name, value); -} -exports.set = set; - -const has = prefs.has.bind(prefs) -exports.has = has; - -function keys(root) { - return prefSvc.getChildList(root); -} -exports.keys = keys; - -const isSet = prefs.isSet.bind(prefs); -exports.isSet = isSet; - -function reset(name) { - try { - prefSvc.clearUserPref(name); - } - catch (e) { - // The pref service throws NS_ERROR_UNEXPECTED when the caller tries - // to reset a pref that doesn't exist or is already set to its default - // value. This interface fails silently in those cases, so callers - // can unconditionally reset a pref without having to check if it needs - // resetting first or trap exceptions after the fact. It passes through - // other exceptions, however, so callers know about them, since we don't - // know what other exceptions might be thrown and what they might mean. - if (e.result != Cr.NS_ERROR_UNEXPECTED) { - throw e; - } - } -} -exports.reset = reset; - -function getLocalized(name, defaultValue) { - let value = null; - try { - value = prefSvc.getComplexValue(name, Ci.nsIPrefLocalizedString).data; - } - finally { - return value || defaultValue; - } -} -exports.getLocalized = getLocalized; - -function setLocalized(name, value) { - // We can't use `prefs.set` here as we have to use `getDefaultBranch` - // (instead of `getBranch`) in order to have `mIsDefault` set to true, here: - // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#233 - // Otherwise, we do not enter into this expected condition: - // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#244 - defaultBranch.setCharPref(name, value); -} -exports.setLocalized = setLocalized; - -exports.Branch = Branch; - diff --git a/addon-sdk/source/lib/sdk/preferences/utils.js b/addon-sdk/source/lib/sdk/preferences/utils.js deleted file mode 100644 index 1d5769c37..000000000 --- a/addon-sdk/source/lib/sdk/preferences/utils.js +++ /dev/null @@ -1,42 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils"); -const { on, off } = require("sdk/system/events"); -const { getMostRecentBrowserWindow } = require('../window/utils'); - -// Opens about:addons in a new tab, then displays the inline -// preferences of the provided add-on -const open = ({ id }) => new Promise((resolve, reject) => { - // opening the about:addons page in a new tab - let tab = openTab(getMostRecentBrowserWindow(), "about:addons"); - let browser = getBrowserForTab(tab); - - // waiting for the about:addons page to load - browser.addEventListener("load", function onPageLoad() { - browser.removeEventListener("load", onPageLoad, true); - let window = browser.contentWindow; - - // wait for the add-on's "addon-options-displayed" - on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) { - if (data === id) { - off("addon-options-displayed", onPrefDisplayed); - resolve({ - id: id, - tabId: getTabId(tab), - "document": doc - }); - } - }, true); - - // display the add-on inline preferences page - window.gViewController.commands.cmd_showItemDetails.doCommand({ id: id }, true); - }, true); -}); -exports.open = open; diff --git a/addon-sdk/source/lib/sdk/private-browsing.js b/addon-sdk/source/lib/sdk/private-browsing.js deleted file mode 100644 index 29ca16185..000000000 --- a/addon-sdk/source/lib/sdk/private-browsing.js +++ /dev/null @@ -1,12 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -module.metadata = { - "stability": "stable" -}; - -const { isPrivate } = require('./private-browsing/utils'); - -exports.isPrivate = isPrivate; diff --git a/addon-sdk/source/lib/sdk/private-browsing/utils.js b/addon-sdk/source/lib/sdk/private-browsing/utils.js deleted file mode 100644 index 8b012f0ce..000000000 --- a/addon-sdk/source/lib/sdk/private-browsing/utils.js +++ /dev/null @@ -1,54 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci, Cu } = require('chrome'); -const { is } = require('../system/xul-app'); -const { isWindowPrivate } = require('../window/utils'); -const { isPrivateBrowsingSupported } = require('../self'); -const { dispatcher } = require("../util/dispatcher"); - -var PrivateBrowsingUtils; - -// Private browsing is only supported in Fx -try { - PrivateBrowsingUtils = Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm', {}).PrivateBrowsingUtils; -} -catch (e) {} - -exports.isGlobalPBSupported = false; - -// checks that per-window private browsing is implemented -var isWindowPBSupported = exports.isWindowPBSupported = - !!PrivateBrowsingUtils && is('Firefox'); - -// checks that per-tab private browsing is implemented -var isTabPBSupported = exports.isTabPBSupported = - !!PrivateBrowsingUtils && is('Fennec'); - -function isPermanentPrivateBrowsing() { - return !!(PrivateBrowsingUtils && PrivateBrowsingUtils.permanentPrivateBrowsing); -} -exports.isPermanentPrivateBrowsing = isPermanentPrivateBrowsing; - -function ignoreWindow(window) { - return !isPrivateBrowsingSupported && isWindowPrivate(window); -} -exports.ignoreWindow = ignoreWindow; - -var getMode = function getMode(chromeWin) { - return (chromeWin !== undefined && isWindowPrivate(chromeWin)); -}; -exports.getMode = getMode; - -const isPrivate = dispatcher("isPrivate"); -isPrivate.when(isPermanentPrivateBrowsing, _ => true); -isPrivate.when(x => x instanceof Ci.nsIDOMWindow, isWindowPrivate); -isPrivate.when(x => Ci.nsIPrivateBrowsingChannel && x instanceof Ci.nsIPrivateBrowsingChannel, x => x.isChannelPrivate); -isPrivate.define(() => false); -exports.isPrivate = isPrivate; diff --git a/addon-sdk/source/lib/sdk/querystring.js b/addon-sdk/source/lib/sdk/querystring.js deleted file mode 100644 index 9982a00ab..000000000 --- a/addon-sdk/source/lib/sdk/querystring.js +++ /dev/null @@ -1,121 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 unescape = decodeURIComponent; -exports.unescape = unescape; - -// encodes a string safely for application/x-www-form-urlencoded -// adheres to RFC 3986 -// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURIComponent -function escape(query) { - return encodeURIComponent(query). - replace(/%20/g, '+'). - replace(/!/g, '%21'). - replace(/'/g, '%27'). - replace(/\(/g, '%28'). - replace(/\)/g, '%29'). - replace(/\*/g, '%2A'); -} -exports.escape = escape; - -// Converts an object of unordered key-vals to a string that can be passed -// as part of a request -function stringify(options, separator, assigner) { - separator = separator || '&'; - assigner = assigner || '='; - // Explicitly return null if we have null, and empty string, or empty object. - if (!options) - return ''; - - // If content is already a string, just return it as is. - if (typeof(options) == 'string') - return options; - - // At this point we have a k:v object. Iterate over it and encode each value. - // Arrays and nested objects will get encoded as needed. For example... - // - // { foo: [1, 2, { omg: 'bbq', 'all your base!': 'are belong to us' }], bar: 'baz' } - // - // will be encoded as - // - // foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz - // - // Keys (including '[' and ']') and values will be encoded with - // `escape` before returning. - // - // Execution was inspired by jQuery, but some details have changed and numeric - // array keys are included (whereas they are not in jQuery). - - let encodedContent = []; - function add(key, val) { - encodedContent.push(escape(key) + assigner + escape(val)); - } - - function make(key, value) { - if (value && typeof(value) === 'object') - Object.keys(value).forEach(function(name) { - make(key + '[' + name + ']', value[name]); - }); - else - add(key, value); - } - - Object.keys(options).forEach(function(name) { make(name, options[name]); }); - return encodedContent.join(separator); - - //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had - // trouble getting that working. It would also be nice to stay - // backwards-compat as long as possible. Keeping this in for now... - // let formData = Cc['@mozilla.org/files/formdata;1']. - // createInstance(Ci.nsIDOMFormData); - // for ([k, v] in Iterator(content)) { - // formData.append(k, v); - // } - // return formData; -} -exports.stringify = stringify; - -// Exporting aliases that nodejs implements just for the sake of -// interoperability. -exports.encode = stringify; -exports.serialize = stringify; - -// Note: That `stringify` and `parse` aren't bijective as we use `stringify` -// as it was implement in request module, but implement `parse` to match nodejs -// behavior. -// TODO: Make `stringify` implement API as in nodejs and figure out backwards -// compatibility. -function parse(query, separator, assigner) { - separator = separator || '&'; - assigner = assigner || '='; - let result = {}; - - if (typeof query !== 'string' || query.length === 0) - return result; - - query.split(separator).forEach(function(chunk) { - let pair = chunk.split(assigner); - let key = unescape(pair[0]); - let value = unescape(pair.slice(1).join(assigner)); - - if (!(key in result)) - result[key] = value; - else if (Array.isArray(result[key])) - result[key].push(value); - else - result[key] = [result[key], value]; - }); - - return result; -}; -exports.parse = parse; -// Exporting aliases that nodejs implements just for the sake of -// interoperability. -exports.decode = parse; diff --git a/addon-sdk/source/lib/sdk/remote/child.js b/addon-sdk/source/lib/sdk/remote/child.js deleted file mode 100644 index 4ccfa661a..000000000 --- a/addon-sdk/source/lib/sdk/remote/child.js +++ /dev/null @@ -1,284 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { isChildLoader } = require('./core'); -if (!isChildLoader) - throw new Error("Cannot load sdk/remote/child in a main process loader."); - -const { Ci, Cc, Cu } = require('chrome'); -const runtime = require('../system/runtime'); -const { Class } = require('../core/heritage'); -const { Namespace } = require('../core/namespace'); -const { omit } = require('../util/object'); -const { when } = require('../system/unload'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const { Disposable } = require('../core/disposable'); -const { EventParent } = require('./utils'); -const { addListItem, removeListItem } = require('../util/list'); - -const loaderID = require('@loader/options').loaderID; - -const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; - -const mm = Cc['@mozilla.org/childprocessmessagemanager;1']. - getService(Ci.nsISyncMessageSender); - -const ns = Namespace(); - -const process = { - port: new EventTarget(), - get id() { - return runtime.processID; - }, - get isRemote() { - return runtime.processType != MAIN_PROCESS; - } -}; -exports.process = process; - -function definePort(obj, name) { - obj.port.emit = (event, ...args) => { - let manager = ns(obj).messageManager; - if (!manager) - return; - - manager.sendAsyncMessage(name, { loaderID, event, args }); - }; -} - -function messageReceived({ data, objects }) { - // Ignore messages from other loaders - if (data.loaderID != loaderID) - return; - - let keys = Object.keys(objects); - if (keys.length) { - // If any objects are CPOWs then ignore this message. We don't want child - // processes interracting with CPOWs - if (!keys.every(name => !Cu.isCrossProcessWrapper(objects[name]))) - return; - - data.args.push(objects); - } - - emit(this.port, data.event, this, ...data.args); -} - -ns(process).messageManager = mm; -definePort(process, 'sdk/remote/process/message'); -let processMessageReceived = messageReceived.bind(process); -mm.addMessageListener('sdk/remote/process/message', processMessageReceived); - -when(() => { - mm.removeMessageListener('sdk/remote/process/message', processMessageReceived); - frames = null; -}); - -process.port.on('sdk/remote/require', (process, uri) => { - require(uri); -}); - -function listenerEquals(a, b) { - for (let prop of ["type", "callback", "isCapturing"]) { - if (a[prop] != b[prop]) - return false; - } - return true; -} - -function listenerFor(type, callback, isCapturing = false) { - return { - type, - callback, - isCapturing, - registeredCallback: undefined, - get args() { - return [ - this.type, - this.registeredCallback ? this.registeredCallback : this.callback, - this.isCapturing - ]; - } - }; -} - -function removeListenerFromArray(array, listener) { - let index = array.findIndex(l => listenerEquals(l, listener)); - if (index < 0) - return; - array.splice(index, 1); -} - -function getListenerFromArray(array, listener) { - return array.find(l => listenerEquals(l, listener)); -} - -function arrayContainsListener(array, listener) { - return !!getListenerFromArray(array, listener); -} - -function makeFrameEventListener(frame, callback) { - return callback.bind(frame); -} - -var FRAME_ID = 0; -var tabMap = new Map(); - -const Frame = Class({ - implements: [ Disposable ], - extends: EventTarget, - setup: function(contentFrame) { - // This ID should be unique for this loader across all processes - let priv = ns(this); - - priv.id = runtime.processID + ":" + FRAME_ID++; - - priv.contentFrame = contentFrame; - priv.messageManager = contentFrame; - priv.domListeners = []; - - tabMap.set(contentFrame.docShell, this); - - priv.messageReceived = messageReceived.bind(this); - priv.messageManager.addMessageListener('sdk/remote/frame/message', priv.messageReceived); - - this.port = new EventTarget(); - definePort(this, 'sdk/remote/frame/message'); - - priv.messageManager.sendAsyncMessage('sdk/remote/frame/attach', { - loaderID, - frameID: priv.id, - processID: runtime.processID - }); - - frames.attachItem(this); - }, - - dispose: function() { - let priv = ns(this); - - emit(this, 'detach', this); - - for (let listener of priv.domListeners) - priv.contentFrame.removeEventListener(...listener.args); - - priv.messageManager.removeMessageListener('sdk/remote/frame/message', priv.messageReceived); - tabMap.delete(priv.contentFrame.docShell); - priv.contentFrame = null; - }, - - get content() { - return ns(this).contentFrame.content; - }, - - get isTab() { - let docShell = ns(this).contentFrame.docShell; - if (process.isRemote) { - // We don't want to roundtrip to the main process to get this property. - // This hack relies on the host app having defined webBrowserChrome only - // in frames that are part of the tabs. Since only Firefox has remote - // processes right now and does this this works. - let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsITabChild); - return !!tabchild.webBrowserChrome; - } - else { - // This is running in the main process so we can break out to the browser - // And check we can find a tab for the browser element directly. - let browser = docShell.chromeEventHandler; - let tab = require('../tabs/utils').getTabForBrowser(browser); - return !!tab; - } - }, - - addEventListener: function(...args) { - let priv = ns(this); - - let listener = listenerFor(...args); - if (arrayContainsListener(priv.domListeners, listener)) - return; - - listener.registeredCallback = makeFrameEventListener(this, listener.callback); - - priv.domListeners.push(listener); - priv.contentFrame.addEventListener(...listener.args); - }, - - removeEventListener: function(...args) { - let priv = ns(this); - - let listener = getListenerFromArray(priv.domListeners, listenerFor(...args)); - if (!listener) - return; - - removeListenerFromArray(priv.domListeners, listener); - priv.contentFrame.removeEventListener(...listener.args); - } -}); - -const FrameList = Class({ - implements: [ EventParent, Disposable ], - extends: EventTarget, - setup: function() { - EventParent.prototype.initialize.call(this); - - this.port = new EventTarget(); - ns(this).domListeners = []; - - this.on('attach', frame => { - for (let listener of ns(this).domListeners) - frame.addEventListener(...listener.args); - }); - }, - - dispose: function() { - // The only case where we get destroyed is when the loader is unloaded in - // which case each frame will clean up its own event listeners. - ns(this).domListeners = null; - }, - - getFrameForWindow: function(window) { - let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); - - return tabMap.get(docShell) || null; - }, - - addEventListener: function(...args) { - let listener = listenerFor(...args); - if (arrayContainsListener(ns(this).domListeners, listener)) - return; - - ns(this).domListeners.push(listener); - for (let frame of this) - frame.addEventListener(...listener.args); - }, - - removeEventListener: function(...args) { - let listener = listenerFor(...args); - if (!arrayContainsListener(ns(this).domListeners, listener)) - return; - - removeListenerFromArray(ns(this).domListeners, listener); - for (let frame of this) - frame.removeEventListener(...listener.args); - } -}); -var frames = exports.frames = new FrameList(); - -function registerContentFrame(contentFrame) { - let frame = new Frame(contentFrame); -} -exports.registerContentFrame = registerContentFrame; - -function unregisterContentFrame(contentFrame) { - let frame = tabMap.get(contentFrame.docShell); - if (!frame) - return; - - frame.destroy(); -} -exports.unregisterContentFrame = unregisterContentFrame; diff --git a/addon-sdk/source/lib/sdk/remote/core.js b/addon-sdk/source/lib/sdk/remote/core.js deleted file mode 100644 index 78bb673fd..000000000 --- a/addon-sdk/source/lib/sdk/remote/core.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const options = require("@loader/options"); - -exports.isChildLoader = options.childLoader; diff --git a/addon-sdk/source/lib/sdk/remote/parent.js b/addon-sdk/source/lib/sdk/remote/parent.js deleted file mode 100644 index f110fe3f6..000000000 --- a/addon-sdk/source/lib/sdk/remote/parent.js +++ /dev/null @@ -1,338 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 { isChildLoader } = require('./core'); -if (isChildLoader) - throw new Error("Cannot load sdk/remote/parent in a child loader."); - -const { Cu, Ci, Cc } = require('chrome'); -const runtime = require('../system/runtime'); - -const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; - -if (runtime.processType != MAIN_PROCESS) { - throw new Error('Cannot use sdk/remote/parent in a child process.'); -} - -const { Class } = require('../core/heritage'); -const { Namespace } = require('../core/namespace'); -const { Disposable } = require('../core/disposable'); -const { omit } = require('../util/object'); -const { when } = require('../system/unload'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const system = require('../system/events'); -const { EventParent } = require('./utils'); -const options = require('@loader/options'); -const loaderModule = require('toolkit/loader'); -const { getTabForBrowser } = require('../tabs/utils'); - -const appInfo = Cc["@mozilla.org/xre/app-info;1"]. - getService(Ci.nsIXULRuntime); - -exports.useRemoteProcesses = appInfo.browserTabsRemoteAutostart; - -// Chose the right function for resolving relative a module id -var moduleResolve; -if (options.isNative) { - moduleResolve = (id, requirer) => loaderModule.nodeResolve(id, requirer, { rootURI: options.rootURI }); -} -else { - moduleResolve = loaderModule.resolve; -} -// Build the sorted path mapping structure that resolveURI requires -var pathMapping = Object.keys(options.paths) - .sort((a, b) => b.length - a.length) - .map(p => [p, options.paths[p]]); - -// Load the scripts in the child processes -var { getNewLoaderID } = require('../../framescript/FrameScriptManager.jsm'); -var PATH = options.paths['']; - -const childOptions = omit(options, ['modules', 'globals', 'resolve', 'load']); -childOptions.modules = {}; -// @l10n/data is just JSON data and can be safely sent across to the child loader -try { - childOptions.modules["@l10n/data"] = require("@l10n/data"); -} -catch (e) { - // There may be no l10n data -} -const loaderID = getNewLoaderID(); -childOptions.loaderID = loaderID; -childOptions.childLoader = true; - -const ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']. - getService(Ci.nsIMessageBroadcaster); -const gmm = Cc['@mozilla.org/globalmessagemanager;1']. - getService(Ci.nsIMessageBroadcaster); - -const ns = Namespace(); - -var processMap = new Map(); - -function definePort(obj, name) { - obj.port.emitCPOW = (event, args, cpows = {}) => { - let manager = ns(obj).messageManager; - if (!manager) - return; - - let method = manager instanceof Ci.nsIMessageBroadcaster ? - "broadcastAsyncMessage" : "sendAsyncMessage"; - - manager[method](name, { loaderID, event, args }, cpows); - }; - - obj.port.emit = (event, ...args) => obj.port.emitCPOW(event, args); -} - -function messageReceived({ target, data }) { - // Ignore messages from other loaders - if (data.loaderID != loaderID) - return; - - emit(this.port, data.event, this, ...data.args); -} - -// Process represents a gecko process that can load webpages. Each process -// contains a number of Frames. This class is used to send and receive messages -// from a single process. -const Process = Class({ - implements: [ Disposable ], - extends: EventTarget, - setup: function(id, messageManager, isRemote) { - ns(this).id = id; - ns(this).isRemote = isRemote; - ns(this).messageManager = messageManager; - ns(this).messageReceived = messageReceived.bind(this); - this.destroy = this.destroy.bind(this); - ns(this).messageManager.addMessageListener('sdk/remote/process/message', ns(this).messageReceived); - ns(this).messageManager.addMessageListener('child-process-shutdown', this.destroy); - - this.port = new EventTarget(); - definePort(this, 'sdk/remote/process/message'); - - // Load any remote modules - for (let module of remoteModules.values()) - this.port.emit('sdk/remote/require', module); - - processMap.set(ns(this).id, this); - processes.attachItem(this); - }, - - dispose: function() { - emit(this, 'detach', this); - processMap.delete(ns(this).id); - ns(this).messageManager.removeMessageListener('sdk/remote/process/message', ns(this).messageReceived); - ns(this).messageManager.removeMessageListener('child-process-shutdown', this.destroy); - ns(this).messageManager = null; - }, - - // Returns true if this process is a child process - get isRemote() { - return ns(this).isRemote; - } -}); - -// Processes gives an API for enumerating an sending and receiving messages from -// all processes as well as detecting when a new process starts. -const Processes = Class({ - implements: [ EventParent ], - extends: EventTarget, - initialize: function() { - EventParent.prototype.initialize.call(this); - ns(this).messageManager = ppmm; - - this.port = new EventTarget(); - definePort(this, 'sdk/remote/process/message'); - }, - - getById: function(id) { - return processMap.get(id); - } -}); -var processes = exports.processes = new Processes(); - -var frameMap = new Map(); - -function setFrameProcess(frame, process) { - ns(frame).process = process; - frames.attachItem(frame); -} - -// Frames display webpages in a process. In the main process every Frame is -// linked with a or - - diff --git a/addon-sdk/source/test/fixtures/test-iframe.js b/addon-sdk/source/test/fixtures/test-iframe.js deleted file mode 100644 index bbfadc4ff..000000000 --- a/addon-sdk/source/test/fixtures/test-iframe.js +++ /dev/null @@ -1,16 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var count = 0; - -setTimeout(function() { - window.addEventListener("message", function(msg) { - if (++count > 1) { - self.postMessage(msg.data); - } - else msg.source.postMessage(msg.data, '*'); - }); - - document.getElementById('inner').src = iframePath; -}, 0); diff --git a/addon-sdk/source/test/fixtures/test-message-manager.js b/addon-sdk/source/test/fixtures/test-message-manager.js deleted file mode 100644 index d647bd8fd..000000000 --- a/addon-sdk/source/test/fixtures/test-message-manager.js +++ /dev/null @@ -1,6 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const TEST_VALUE = 11; - diff --git a/addon-sdk/source/test/fixtures/test-net-url.txt b/addon-sdk/source/test/fixtures/test-net-url.txt deleted file mode 100644 index 9f8166e61..000000000 --- a/addon-sdk/source/test/fixtures/test-net-url.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, ゼロ! \ No newline at end of file diff --git a/addon-sdk/source/test/fixtures/test-page-mod.html b/addon-sdk/source/test/fixtures/test-page-mod.html deleted file mode 100644 index 901abefc4..000000000 --- a/addon-sdk/source/test/fixtures/test-page-mod.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Page Mod test - - -

Lorem ipsum dolor sit amet.

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

Lorem ipsum dolor sit amet.

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

bar

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

' + path + '

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

403 Forbidden

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

404 Not Found

\ -

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

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

416 Requested Range Not Satisfiable

\ -

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

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

500 Internal Server Error

\ -

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

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

501 Not Implemented

\ -

This server is not (yet) Apache.

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

505 HTTP Version Not Supported

\ -

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

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

httpd.js

\ -

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

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

Hi!

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

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

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

- -

- - A targetted link. - -

- -

- -

- -

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

- -

- - - -

-

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

- -

- -

- -

- -

- -

- -

- -

-

This content is editable.

-

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

hello

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

hello

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

hello window-2

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

Multiple Items

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

item destroy

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

paragraph

- -

hi

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

hello

-

-

-

-

-

-

-

-

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

First paragraph

-

Second paragraph

-
-
-
-
Second title
-
-

First paragraph

-

Second paragraph

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

Hello

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

Title

Content

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

Message Manager

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

Window to Message Manager

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

Node to Message Manager

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

page1

- page2 - - diff --git a/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page2.html b/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page2.html deleted file mode 100644 index 8f589f8bb..000000000 --- a/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page2.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - -

page2

- - diff --git a/toolkit/components/extensions/test/mochitest/file_with_about_blank.html b/toolkit/components/extensions/test/mochitest/file_with_about_blank.html deleted file mode 100644 index af51c2e52..000000000 --- a/toolkit/components/extensions/test/mochitest/file_with_about_blank.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/head.js b/toolkit/components/extensions/test/mochitest/head.js deleted file mode 100644 index 1b1a29472..000000000 --- a/toolkit/components/extensions/test/mochitest/head.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; - -/* exported waitForLoad */ - -function waitForLoad(win) { - return new Promise(resolve => { - win.addEventListener("load", function listener() { - win.removeEventListener("load", listener, true); - resolve(); - }, true); - }); -} - diff --git a/toolkit/components/extensions/test/mochitest/head_cookies.js b/toolkit/components/extensions/test/mochitest/head_cookies.js deleted file mode 100644 index 9f6966551..000000000 --- a/toolkit/components/extensions/test/mochitest/head_cookies.js +++ /dev/null @@ -1,167 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -/* exported testCookies */ - -function* testCookies(options) { - // Changing the options object is a bit of a hack, but it allows us to easily - // pass an expiration date to the background script. - options.expiry = Date.now() / 1000 + 3600; - - async function background(backgroundOptions) { - // Ask the parent scope to change some cookies we may or may not have - // permission for. - let awaitChanges = new Promise(resolve => { - browser.test.onMessage.addListener(msg => { - browser.test.assertEq("cookies-changed", msg, "browser.test.onMessage"); - resolve(); - }); - }); - - let changed = []; - browser.cookies.onChanged.addListener(event => { - changed.push(`${event.cookie.name}:${event.cause}`); - }); - browser.test.sendMessage("change-cookies"); - - - // Try to access some cookies in various ways. - let {url, domain, secure} = backgroundOptions; - - let failures = 0; - let tallyFailure = error => { - failures++; - }; - - try { - await awaitChanges; - - let cookie = await browser.cookies.get({url, name: "foo"}); - browser.test.assertEq(backgroundOptions.shouldPass, cookie != null, "should pass == get cookie"); - - let cookies = await browser.cookies.getAll({domain}); - if (backgroundOptions.shouldPass) { - browser.test.assertEq(2, cookies.length, "expected number of cookies"); - } else { - browser.test.assertEq(0, cookies.length, "expected number of cookies"); - } - - await Promise.all([ - browser.cookies.set({url, domain, secure, name: "foo", "value": "baz", expirationDate: backgroundOptions.expiry}).catch(tallyFailure), - browser.cookies.set({url, domain, secure, name: "bar", "value": "quux", expirationDate: backgroundOptions.expiry}).catch(tallyFailure), - browser.cookies.remove({url, name: "deleted"}), - ]); - - if (backgroundOptions.shouldPass) { - // The order of eviction events isn't guaranteed, so just check that - // it's there somewhere. - let evicted = changed.indexOf("evicted:evicted"); - if (evicted < 0) { - browser.test.fail("got no eviction event"); - } else { - browser.test.succeed("got eviction event"); - changed.splice(evicted, 1); - } - - browser.test.assertEq("x:explicit,x:overwrite,x:explicit,x:explicit,foo:overwrite,foo:explicit,bar:explicit,deleted:explicit", - changed.join(","), "expected changes"); - } else { - browser.test.assertEq("", changed.join(","), "expected no changes"); - } - - if (!(backgroundOptions.shouldPass || backgroundOptions.shouldWrite)) { - browser.test.assertEq(2, failures, "Expected failures"); - } else { - browser.test.assertEq(0, failures, "Expected no failures"); - } - - browser.test.notifyPass("cookie-permissions"); - } catch (error) { - browser.test.fail(`Error: ${error} :: ${error.stack}`); - browser.test.notifyFail("cookie-permissions"); - } - } - - let extension = ExtensionTestUtils.loadExtension({ - manifest: { - "permissions": options.permissions, - }, - - background: `(${background})(${JSON.stringify(options)})`, - }); - - - let cookieSvc = SpecialPowers.Services.cookies; - - let domain = options.domain.replace(/^\.?/, "."); - - // This will be evicted after we add a fourth cookie. - cookieSvc.add(domain, "/", "evicted", "bar", options.secure, false, false, options.expiry); - // This will be modified by the background script. - cookieSvc.add(domain, "/", "foo", "bar", options.secure, false, false, options.expiry); - // This will be deleted by the background script. - cookieSvc.add(domain, "/", "deleted", "bar", options.secure, false, false, options.expiry); - - - yield extension.startup(); - - yield extension.awaitMessage("change-cookies"); - cookieSvc.add(domain, "/", "x", "y", options.secure, false, false, options.expiry); - cookieSvc.add(domain, "/", "x", "z", options.secure, false, false, options.expiry); - cookieSvc.remove(domain, "x", "/", false, {}); - extension.sendMessage("cookies-changed"); - - yield extension.awaitFinish("cookie-permissions"); - yield extension.unload(); - - - function getCookies(host) { - let cookies = []; - let enum_ = cookieSvc.getCookiesFromHost(host, {}); - while (enum_.hasMoreElements()) { - cookies.push(enum_.getNext().QueryInterface(SpecialPowers.Ci.nsICookie2)); - } - return cookies.sort((a, b) => String.localeCompare(a.name, b.name)); - } - - let cookies = getCookies(options.domain); - info(`Cookies: ${cookies.map(c => `${c.name}=${c.value}`)}`); - - if (options.shouldPass) { - is(cookies.length, 2, "expected two cookies for host"); - - is(cookies[0].name, "bar", "correct cookie name"); - is(cookies[0].value, "quux", "correct cookie value"); - - is(cookies[1].name, "foo", "correct cookie name"); - is(cookies[1].value, "baz", "correct cookie value"); - } else if (options.shouldWrite) { - // Note: |shouldWrite| applies only when |shouldPass| is false. - // This is necessary because, unfortunately, websites (and therefore web - // extensions) are allowed to write some cookies which they're not allowed - // to read. - is(cookies.length, 3, "expected three cookies for host"); - - is(cookies[0].name, "bar", "correct cookie name"); - is(cookies[0].value, "quux", "correct cookie value"); - - is(cookies[1].name, "deleted", "correct cookie name"); - - is(cookies[2].name, "foo", "correct cookie name"); - is(cookies[2].value, "baz", "correct cookie value"); - } else { - is(cookies.length, 2, "expected two cookies for host"); - - is(cookies[0].name, "deleted", "correct second cookie name"); - - is(cookies[1].name, "foo", "correct cookie name"); - is(cookies[1].value, "bar", "correct cookie value"); - } - - for (let cookie of cookies) { - cookieSvc.remove(cookie.host, cookie.name, "/", false, {}); - } - // Make sure we don't silently poison subsequent tests if something goes wrong. - is(getCookies(options.domain).length, 0, "cookies cleared"); -} diff --git a/toolkit/components/extensions/test/mochitest/head_webrequest.js b/toolkit/components/extensions/test/mochitest/head_webrequest.js deleted file mode 100644 index 96924e505..000000000 --- a/toolkit/components/extensions/test/mochitest/head_webrequest.js +++ /dev/null @@ -1,331 +0,0 @@ -"use strict"; - -let commonEvents = { - "onBeforeRequest": [{urls: [""]}, ["blocking"]], - "onBeforeSendHeaders": [{urls: [""]}, ["blocking", "requestHeaders"]], - "onSendHeaders": [{urls: [""]}, ["requestHeaders"]], - "onBeforeRedirect": [{urls: [""]}], - "onHeadersReceived": [{urls: [""]}, ["blocking", "responseHeaders"]], - "onResponseStarted": [{urls: [""]}], - "onCompleted": [{urls: [""]}, ["responseHeaders"]], - "onErrorOccurred": [{urls: [""]}], -}; - -function background(events) { - let expect; - let ignore; - let defaultOrigin; - - browser.test.onMessage.addListener((msg, expected) => { - if (msg !== "set-expected") { - return; - } - expect = expected.expect; - defaultOrigin = expected.origin; - ignore = expected.ignore; - let promises = []; - // Initialize some stuff we'll need in the tests. - for (let entry of Object.values(expect)) { - // a place for the test infrastructure to store some state. - entry.test = {}; - // Each entry in expected gets a Promise that will be resolved in the - // last event for that entry. This will either be onCompleted, or the - // last entry if an events list was provided. - promises.push(new Promise(resolve => { entry.test.resolve = resolve; })); - // If events was left undefined, we're expecting all normal events we're - // listening for, exclude onBeforeRedirect and onErrorOccurred - if (entry.events === undefined) { - entry.events = Object.keys(events).filter(name => name != "onErrorOccurred" && name != "onBeforeRedirect"); - } - if (entry.optional_events === undefined) { - entry.optional_events = []; - } - } - // When every expected entry has finished our test is done. - Promise.all(promises).then(() => { - browser.test.sendMessage("done"); - }); - browser.test.sendMessage("continue"); - }); - - // Retrieve the per-file/test expected values. - function getExpected(details) { - let url = new URL(details.url); - let filename; - if (url.protocol == "data:") { - // pathname is everything after protocol. - filename = url.pathname; - } else { - filename = url.pathname.split("/").pop(); - } - if (ignore && ignore.includes(filename)) { - return; - } - let expected = expect[filename]; - if (!expected) { - browser.test.fail(`unexpected request ${filename}`); - return; - } - // Save filename for redirect verification. - expected.test.filename = filename; - return expected; - } - - // Process any test header modifications that can happen in request or response phases. - // If a test includes headers, it needs a complete header object, no undefined - // objects even if empty: - // request: { - // add: {"HeaderName": "value",}, - // modify: {"HeaderName": "value",}, - // remove: ["HeaderName",], - // }, - // response: { - // add: {"HeaderName": "value",}, - // modify: {"HeaderName": "value",}, - // remove: ["HeaderName",], - // }, - function processHeaders(phase, expected, details) { - // This should only happen once per phase [request|response]. - browser.test.assertFalse(!!expected.test[phase], `First processing of headers for ${phase}`); - expected.test[phase] = true; - - let headers = details[`${phase}Headers`]; - browser.test.assertTrue(Array.isArray(headers), `${phase}Headers array present`); - - let {add, modify, remove} = expected.headers[phase]; - - for (let name in add) { - browser.test.assertTrue(!headers.find(h => h.name === name), `header ${name} to be added not present yet in ${phase}Headers`); - let header = {name: name}; - if (name.endsWith("-binary")) { - header.binaryValue = Array.from(add[name], c => c.charCodeAt(0)); - } else { - header.value = add[name]; - } - headers.push(header); - } - - let modifiedAny = false; - for (let header of headers) { - if (header.name.toLowerCase() in modify) { - header.value = modify[header.name.toLowerCase()]; - modifiedAny = true; - } - } - browser.test.assertTrue(modifiedAny, `at least one ${phase}Headers element to modify`); - - let deletedAny = false; - for (let j = headers.length; j-- > 0;) { - if (remove.includes(headers[j].name.toLowerCase())) { - headers.splice(j, 1); - deletedAny = true; - } - } - browser.test.assertTrue(deletedAny, `at least one ${phase}Headers element to delete`); - - return headers; - } - - // phase is request or response. - function checkHeaders(phase, expected, details) { - if (!/^https?:/.test(details.url)) { - return; - } - - let headers = details[`${phase}Headers`]; - browser.test.assertTrue(Array.isArray(headers), `valid ${phase}Headers array`); - - let {add, modify, remove} = expected.headers[phase]; - for (let name in add) { - let value = headers.find(h => h.name.toLowerCase() === name.toLowerCase()).value; - browser.test.assertEq(value, add[name], `header ${name} correctly injected in ${phase}Headers`); - } - - for (let name in modify) { - let value = headers.find(h => h.name.toLowerCase() === name.toLowerCase()).value; - browser.test.assertEq(value, modify[name], `header ${name} matches modified value`); - } - - for (let name of remove) { - let found = headers.find(h => h.name.toLowerCase() === name.toLowerCase()); - browser.test.assertFalse(!!found, `deleted header ${name} still found in ${phase}Headers`); - } - } - - function getListener(name) { - return details => { - let result = {}; - browser.test.log(`${name} ${details.requestId} ${details.url}`); - let expected = getExpected(details); - if (!expected) { - return result; - } - let expectedEvent = expected.events[0] == name; - if (expectedEvent) { - expected.events.shift(); - } else { - expectedEvent = expected.optional_events[0] == name; - if (expectedEvent) { - expected.optional_events.shift(); - } - } - browser.test.assertTrue(expectedEvent, `received ${name}`); - browser.test.assertEq(expected.type, details.type, "resource type is correct"); - browser.test.assertEq(expected.origin || defaultOrigin, details.originUrl, "origin is correct"); - - if (name == "onBeforeRequest") { - // Save some values to test request consistency in later events. - browser.test.assertTrue(details.tabId !== undefined, `tabId ${details.tabId}`); - browser.test.assertTrue(details.requestId !== undefined, `requestId ${details.requestId}`); - // Validate requestId if it's already set, this happens with redirects. - if (expected.test.requestId !== undefined) { - browser.test.assertEq("string", typeof expected.test.requestId, `requestid ${expected.test.requestId} is string`); - browser.test.assertEq("string", typeof details.requestId, `requestid ${details.requestId} is string`); - browser.test.assertEq("number", typeof parseInt(details.requestId, 10), "parsed requestid is number"); - browser.test.assertNotEq(expected.test.requestId, details.requestId, - `last requestId ${expected.test.requestId} different from this one ${details.requestId}`); - } else { - // Save any values we want to validate in later events. - expected.test.requestId = details.requestId; - expected.test.tabId = details.tabId; - } - // Tests we don't need to do every event. - browser.test.assertTrue(details.type.toUpperCase() in browser.webRequest.ResourceType, `valid resource type ${details.type}`); - if (details.type == "main_frame") { - browser.test.assertEq(0, details.frameId, "frameId is zero when type is main_frame bug 1329299"); - } - } else { - // On events after onBeforeRequest, check the previous values. - browser.test.assertEq(expected.test.requestId, details.requestId, "correct requestId"); - browser.test.assertEq(expected.test.tabId, details.tabId, "correct tabId"); - } - if (name == "onBeforeSendHeaders") { - if (expected.headers && expected.headers.request) { - result.requestHeaders = processHeaders("request", expected, details); - } - if (expected.redirect) { - browser.test.log(`${name} redirect request`); - result.redirectUrl = details.url.replace(expected.test.filename, expected.redirect); - } - } - if (name == "onSendHeaders") { - if (expected.headers && expected.headers.request) { - checkHeaders("request", expected, details); - } - } - if (name == "onHeadersReceived") { - browser.test.assertEq(expected.status || 200, details.statusCode, - `expected HTTP status received for ${details.url}`); - if (expected.headers && expected.headers.response) { - result.responseHeaders = processHeaders("response", expected, details); - } - } - if (name == "onCompleted") { - // If we have already completed a GET request for this url, - // and it was found, we expect for the response to come fromCache. - // expected.cached may be undefined, force boolean. - let expectCached = !!expected.cached && details.method === "GET" && details.statusCode != 404; - browser.test.assertEq(expectCached, details.fromCache, "fromCache is correct"); - // We can only tell IPs for non-cached HTTP requests. - if (!details.fromCache && /^https?:/.test(details.url)) { - browser.test.assertEq("127.0.0.1", details.ip, `correct ip for ${details.url}`); - } - if (expected.headers && expected.headers.response) { - checkHeaders("response", expected, details); - } - } - - if (expected.cancel && expected.cancel == name) { - browser.test.log(`${name} cancel request`); - browser.test.sendMessage("cancelled"); - result.cancel = true; - } - // If we've used up all the events for this test, resolve the promise. - // If something wrong happens and more events come through, there will be - // failures. - if (expected.events.length <= 0) { - expected.test.resolve(); - } - return result; - }; - } - - for (let [name, args] of Object.entries(events)) { - browser.test.log(`adding listener for ${name}`); - try { - browser.webRequest[name].addListener(getListener(name), ...args); - } catch (e) { - browser.test.assertTrue(/\brequestBody\b/.test(e.message), - "Request body is unsupported"); - - // RequestBody is disabled in release builds. - if (!/\brequestBody\b/.test(e.message)) { - throw e; - } - - args.splice(args.indexOf("requestBody"), 1); - browser.webRequest[name].addListener(getListener(name), ...args); - } - } -} - -/* exported makeExtension */ - -function makeExtension(events = commonEvents) { - return ExtensionTestUtils.loadExtension({ - manifest: { - permissions: [ - "webRequest", - "webRequestBlocking", - "", - ], - }, - background: `(${background})(${JSON.stringify(events)})`, - }); -} - -/* exported addStylesheet */ - -function addStylesheet(file) { - let link = document.createElement("link"); - link.setAttribute("rel", "stylesheet"); - link.setAttribute("href", file); - document.body.appendChild(link); -} - -/* exported addLink */ - -function addLink(file) { - let a = document.createElement("a"); - a.setAttribute("href", file); - a.setAttribute("target", "_blank"); - document.body.appendChild(a); - return a; -} - -/* exported addImage */ - -function addImage(file) { - let img = document.createElement("img"); - img.setAttribute("src", file); - document.body.appendChild(img); -} - -/* exported addScript */ - -function addScript(file) { - let script = document.createElement("script"); - script.setAttribute("type", "text/javascript"); - script.setAttribute("src", file); - document.getElementsByTagName("head").item(0).appendChild(script); -} - -/* exported addFrame */ - -function addFrame(file) { - let frame = document.createElement("iframe"); - frame.setAttribute("width", "200"); - frame.setAttribute("height", "200"); - frame.setAttribute("src", file); - document.body.appendChild(frame); -} diff --git a/toolkit/components/extensions/test/mochitest/mochitest.ini b/toolkit/components/extensions/test/mochitest/mochitest.ini deleted file mode 100644 index 45586237e..000000000 --- a/toolkit/components/extensions/test/mochitest/mochitest.ini +++ /dev/null @@ -1,114 +0,0 @@ -[DEFAULT] -support-files = - head.js - file_mixed.html - head_webrequest.js - file_csp.html - file_csp.html^headers^ - file_WebRequest_page3.html - file_webNavigation_clientRedirect.html - file_webNavigation_clientRedirect_httpHeaders.html - file_webNavigation_clientRedirect_httpHeaders.html^headers^ - file_webNavigation_frameClientRedirect.html - file_webNavigation_frameRedirect.html - file_webNavigation_manualSubframe.html - file_webNavigation_manualSubframe_page1.html - file_webNavigation_manualSubframe_page2.html - file_WebNavigation_page1.html - file_WebNavigation_page2.html - file_WebNavigation_page3.html - file_with_about_blank.html - file_image_good.png - file_image_bad.png - file_image_redirect.png - file_style_good.css - file_style_bad.css - file_style_redirect.css - file_script_good.js - file_script_bad.js - file_script_redirect.js - file_script_xhr.js - file_sample.html - redirection.sjs - file_privilege_escalation.html - file_ext_test_api_injection.js - file_permission_xhr.html - file_teardown_test.js - return_headers.sjs - webrequest_worker.js -tags = webextensions - -[test_clipboard.html] -# skip-if = # disabled test case with_permission_allow_copy, see inline comment. -[test_ext_inIncognitoContext_window.html] -skip-if = os == 'android' # Android does not currently support windows. -[test_ext_geturl.html] -[test_ext_background_canvas.html] -[test_ext_content_security_policy.html] -[test_ext_contentscript.html] -[test_ext_contentscript_api_injection.html] -[test_ext_contentscript_async_loading.html] -[test_ext_contentscript_context.html] -[test_ext_contentscript_create_iframe.html] -[test_ext_contentscript_devtools_metadata.html] -[test_ext_contentscript_exporthelpers.html] -[test_ext_contentscript_css.html] -[test_ext_contentscript_about_blank.html] -[test_ext_contentscript_permission.html] -skip-if = os == 'android' # Android does not support tabs API. Bug 1260250 -[test_ext_contentscript_teardown.html] -skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250 -[test_ext_exclude_include_globs.html] -[test_ext_i18n_css.html] -[test_ext_generate.html] -[test_ext_notifications.html] -[test_ext_permission_xhr.html] -[test_ext_runtime_connect.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_runtime_connect_twoway.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_runtime_connect2.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_runtime_disconnect.html] -[test_ext_runtime_id.html] -[test_ext_sandbox_var.html] -[test_ext_sendmessage_reply.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_sendmessage_reply2.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_sendmessage_doublereply.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_sendmessage_no_receiver.html] -[test_ext_storage_content.html] -[test_ext_storage_tab.html] -skip-if = os == 'android' # Android does not currently support tabs. -[test_ext_test.html] -[test_ext_cookies.html] -skip-if = os == 'android' # Bug 1258975 on android. -[test_ext_background_api_injection.html] -[test_ext_background_generated_url.html] -[test_ext_background_teardown.html] -[test_ext_tab_teardown.html] -skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250 -[test_ext_unload_frame.html] -[test_ext_i18n.html] -skip-if = (os == 'android') # Bug 1258975 on android. -[test_ext_listener_proxies.html] -[test_ext_web_accessible_resources.html] -skip-if = (os == 'android') # Bug 1258975 on android. -[test_ext_webrequest_background_events.html] -skip-if = os == 'android' # webrequest api unsupported (bug 1258975). -[test_ext_webrequest_basic.html] -skip-if = os == 'android' # webrequest api unsupported (bug 1258975). -[test_ext_webrequest_suspend.html] -skip-if = os == 'android' # webrequest api unsupported (bug 1258975). -[test_ext_webrequest_upload.html] -skip-if = release_or_beta || os == 'android' # webrequest api unsupported (bug 1258975). -[test_ext_webnavigation.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_webnavigation_filters.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_window_postMessage.html] -[test_ext_subframes_privileges.html] -skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). -[test_ext_xhr_capabilities.html] diff --git a/toolkit/components/extensions/test/mochitest/redirection.sjs b/toolkit/components/extensions/test/mochitest/redirection.sjs deleted file mode 100644 index 370ecd213..000000000 --- a/toolkit/components/extensions/test/mochitest/redirection.sjs +++ /dev/null @@ -1,4 +0,0 @@ -function handleRequest(aRequest, aResponse) { - aResponse.setStatusLine(aRequest.httpVersion, 302); - aResponse.setHeader("Location", "./dummy_page.html"); -} diff --git a/toolkit/components/extensions/test/mochitest/return_headers.sjs b/toolkit/components/extensions/test/mochitest/return_headers.sjs deleted file mode 100644 index 54e2e5fb4..000000000 --- a/toolkit/components/extensions/test/mochitest/return_headers.sjs +++ /dev/null @@ -1,20 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ft=javascript sts=2 sw=2 et tw=80: */ -"use strict"; - -/* exported handleRequest */ - -function handleRequest(request, response) { - response.setHeader("Content-Type", "text/plain", false); - - let headers = {}; - // Why on earth is this a nsISimpleEnumerator... - let enumerator = request.headers; - while (enumerator.hasMoreElements()) { - let header = enumerator.getNext().data; - headers[header.toLowerCase()] = request.getHeader(header); - } - - response.write(JSON.stringify(headers)); -} - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html deleted file mode 100644 index 0edf5ea86..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_page.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_page.html deleted file mode 100644 index 3c4774652..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_page.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html deleted file mode 100644 index e08121a8f..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - Test for content script unrecognized property on manifest - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html deleted file mode 100644 index c1aaae035..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - Test downloads.download() saveAs option - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_eventpage_warning.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_eventpage_warning.html deleted file mode 100644 index ecea8237e..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_eventpage_warning.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - Test for WebExtension EventPage Warning - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_hybrid_addons.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_hybrid_addons.html deleted file mode 100644 index a74c551f0..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_hybrid_addons.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - Test for hybrid addons: SDK or bootstrap.js + embedded WebExtension - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_idle.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_idle.html deleted file mode 100644 index 3c3063e67..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_idle.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html deleted file mode 100644 index e3098e6b1..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_storage_cleanup.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_storage_cleanup.html deleted file mode 100644 index 010769500..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_storage_cleanup.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_trustworthy_origin.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_trustworthy_origin.html deleted file mode 100644 index 573c08806..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_trustworthy_origin.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html deleted file mode 100644 index 768eb31fd..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - Test for simple WebExtension - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html deleted file mode 100644 index a13c4d475..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - Test for simple WebExtension - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_native_messaging_paths.html b/toolkit/components/extensions/test/mochitest/test_chrome_native_messaging_paths.html deleted file mode 100644 index 29a148063..000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_native_messaging_paths.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_clipboard.html b/toolkit/components/extensions/test/mochitest/test_clipboard.html deleted file mode 100644 index 900ee5f10..000000000 --- a/toolkit/components/extensions/test/mochitest/test_clipboard.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - clipboard permission test - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js deleted file mode 100644 index 0f617c37e..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js +++ /dev/null @@ -1,158 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -// Tests whether not too many APIs are visible by default. -// This file is used by test_ext_all_apis.html in browser/ and mobile/android/, -// which may modify the following variables to add or remove expected APIs. -/* globals expectedContentApisTargetSpecific */ -/* globals expectedBackgroundApisTargetSpecific */ - -// Generates a list of expectations. -function generateExpectations(list) { - return list.reduce((allApis, path) => { - return allApis.concat(`browser.${path}`, `chrome.${path}`); - }, []).sort(); -} - -let expectedCommonApis = [ - "extension.getURL", - "extension.inIncognitoContext", - "extension.lastError", - "i18n.detectLanguage", - "i18n.getAcceptLanguages", - "i18n.getMessage", - "i18n.getUILanguage", - "runtime.OnInstalledReason", - "runtime.OnRestartRequiredReason", - "runtime.PlatformArch", - "runtime.PlatformOs", - "runtime.RequestUpdateCheckStatus", - "runtime.getManifest", - "runtime.connect", - "runtime.getURL", - "runtime.id", - "runtime.lastError", - "runtime.onConnect", - "runtime.onMessage", - "runtime.sendMessage", - // If you want to add a new powerful test API, please see bug 1287233. - "test.assertEq", - "test.assertFalse", - "test.assertRejects", - "test.assertThrows", - "test.assertTrue", - "test.fail", - "test.log", - "test.notifyFail", - "test.notifyPass", - "test.onMessage", - "test.sendMessage", - "test.succeed", -]; - -let expectedContentApis = [ - ...expectedCommonApis, - ...expectedContentApisTargetSpecific, -]; - -let expectedBackgroundApis = [ - ...expectedCommonApis, - ...expectedBackgroundApisTargetSpecific, - "extension.ViewType", - "extension.getBackgroundPage", - "extension.getViews", - "extension.isAllowedFileSchemeAccess", - "extension.isAllowedIncognitoAccess", - // Note: extensionTypes is not visible in Chrome. - "extensionTypes.ImageFormat", - "extensionTypes.RunAt", - "management.ExtensionDisabledReason", - "management.ExtensionInstallType", - "management.ExtensionType", - "management.getSelf", - "management.uninstallSelf", - "runtime.getBackgroundPage", - "runtime.getBrowserInfo", - "runtime.getPlatformInfo", - "runtime.onInstalled", - "runtime.onStartup", - "runtime.onUpdateAvailable", - "runtime.openOptionsPage", - "runtime.reload", - "runtime.setUninstallURL", -]; - -function sendAllApis() { - function isEvent(key, val) { - if (!/^on[A-Z]/.test(key)) { - return false; - } - let eventKeys = []; - for (let prop in val) { - eventKeys.push(prop); - } - eventKeys = eventKeys.sort().join(); - return eventKeys === "addListener,hasListener,removeListener"; - } - function mayRecurse(key, val) { - if (Object.keys(val).filter(k => !/^[A-Z\-0-9_]+$/.test(k)).length === 0) { - // Don't recurse on constants and empty objects. - return false; - } - return !isEvent(key, val); - } - - let results = []; - function diveDeeper(path, obj) { - for (let key in obj) { - let val = obj[key]; - if (typeof val == "object" && val !== null && mayRecurse(key, val)) { - diveDeeper(`${path}.${key}`, val); - } else if (val !== undefined) { - results.push(`${path}.${key}`); - } - } - } - diveDeeper("browser", browser); - diveDeeper("chrome", chrome); - browser.test.sendMessage("allApis", results.sort()); -} - -add_task(function* test_enumerate_content_script_apis() { - let extensionData = { - manifest: { - content_scripts: [{ - matches: ["http://mochi.test/*/file_sample.html"], - js: ["contentscript.js"], - run_at: "document_start", - }], - }, - files: { - "contentscript.js": sendAllApis, - }, - }; - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - - let win = window.open("file_sample.html"); - let actualApis = yield extension.awaitMessage("allApis"); - win.close(); - let expectedApis = generateExpectations(expectedContentApis); - isDeeply(actualApis, expectedApis, "content script APIs"); - - yield extension.unload(); -}); - -add_task(function* test_enumerate_background_script_apis() { - let extensionData = { - background: sendAllApis, - }; - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - let actualApis = yield extension.awaitMessage("allApis"); - let expectedApis = generateExpectations(expectedBackgroundApis); - isDeeply(actualApis, expectedApis, "background script APIs"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html b/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html deleted file mode 100644 index f43a59f81..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - Test for privilege escalation into content pages - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html b/toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html deleted file mode 100644 index bff7190cb..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - Test for background page canvas rendering - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_background_generated_url.html b/toolkit/components/extensions/test/mochitest/test_ext_background_generated_url.html deleted file mode 100644 index f4fcf3d34..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_background_generated_url.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - Test _generated_background_page.html - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_background_teardown.html b/toolkit/components/extensions/test/mochitest/test_ext_background_teardown.html deleted file mode 100644 index bb6b2e970..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_background_teardown.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - Test for background script teardown - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_content_security_policy.html b/toolkit/components/extensions/test/mochitest/test_ext_content_security_policy.html deleted file mode 100644 index a36f29563..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_content_security_policy.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - WebExtension CSP test - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript.html deleted file mode 100644 index 39f1bfabd..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html deleted file mode 100644 index 3766678e7..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - Test content script match_about_blank option - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_api_injection.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_api_injection.html deleted file mode 100644 index abf3d349f..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_api_injection.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - Test for privilege escalation into iframe with content script APIs - - - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_async_loading.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_async_loading.html deleted file mode 100644 index d78f7ce02..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_async_loading.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - Test content script async loading - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_context.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_context.html deleted file mode 100644 index 97b1645dd..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_context.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - Test for content script contexts - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_create_iframe.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_create_iframe.html deleted file mode 100644 index 8aac3e213..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_create_iframe.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_css.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_css.html deleted file mode 100644 index 5630a1d68..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_css.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html deleted file mode 100644 index 137a3cda4..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - Test for Sandbox metadata on WebExtensions ContentScripts - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_exporthelpers.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_exporthelpers.html deleted file mode 100644 index f3414901d..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_exporthelpers.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_incognito.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_incognito.html deleted file mode 100644 index a2f38dce6..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_incognito.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - Test for content script private browsing ID - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_permission.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_permission.html deleted file mode 100644 index eaf815092..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_permission.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_teardown.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_teardown.html deleted file mode 100644 index 33a8c4ccc..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_teardown.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - Test for content script teardown - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies.html deleted file mode 100644 index d414a4e46..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_cookies.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html deleted file mode 100644 index bc4994eec..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html deleted file mode 100644 index 3927d9e94..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_bad.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_bad.html deleted file mode 100644 index 15a62855a..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_bad.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_good.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_good.html deleted file mode 100644 index 31e83188c..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_good.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html b/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html deleted file mode 100644 index 640522b40..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_generate.html b/toolkit/components/extensions/test/mochitest/test_ext_generate.html deleted file mode 100644 index cfafcbad9..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_generate.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - Test for generating WebExtensions - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_geturl.html b/toolkit/components/extensions/test/mochitest/test_ext_geturl.html deleted file mode 100644 index 6e39c2f5d..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_geturl.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_i18n.html b/toolkit/components/extensions/test/mochitest/test_ext_i18n.html deleted file mode 100644 index 1f7330bbb..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_i18n.html +++ /dev/null @@ -1,432 +0,0 @@ - - - - - Test for WebExtension localization APIs - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_i18n_css.html b/toolkit/components/extensions/test/mochitest/test_ext_i18n_css.html deleted file mode 100644 index 7c6a8eeaa..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_i18n_css.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html b/toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html deleted file mode 100644 index 675cbb298..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_jsversion.html b/toolkit/components/extensions/test/mochitest/test_ext_jsversion.html deleted file mode 100644 index da0c355e0..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_jsversion.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - Test for simple WebExtension - - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_listener_proxies.html b/toolkit/components/extensions/test/mochitest/test_ext_listener_proxies.html deleted file mode 100644 index ca8db873e..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_listener_proxies.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_notifications.html b/toolkit/components/extensions/test/mochitest/test_ext_notifications.html deleted file mode 100644 index d1b798cf9..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_notifications.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - Test for notifications - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_permission_xhr.html b/toolkit/components/extensions/test/mochitest/test_ext_permission_xhr.html deleted file mode 100644 index 07967d5d0..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_permission_xhr.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - WebExtension Test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html deleted file mode 100644 index 60351eaee..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html deleted file mode 100644 index dce12b21b..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html deleted file mode 100644 index e84134eff..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - WebExtension test - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html deleted file mode 100644 index 5764d0a3c..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_id.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_id.html deleted file mode 100644 index 4cdefda41..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_id.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - Test for browser.runtime.id - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sandbox_var.html b/toolkit/components/extensions/test/mochitest/test_ext_sandbox_var.html deleted file mode 100644 index 426a71ac6..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_sandbox_var.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_schema.html b/toolkit/components/extensions/test/mochitest/test_ext_schema.html deleted file mode 100644 index 8a0e11c56..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_schema.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - Test for schema API creation - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html deleted file mode 100644 index a3ef37cad..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html deleted file mode 100644 index 96af6558e..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html deleted file mode 100644 index a4ac708b2..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html deleted file mode 100644 index 1ebc1b40f..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_storage_content.html b/toolkit/components/extensions/test/mochitest/test_ext_storage_content.html deleted file mode 100644 index 09a33814a..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_storage_content.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_storage_tab.html b/toolkit/components/extensions/test/mochitest/test_ext_storage_tab.html deleted file mode 100644 index 32d8e6af0..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_storage_tab.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html b/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html deleted file mode 100644 index 1f3a9a3c9..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tab_teardown.html b/toolkit/components/extensions/test/mochitest/test_ext_tab_teardown.html deleted file mode 100644 index dc351e48a..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_tab_teardown.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - Test for extension tab teardown - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_test.html b/toolkit/components/extensions/test/mochitest/test_ext_test.html deleted file mode 100644 index fef31e0e2..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_test.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - Testing test - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_unload_frame.html b/toolkit/components/extensions/test/mochitest/test_ext_unload_frame.html deleted file mode 100644 index 5572de281..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_unload_frame.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - WebExtensions test - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html deleted file mode 100644 index fa3228739..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html +++ /dev/null @@ -1,353 +0,0 @@ - - - - Test the web_accessible_resources manifest directive - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html deleted file mode 100644 index 2287fd9b1..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - Test for simple WebExtension - - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html deleted file mode 100644 index a0de5e9e5..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html +++ /dev/null @@ -1,308 +0,0 @@ - - - - Test for simple WebExtension - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html deleted file mode 100644 index 78efeab35..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - Test for simple WebExtension - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html deleted file mode 100644 index ef77fee3b..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - - - - - - - - - - -
Sample text
- - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_suspend.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_suspend.html deleted file mode 100644 index c8423ec7c..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_suspend.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - Test for simple WebExtension - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html deleted file mode 100644 index 998ab9800..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - - - - - - - -
- - - - -
- -
- - - -
- - -
- - -
- - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_window_postMessage.html b/toolkit/components/extensions/test/mochitest/test_ext_window_postMessage.html deleted file mode 100644 index 7d49d55ba..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_window_postMessage.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - Test for content script - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/test_ext_xhr_capabilities.html b/toolkit/components/extensions/test/mochitest/test_ext_xhr_capabilities.html deleted file mode 100644 index 1afdadb9f..000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_xhr_capabilities.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - Test XHR capabilities - - - - - - - - - - - - diff --git a/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js b/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js deleted file mode 100644 index ccfb2ac1f..000000000 --- a/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; - -onmessage = function(event) { - fetch("https://example.com/example.txt").then(() => { - postMessage("Done!"); - }); -}; - diff --git a/toolkit/components/extensions/test/mochitest/webrequest_test.jsm b/toolkit/components/extensions/test/mochitest/webrequest_test.jsm deleted file mode 100644 index bfb148301..000000000 --- a/toolkit/components/extensions/test/mochitest/webrequest_test.jsm +++ /dev/null @@ -1,22 +0,0 @@ -"use strict"; - -this.EXPORTED_SYMBOLS = ["webrequest_test"]; - -Components.utils.importGlobalProperties(["fetch", "XMLHttpRequest"]); - -this.webrequest_test = { - testFetch(url) { - return fetch(url); - }, - - testXHR(url) { - return new Promise(resolve => { - let xhr = new XMLHttpRequest(); - xhr.open("HEAD", url); - xhr.onload = () => { - resolve(); - }; - xhr.send(); - }); - }, -}; diff --git a/toolkit/components/extensions/test/mochitest/webrequest_worker.js b/toolkit/components/extensions/test/mochitest/webrequest_worker.js deleted file mode 100644 index dcffd0857..000000000 --- a/toolkit/components/extensions/test/mochitest/webrequest_worker.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; - -fetch("https://example.com/example.txt"); diff --git a/toolkit/components/extensions/test/xpcshell/.eslintrc.js b/toolkit/components/extensions/test/xpcshell/.eslintrc.js deleted file mode 100644 index 3758537ef..000000000 --- a/toolkit/components/extensions/test/xpcshell/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -module.exports = { // eslint-disable-line no-undef - "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc.js", - - "globals": { - "browser": false, - }, -}; diff --git a/toolkit/components/extensions/test/xpcshell/data/file_download.html b/toolkit/components/extensions/test/xpcshell/data/file_download.html deleted file mode 100644 index d970c6325..000000000 --- a/toolkit/components/extensions/test/xpcshell/data/file_download.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - -
Download HTML File
- - - diff --git a/toolkit/components/extensions/test/xpcshell/data/file_download.txt b/toolkit/components/extensions/test/xpcshell/data/file_download.txt deleted file mode 100644 index 6293c7af7..000000000 --- a/toolkit/components/extensions/test/xpcshell/data/file_download.txt +++ /dev/null @@ -1 +0,0 @@ -This is a sample file used in download tests. diff --git a/toolkit/components/extensions/test/xpcshell/head.js b/toolkit/components/extensions/test/xpcshell/head.js deleted file mode 100644 index 9e22be6da..000000000 --- a/toolkit/components/extensions/test/xpcshell/head.js +++ /dev/null @@ -1,111 +0,0 @@ -"use strict"; - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -/* exported createHttpServer, promiseConsoleOutput, cleanupDir */ - -Components.utils.import("resource://gre/modules/Task.jsm"); -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Timer.jsm"); -Components.utils.import("resource://testing-common/AddonTestUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", - "resource://gre/modules/AppConstants.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Extension", - "resource://gre/modules/Extension.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData", - "resource://gre/modules/Extension.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement", - "resource://gre/modules/ExtensionManagement.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils", - "resource://testing-common/ExtensionXPCShellUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "HttpServer", - "resource://testing-common/httpd.js"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Schemas", - "resource://gre/modules/Schemas.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); - -ExtensionTestUtils.init(this); - -/** - * Creates a new HttpServer for testing, and begins listening on the - * specified port. Automatically shuts down the server when the test - * unit ends. - * - * @param {integer} [port] - * The port to listen on. If omitted, listen on a random - * port. The latter is the preferred behavior. - * - * @returns {HttpServer} - */ -function createHttpServer(port = -1) { - let server = new HttpServer(); - server.start(port); - - do_register_cleanup(() => { - return new Promise(resolve => { - server.stop(resolve); - }); - }); - - return server; -} - -var promiseConsoleOutput = Task.async(function* (task) { - const DONE = `=== console listener ${Math.random()} done ===`; - - let listener; - let messages = []; - let awaitListener = new Promise(resolve => { - listener = msg => { - if (msg == DONE) { - resolve(); - } else { - void (msg instanceof Ci.nsIConsoleMessage); - messages.push(msg); - } - }; - }); - - Services.console.registerListener(listener); - try { - let result = yield task(); - - Services.console.logStringMessage(DONE); - yield awaitListener; - - return {messages, result}; - } finally { - Services.console.unregisterListener(listener); - } -}); - -// Attempt to remove a directory. If the Windows OS is still using the -// file sometimes remove() will fail. So try repeatedly until we can -// remove it or we give up. -function cleanupDir(dir) { - let count = 0; - return new Promise((resolve, reject) => { - function tryToRemoveDir() { - count += 1; - try { - dir.remove(true); - } catch (e) { - // ignore - } - if (!dir.exists()) { - return resolve(); - } - if (count >= 25) { - return reject(`Failed to cleanup directory: ${dir}`); - } - setTimeout(tryToRemoveDir, 100); - } - tryToRemoveDir(); - }); -} diff --git a/toolkit/components/extensions/test/xpcshell/head_native_messaging.js b/toolkit/components/extensions/test/xpcshell/head_native_messaging.js deleted file mode 100644 index f7c619b76..000000000 --- a/toolkit/components/extensions/test/xpcshell/head_native_messaging.js +++ /dev/null @@ -1,131 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -/* globals AppConstants, FileUtils */ -/* exported getSubprocessCount, setupHosts, waitForSubprocessExit */ - -XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry", - "resource://testing-common/MockRegistry.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "setTimeout", - "resource://gre/modules/Timer.jsm"); - -let {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm"); - - -// It's important that we use a space in this directory name to make sure we -// correctly handle executing batch files with spaces in their path. -let tmpDir = FileUtils.getDir("TmpD", ["Native Messaging"]); -tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - -do_register_cleanup(() => { - tmpDir.remove(true); -}); - -function getPath(filename) { - return OS.Path.join(tmpDir.path, filename); -} - -const ID = "native@tests.mozilla.org"; - - -function* setupHosts(scripts) { - const PERMS = {unixMode: 0o755}; - - const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); - const pythonPath = yield Subprocess.pathSearch(env.get("PYTHON")); - - function* writeManifest(script, scriptPath, path) { - let body = `#!${pythonPath} -u\n${script.script}`; - - yield OS.File.writeAtomic(scriptPath, body); - yield OS.File.setPermissions(scriptPath, PERMS); - - let manifest = { - name: script.name, - description: script.description, - path, - type: "stdio", - allowed_extensions: [ID], - }; - - let manifestPath = getPath(`${script.name}.json`); - yield OS.File.writeAtomic(manifestPath, JSON.stringify(manifest)); - - return manifestPath; - } - - switch (AppConstants.platform) { - case "macosx": - case "linux": - let dirProvider = { - getFile(property) { - if (property == "XREUserNativeMessaging") { - return tmpDir.clone(); - } else if (property == "XRESysNativeMessaging") { - return tmpDir.clone(); - } - return null; - }, - }; - - Services.dirsvc.registerProvider(dirProvider); - do_register_cleanup(() => { - Services.dirsvc.unregisterProvider(dirProvider); - }); - - for (let script of scripts) { - let path = getPath(`${script.name}.py`); - - yield writeManifest(script, path, path); - } - break; - - case "win": - const REGKEY = String.raw`Software\Mozilla\NativeMessagingHosts`; - - let registry = new MockRegistry(); - do_register_cleanup(() => { - registry.shutdown(); - }); - - for (let script of scripts) { - // It's important that we use a space in this filename. See directory - // name comment above. - let batPath = getPath(`batch ${script.name}.bat`); - let scriptPath = getPath(`${script.name}.py`); - - let batBody = `@ECHO OFF\n${pythonPath} -u "${scriptPath}" %*\n`; - yield OS.File.writeAtomic(batPath, batBody); - - // Create absolute and relative path versions of the entry. - for (let [name, path] of [[script.name, batPath], - [`relative.${script.name}`, OS.Path.basename(batPath)]]) { - script.name = name; - let manifestPath = yield writeManifest(script, scriptPath, path); - - registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, - `${REGKEY}\\${script.name}`, "", manifestPath); - } - } - break; - - default: - ok(false, `Native messaging is not supported on ${AppConstants.platform}`); - } -} - - -function getSubprocessCount() { - return SubprocessImpl.Process.getWorker().call("getProcesses", []) - .then(result => result.size); -} -function waitForSubprocessExit() { - return SubprocessImpl.Process.getWorker().call("waitForNoProcesses", []).then(() => { - // Return to the main event loop to give IO handlers enough time to consume - // their remaining buffered input. - return new Promise(resolve => setTimeout(resolve, 0)); - }); -} diff --git a/toolkit/components/extensions/test/xpcshell/head_sync.js b/toolkit/components/extensions/test/xpcshell/head_sync.js deleted file mode 100644 index 9b66b78e7..000000000 --- a/toolkit/components/extensions/test/xpcshell/head_sync.js +++ /dev/null @@ -1,67 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"; - -/* exported withSyncContext */ - -Components.utils.import("resource://gre/modules/Services.jsm", this); -Components.utils.import("resource://gre/modules/ExtensionCommon.jsm", this); - -var { - BaseContext, -} = ExtensionCommon; - -class Context extends BaseContext { - constructor(principal) { - super(); - Object.defineProperty(this, "principal", { - value: principal, - configurable: true, - }); - this.sandbox = Components.utils.Sandbox(principal, {wantXrays: false}); - this.extension = {id: "test@web.extension"}; - } - - get cloneScope() { - return this.sandbox; - } -} - -/** - * Call the given function with a newly-constructed context. - * Unload the context on the way out. - * - * @param {function} f the function to call - */ -function* withContext(f) { - const ssm = Services.scriptSecurityManager; - const PRINCIPAL1 = ssm.createCodebasePrincipalFromOrigin("http://www.example.org"); - const context = new Context(PRINCIPAL1); - try { - yield* f(context); - } finally { - yield context.unload(); - } -} - -/** - * Like withContext(), but also turn on the "storage.sync" pref for - * the duration of the function. - * Calls to this function can be replaced with calls to withContext - * once the pref becomes on by default. - * - * @param {function} f the function to call - */ -function* withSyncContext(f) { - const STORAGE_SYNC_PREF = "webextensions.storage.sync.enabled"; - let prefs = Services.prefs; - - try { - prefs.setBoolPref(STORAGE_SYNC_PREF, true); - yield* withContext(f); - } finally { - prefs.clearUserPref(STORAGE_SYNC_PREF); - } -} diff --git a/toolkit/components/extensions/test/xpcshell/native_messaging.ini b/toolkit/components/extensions/test/xpcshell/native_messaging.ini deleted file mode 100644 index d0e1da163..000000000 --- a/toolkit/components/extensions/test/xpcshell/native_messaging.ini +++ /dev/null @@ -1,13 +0,0 @@ -[DEFAULT] -head = head.js head_native_messaging.js -tail = -firefox-appdir = browser -skip-if = appname == "thunderbird" || os == "android" -subprocess = true -support-files = - data/** -tags = webextensions - -[test_ext_native_messaging.js] -[test_ext_native_messaging_perf.js] -[test_ext_native_messaging_unresponsive.js] diff --git a/toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js b/toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js deleted file mode 100644 index b6213baac..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -Cu.import("resource://gre/modules/Preferences.jsm"); - -const ADDON_ID = "test@web.extension"; - -const aps = Cc["@mozilla.org/addons/policy-service;1"] - .getService(Ci.nsIAddonPolicyService).wrappedJSObject; - -do_register_cleanup(() => { - aps.setAddonCSP(ADDON_ID, null); -}); - -add_task(function* test_addon_csp() { - equal(aps.baseCSP, Preferences.get("extensions.webextensions.base-content-security-policy"), - "Expected base CSP value"); - - equal(aps.defaultCSP, Preferences.get("extensions.webextensions.default-content-security-policy"), - "Expected default CSP value"); - - equal(aps.getAddonCSP(ADDON_ID), aps.defaultCSP, - "CSP for unknown add-on ID should be the default CSP"); - - - const CUSTOM_POLICY = "script-src: 'self' https://xpcshell.test.custom.csp; object-src: 'none'"; - - aps.setAddonCSP(ADDON_ID, CUSTOM_POLICY); - - equal(aps.getAddonCSP(ADDON_ID), CUSTOM_POLICY, "CSP should point to add-on's custom policy"); - - - aps.setAddonCSP(ADDON_ID, null); - - equal(aps.getAddonCSP(ADDON_ID), aps.defaultCSP, - "CSP should revert to default when set to null"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_csp_validator.js b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js deleted file mode 100644 index 59a7322bc..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_csp_validator.js +++ /dev/null @@ -1,85 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -const cps = Cc["@mozilla.org/addons/content-policy;1"].getService(Ci.nsIAddonContentPolicy); - -add_task(function* test_csp_validator() { - let checkPolicy = (policy, expectedResult, message = null) => { - do_print(`Checking policy: ${policy}`); - - let result = cps.validateAddonCSP(policy); - equal(result, expectedResult); - }; - - checkPolicy("script-src 'self'; object-src 'self';", - null); - - let hash = "'sha256-NjZhMDQ1YjQ1MjEwMmM1OWQ4NDBlYzA5N2Q1OWQ5NDY3ZTEzYTNmMzRmNjQ5NGU1MzlmZmQzMmMxYmIzNWYxOCAgLQo='"; - - checkPolicy(`script-src 'self' https://com https://*.example.com moz-extension://09abcdef blob: filesystem: ${hash} 'unsafe-eval'; ` + - `object-src 'self' https://com https://*.example.com moz-extension://09abcdef blob: filesystem: ${hash}`, - null); - - checkPolicy("", - "Policy is missing a required \u2018script-src\u2019 directive"); - - checkPolicy("object-src 'none';", - "Policy is missing a required \u2018script-src\u2019 directive"); - - - checkPolicy("default-src 'self'", null, - "A valid default-src should count as a valid script-src or object-src"); - - checkPolicy("default-src 'self'; script-src 'self'", null, - "A valid default-src should count as a valid script-src or object-src"); - - checkPolicy("default-src 'self'; object-src 'self'", null, - "A valid default-src should count as a valid script-src or object-src"); - - - checkPolicy("default-src 'self'; script-src http://example.com", - "\u2018script-src\u2019 directive contains a forbidden http: protocol source", - "A valid default-src should not allow an invalid script-src directive"); - - checkPolicy("default-src 'self'; object-src http://example.com", - "\u2018object-src\u2019 directive contains a forbidden http: protocol source", - "A valid default-src should not allow an invalid object-src directive"); - - - checkPolicy("script-src 'self';", - "Policy is missing a required \u2018object-src\u2019 directive"); - - checkPolicy("script-src 'none'; object-src 'none'", - "\u2018script-src\u2019 must include the source 'self'"); - - checkPolicy("script-src 'self'; object-src 'none';", - null); - - checkPolicy("script-src 'self' 'unsafe-inline'; object-src 'self';", - "\u2018script-src\u2019 directive contains a forbidden 'unsafe-inline' keyword"); - - - let directives = ["script-src", "object-src"]; - - for (let [directive, other] of [directives, directives.slice().reverse()]) { - for (let src of ["https://*", "https://*.blogspot.com", "https://*"]) { - checkPolicy(`${directive} 'self' ${src}; ${other} 'self';`, - `https: wildcard sources in \u2018${directive}\u2019 directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)`); - } - - checkPolicy(`${directive} 'self' https:; ${other} 'self';`, - `https: protocol requires a host in \u2018${directive}\u2019 directives`); - - checkPolicy(`${directive} 'self' http://example.com; ${other} 'self';`, - `\u2018${directive}\u2019 directive contains a forbidden http: protocol source`); - - for (let protocol of ["http", "ftp", "meh"]) { - checkPolicy(`${directive} 'self' ${protocol}:; ${other} 'self';`, - `\u2018${directive}\u2019 directive contains a forbidden ${protocol}: protocol source`); - } - - checkPolicy(`${directive} 'self' 'nonce-01234'; ${other} 'self';`, - `\u2018${directive}\u2019 directive contains a forbidden 'nonce-*' keyword`); - } -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js deleted file mode 100644 index 936c984c6..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js +++ /dev/null @@ -1,210 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_alarm_without_permissions() { - function backgroundScript() { - browser.test.assertTrue(!browser.alarms, - "alarm API is not available when the alarm permission is not required"); - browser.test.notifyPass("alarms_permission"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: [], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("alarms_permission"); - yield extension.unload(); -}); - - -add_task(function* test_alarm_fires() { - function backgroundScript() { - let ALARM_NAME = "test_ext_alarms"; - let timer; - - browser.alarms.onAlarm.addListener(alarm => { - browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the correct name"); - clearTimeout(timer); - browser.test.notifyPass("alarm-fires"); - }); - - browser.alarms.create(ALARM_NAME, {delayInMinutes: 0.02}); - - timer = setTimeout(async () => { - browser.test.fail("alarm fired within expected time"); - let wasCleared = await browser.alarms.clear(ALARM_NAME); - browser.test.assertTrue(wasCleared, "alarm was cleared"); - browser.test.notifyFail("alarm-fires"); - }, 10000); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["alarms"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("alarm-fires"); - yield extension.unload(); -}); - - -add_task(function* test_alarm_fires_with_when() { - function backgroundScript() { - let ALARM_NAME = "test_ext_alarms"; - let timer; - - browser.alarms.onAlarm.addListener(alarm => { - browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the expected name"); - clearTimeout(timer); - browser.test.notifyPass("alarm-when"); - }); - - browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000}); - - timer = setTimeout(async () => { - browser.test.fail("alarm fired within expected time"); - let wasCleared = await browser.alarms.clear(ALARM_NAME); - browser.test.assertTrue(wasCleared, "alarm was cleared"); - browser.test.notifyFail("alarm-when"); - }, 10000); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["alarms"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("alarm-when"); - yield extension.unload(); -}); - - -add_task(function* test_alarm_clear_non_matching_name() { - async function backgroundScript() { - let ALARM_NAME = "test_ext_alarms"; - - browser.alarms.create(ALARM_NAME, {when: Date.now() + 2000}); - - let wasCleared = await browser.alarms.clear(ALARM_NAME + "1"); - browser.test.assertFalse(wasCleared, "alarm was not cleared"); - - let alarms = await browser.alarms.getAll(); - browser.test.assertEq(1, alarms.length, "alarm was not removed"); - browser.test.notifyPass("alarm-clear"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["alarms"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("alarm-clear"); - yield extension.unload(); -}); - - -add_task(function* test_alarm_get_and_clear_single_argument() { - async function backgroundScript() { - browser.alarms.create({when: Date.now() + 2000}); - - let alarm = await browser.alarms.get(); - browser.test.assertEq("", alarm.name, "expected alarm returned"); - - let wasCleared = await browser.alarms.clear(); - browser.test.assertTrue(wasCleared, "alarm was cleared"); - - let alarms = await browser.alarms.getAll(); - browser.test.assertEq(0, alarms.length, "alarm was removed"); - - browser.test.notifyPass("alarm-single-arg"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["alarms"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("alarm-single-arg"); - yield extension.unload(); -}); - - -add_task(function* test_get_get_all_clear_all_alarms() { - async function backgroundScript() { - const ALARM_NAME = "test_alarm"; - - let suffixes = [0, 1, 2]; - - for (let suffix of suffixes) { - browser.alarms.create(ALARM_NAME + suffix, {when: Date.now() + (suffix + 1) * 10000}); - } - - let alarms = await browser.alarms.getAll(); - browser.test.assertEq(suffixes.length, alarms.length, "expected number of alarms were found"); - alarms.forEach((alarm, index) => { - browser.test.assertEq(ALARM_NAME + index, alarm.name, "alarm has the expected name"); - }); - - - for (let suffix of suffixes) { - let alarm = await browser.alarms.get(ALARM_NAME + suffix); - browser.test.assertEq(ALARM_NAME + suffix, alarm.name, "alarm has the expected name"); - browser.test.sendMessage(`get-${suffix}`); - } - - let wasCleared = await browser.alarms.clear(ALARM_NAME + suffixes[0]); - browser.test.assertTrue(wasCleared, "alarm was cleared"); - - alarms = await browser.alarms.getAll(); - browser.test.assertEq(2, alarms.length, "alarm was removed"); - - let alarm = await browser.alarms.get(ALARM_NAME + suffixes[0]); - browser.test.assertEq(undefined, alarm, "non-existent alarm is undefined"); - browser.test.sendMessage(`get-invalid`); - - wasCleared = await browser.alarms.clearAll(); - browser.test.assertTrue(wasCleared, "alarms were cleared"); - - alarms = await browser.alarms.getAll(); - browser.test.assertEq(0, alarms.length, "no alarms exist"); - browser.test.sendMessage("clearAll"); - browser.test.sendMessage("clear"); - browser.test.sendMessage("getAll"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["alarms"], - }, - }); - - yield Promise.all([ - extension.startup(), - extension.awaitMessage("getAll"), - extension.awaitMessage("get-0"), - extension.awaitMessage("get-1"), - extension.awaitMessage("get-2"), - extension.awaitMessage("clear"), - extension.awaitMessage("get-invalid"), - extension.awaitMessage("clearAll"), - ]); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js deleted file mode 100644 index 11407b108..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js +++ /dev/null @@ -1,33 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_cleared_alarm_does_not_fire() { - async function backgroundScript() { - let ALARM_NAME = "test_ext_alarms"; - - browser.alarms.onAlarm.addListener(alarm => { - browser.test.fail("cleared alarm does not fire"); - browser.test.notifyFail("alarm-cleared"); - }); - browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000}); - - let wasCleared = await browser.alarms.clear(ALARM_NAME); - browser.test.assertTrue(wasCleared, "alarm was cleared"); - - await new Promise(resolve => setTimeout(resolve, 2000)); - - browser.test.notifyPass("alarm-cleared"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["alarms"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("alarm-cleared"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js deleted file mode 100644 index 6bcdf4e33..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js +++ /dev/null @@ -1,44 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_periodic_alarm_fires() { - function backgroundScript() { - const ALARM_NAME = "test_ext_alarms"; - let count = 0; - let timer; - - browser.alarms.onAlarm.addListener(async alarm => { - browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the expected name"); - if (count++ === 3) { - clearTimeout(timer); - let wasCleared = await browser.alarms.clear(ALARM_NAME); - browser.test.assertTrue(wasCleared, "alarm was cleared"); - - browser.test.notifyPass("alarm-periodic"); - } - }); - - browser.alarms.create(ALARM_NAME, {periodInMinutes: 0.02}); - - timer = setTimeout(async () => { - browser.test.fail("alarm fired expected number of times"); - - let wasCleared = await browser.alarms.clear(ALARM_NAME); - browser.test.assertTrue(wasCleared, "alarm was cleared"); - - browser.test.notifyFail("alarm-periodic"); - }, 30000); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["alarms"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("alarm-periodic"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js deleted file mode 100644 index 96f61acb5..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js +++ /dev/null @@ -1,44 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - - -add_task(function* test_duplicate_alarm_name_replaces_alarm() { - function backgroundScript() { - let count = 0; - - browser.alarms.onAlarm.addListener(async alarm => { - if (alarm.name === "master alarm") { - browser.alarms.create("child alarm", {delayInMinutes: 0.05}); - let results = await browser.alarms.getAll(); - - browser.test.assertEq(2, results.length, "exactly two alarms exist"); - browser.test.assertEq("master alarm", results[0].name, "first alarm has the expected name"); - browser.test.assertEq("child alarm", results[1].name, "second alarm has the expected name"); - - if (count++ === 3) { - await browser.alarms.clear("master alarm"); - await browser.alarms.clear("child alarm"); - - browser.test.notifyPass("alarm-duplicate"); - } - } else { - browser.test.fail("duplicate named alarm replaced existing alarm"); - browser.test.notifyFail("alarm-duplicate"); - } - }); - - browser.alarms.create("master alarm", {delayInMinutes: 0.025, periodInMinutes: 0.025}); - } - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["alarms"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("alarm-duplicate"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js b/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js deleted file mode 100644 index d653d0e7a..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js +++ /dev/null @@ -1,64 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -let {Management} = Cu.import("resource://gre/modules/Extension.jsm", {}); -function getNextContext() { - return new Promise(resolve => { - Management.on("proxy-context-load", function listener(type, context) { - Management.off("proxy-context-load", listener); - resolve(context); - }); - }); -} - -add_task(function* test_storage_api_without_permissions() { - let extension = ExtensionTestUtils.loadExtension({ - background() { - // Force API initialization. - void browser.storage; - }, - - manifest: { - permissions: [], - }, - }); - - let contextPromise = getNextContext(); - yield extension.startup(); - - let context = yield contextPromise; - - // Force API initialization. - void context.apiObj; - - ok(!("storage" in context.apiObj), - "The storage API should not be initialized"); - - yield extension.unload(); -}); - -add_task(function* test_storage_api_with_permissions() { - let extension = ExtensionTestUtils.loadExtension({ - background() { - void browser.storage; - }, - - manifest: { - permissions: ["storage"], - }, - }); - - let contextPromise = getNextContext(); - yield extension.startup(); - - let context = yield contextPromise; - - // Force API initialization. - void context.apiObj; - - equal(typeof context.apiObj.storage, "object", - "The storage API should be initialized"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_apimanager.js b/toolkit/components/extensions/test/xpcshell/test_ext_apimanager.js deleted file mode 100644 index 3f6672a11..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_apimanager.js +++ /dev/null @@ -1,91 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"; - -Cu.import("resource://gre/modules/ExtensionCommon.jsm"); - -const { - SchemaAPIManager, -} = ExtensionCommon; - -this.unknownvar = "Some module-global var"; - -var gUniqueId = 0; - -// SchemaAPIManager's loadScript uses loadSubScript to load a script. This -// requires a local (resource://) URL. So create such a temporary URL for -// testing. -function toLocalURI(code) { - let dataUrl = `data:charset=utf-8,${encodeURIComponent(code)}`; - let uniqueResPart = `need-a-local-uri-for-subscript-loading-${++gUniqueId}`; - Services.io.getProtocolHandler("resource") - .QueryInterface(Ci.nsIResProtocolHandler) - .setSubstitution(uniqueResPart, Services.io.newURI(dataUrl, null, null)); - return `resource://${uniqueResPart}`; -} - -add_task(function* test_global_isolation() { - let manA = new SchemaAPIManager("procA"); - let manB = new SchemaAPIManager("procB"); - - // The "global" variable should be persistent and shared. - manA.loadScript(toLocalURI`global.globalVar = 1;`); - do_check_eq(manA.global.globalVar, 1); - do_check_eq(manA.global.unknownvar, undefined); - manA.loadScript(toLocalURI`global.canSeeGlobal = global.globalVar;`); - do_check_eq(manA.global.canSeeGlobal, 1); - - // Each loadScript call should have their own scope, and global is shared. - manA.loadScript(toLocalURI`this.aVar = 1; global.thisScopeVar = aVar`); - do_check_eq(manA.global.aVar, undefined); - do_check_eq(manA.global.thisScopeVar, 1); - manA.loadScript(toLocalURI`global.differentScopeVar = this.aVar;`); - do_check_eq(manA.global.differentScopeVar, undefined); - manA.loadScript(toLocalURI`global.cantSeeOtherScope = typeof aVar;`); - do_check_eq(manA.global.cantSeeOtherScope, "undefined"); - - manB.loadScript(toLocalURI`global.tryReadOtherGlobal = global.tryagain;`); - do_check_eq(manA.global.tryReadOtherGlobal, undefined); - - // Cu.import without second argument exports to the caller's global. Let's - // verify that it does not leak to the SchemaAPIManager's global. - do_check_eq(typeof ExtensionUtils, "undefined"); // Sanity check #1. - manA.loadScript(toLocalURI`global.hasExtUtils = typeof ExtensionUtils;`); - do_check_eq(manA.global.hasExtUtils, "undefined"); // Sanity check #2 - - Cu.import("resource://gre/modules/ExtensionUtils.jsm"); - do_check_eq(typeof ExtensionUtils, "object"); // Sanity check #3. - - manA.loadScript(toLocalURI`global.hasExtUtils = typeof ExtensionUtils;`); - do_check_eq(manA.global.hasExtUtils, "undefined"); - manB.loadScript(toLocalURI`global.hasExtUtils = typeof ExtensionUtils;`); - do_check_eq(manB.global.hasExtUtils, "undefined"); - - // Confirm that Cu.import does not leak between SchemaAPIManager globals. - manA.loadScript(toLocalURI` - Cu.import("resource://gre/modules/ExtensionUtils.jsm"); - global.hasExtUtils = typeof ExtensionUtils; - `); - do_check_eq(manA.global.hasExtUtils, "object"); // Sanity check. - manB.loadScript(toLocalURI`global.hasExtUtils = typeof ExtensionUtils;`); - do_check_eq(manB.global.hasExtUtils, "undefined"); - - // Prototype modifications should be isolated. - manA.loadScript(toLocalURI` - Object.prototype.modifiedByA = "Prrft"; - global.fromA = {}; - `); - manA.loadScript(toLocalURI` - global.fromAagain = {}; - `); - manB.loadScript(toLocalURI` - global.fromB = {}; - `); - do_check_eq(manA.global.modifiedByA, "Prrft"); - do_check_eq(manA.global.fromA.modifiedByA, "Prrft"); - do_check_eq(manA.global.fromAagain.modifiedByA, "Prrft"); - do_check_eq(manB.global.modifiedByA, undefined); - do_check_eq(manB.global.fromB.modifiedByA, undefined); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js deleted file mode 100644 index 26282fcb9..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js +++ /dev/null @@ -1,23 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -/* eslint-disable mozilla/balanced-listeners */ - -add_task(function* test_DOMContentLoaded_in_generated_background_page() { - let extension = ExtensionTestUtils.loadExtension({ - background() { - function reportListener(event) { - browser.test.sendMessage("eventname", event.type); - } - document.addEventListener("DOMContentLoaded", reportListener); - window.addEventListener("load", reportListener); - }, - }); - - yield extension.startup(); - equal("DOMContentLoaded", yield extension.awaitMessage("eventname")); - equal("load", yield extension.awaitMessage("eventname")); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js deleted file mode 100644 index 4bf59b798..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js +++ /dev/null @@ -1,24 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_reload_generated_background_page() { - let extension = ExtensionTestUtils.loadExtension({ - background() { - if (location.hash !== "#firstrun") { - browser.test.sendMessage("first run"); - location.hash = "#firstrun"; - browser.test.assertEq("#firstrun", location.hash); - location.reload(); - } else { - browser.test.notifyPass("second run"); - } - }, - }); - - yield extension.startup(); - yield extension.awaitMessage("first run"); - yield extension.awaitFinish("second run"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_global_history.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_global_history.js deleted file mode 100644 index 092a9f5b3..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_background_global_history.js +++ /dev/null @@ -1,22 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -Cu.import("resource://testing-common/PlacesTestUtils.jsm"); - -add_task(function* test_global_history() { - let extension = ExtensionTestUtils.loadExtension({ - background() { - browser.test.sendMessage("background-loaded", location.href); - }, - }); - - yield extension.startup(); - - let backgroundURL = yield extension.awaitMessage("background-loaded"); - - yield extension.unload(); - - let exists = yield PlacesTestUtils.isPageInDB(backgroundURL); - ok(!exists, "Background URL should not be in history database"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_private_browsing.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_private_browsing.js deleted file mode 100644 index 8e8b5e0b0..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_background_private_browsing.js +++ /dev/null @@ -1,40 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -Cu.import("resource://gre/modules/Preferences.jsm"); - -function* testBackgroundPage(expected) { - let extension = ExtensionTestUtils.loadExtension({ - async background() { - browser.test.assertEq(window, browser.extension.getBackgroundPage(), - "Caller should be able to access itself as a background page"); - browser.test.assertEq(window, await browser.runtime.getBackgroundPage(), - "Caller should be able to access itself as a background page"); - - browser.test.sendMessage("incognito", browser.extension.inIncognitoContext); - }, - }); - - yield extension.startup(); - - let incognito = yield extension.awaitMessage("incognito"); - equal(incognito, expected.incognito, "Expected incognito value"); - - yield extension.unload(); -} - -add_task(function* test_background_incognito() { - do_print("Test background page incognito value with permanent private browsing disabled"); - - yield testBackgroundPage({incognito: false}); - - do_print("Test background page incognito value with permanent private browsing enabled"); - - Preferences.set("browser.privatebrowsing.autostart", true); - do_register_cleanup(() => { - Preferences.reset("browser.privatebrowsing.autostart"); - }); - - yield testBackgroundPage({incognito: true}); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_runtime_connect_params.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_runtime_connect_params.js deleted file mode 100644 index 426833edd..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_background_runtime_connect_params.js +++ /dev/null @@ -1,72 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -function backgroundScript() { - let received_ports_number = 0; - - const expected_received_ports_number = 1; - - function countReceivedPorts(port) { - received_ports_number++; - - if (port.name == "check-results") { - browser.runtime.onConnect.removeListener(countReceivedPorts); - - browser.test.assertEq(expected_received_ports_number, received_ports_number, "invalid connect should not create a port"); - - browser.test.notifyPass("runtime.connect invalid params"); - } - } - - browser.runtime.onConnect.addListener(countReceivedPorts); - - let childFrame = document.createElement("iframe"); - childFrame.src = "extensionpage.html"; - document.body.appendChild(childFrame); -} - -function senderScript() { - let detected_invalid_connect_params = 0; - - const invalid_connect_params = [ - // too many params - ["fake-extensions-id", {name: "fake-conn-name"}, "unexpected third params"], - // invalid params format - [{}, {}], - ["fake-extensions-id", "invalid-connect-info-format"], - ]; - const expected_detected_invalid_connect_params = invalid_connect_params.length; - - function assertInvalidConnectParamsException(params) { - try { - browser.runtime.connect(...params); - } catch (e) { - detected_invalid_connect_params++; - browser.test.assertTrue(e.toString().indexOf("Incorrect argument types for runtime.connect.") >= 0, "exception message is correct"); - } - } - for (let params of invalid_connect_params) { - assertInvalidConnectParamsException(params); - } - browser.test.assertEq(expected_detected_invalid_connect_params, detected_invalid_connect_params, "all invalid runtime.connect params detected"); - - browser.runtime.connect(browser.runtime.id, {name: "check-results"}); -} - -let extensionData = { - background: backgroundScript, - files: { - "senderScript.js": senderScript, - "extensionpage.html": ``, - }, -}; - -add_task(function* test_backgroundRuntimeConnectParams() { - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - - yield extension.awaitFinish("runtime.connect invalid params"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js deleted file mode 100644 index c5f2f1332..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js +++ /dev/null @@ -1,45 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* testBackgroundWindow() { - let extension = ExtensionTestUtils.loadExtension({ - background() { - browser.test.log("background script executed"); - - browser.test.sendMessage("background-script-load"); - - let img = document.createElement("img"); - img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; - document.body.appendChild(img); - - img.onload = () => { - browser.test.log("image loaded"); - - let iframe = document.createElement("iframe"); - iframe.src = "about:blank?1"; - - iframe.onload = () => { - browser.test.log("iframe loaded"); - setTimeout(() => { - browser.test.notifyPass("background sub-window test done"); - }, 0); - }; - document.body.appendChild(iframe); - }; - }, - }); - - let loadCount = 0; - extension.onMessage("background-script-load", () => { - loadCount++; - }); - - yield extension.startup(); - - yield extension.awaitFinish("background sub-window test done"); - - equal(loadCount, 1, "background script loaded only once"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js deleted file mode 100644 index 948e2913e..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js +++ /dev/null @@ -1,34 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* testBackgroundWindowProperties() { - let extension = ExtensionTestUtils.loadExtension({ - background() { - let expectedValues = { - screenX: 0, - screenY: 0, - outerWidth: 0, - outerHeight: 0, - }; - - for (let k in window) { - try { - if (k in expectedValues) { - browser.test.assertEq(expectedValues[k], window[k], - `should return the expected value for window property: ${k}`); - } else { - void window[k]; - } - } catch (e) { - browser.test.assertEq(null, e, `unexpected exception accessing window property: ${k}`); - } - } - - browser.test.notifyPass("background.testWindowProperties.done"); - }, - }); - yield extension.startup(); - yield extension.awaitFinish("background.testWindowProperties.done"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js deleted file mode 100644 index 56a14e189..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js +++ /dev/null @@ -1,190 +0,0 @@ -"use strict"; - -const global = this; - -Cu.import("resource://gre/modules/Timer.jsm"); - -Cu.import("resource://gre/modules/ExtensionCommon.jsm"); -Cu.import("resource://gre/modules/ExtensionUtils.jsm"); - -var { - BaseContext, -} = ExtensionCommon; - -var { - EventManager, - SingletonEventManager, -} = ExtensionUtils; - -class StubContext extends BaseContext { - constructor() { - let fakeExtension = {id: "test@web.extension"}; - super("testEnv", fakeExtension); - this.sandbox = Cu.Sandbox(global); - } - - get cloneScope() { - return this.sandbox; - } -} - - -add_task(function* test_post_unload_promises() { - let context = new StubContext(); - - let fail = result => { - ok(false, `Unexpected callback: ${result}`); - }; - - // Make sure promises resolve normally prior to unload. - let promises = [ - context.wrapPromise(Promise.resolve()), - context.wrapPromise(Promise.reject({message: ""})).catch(() => {}), - ]; - - yield Promise.all(promises); - - // Make sure promises that resolve after unload do not trigger - // resolution handlers. - - context.wrapPromise(Promise.resolve("resolved")) - .then(fail); - - context.wrapPromise(Promise.reject({message: "rejected"})) - .then(fail, fail); - - context.unload(); - - // The `setTimeout` ensures that we return to the event loop after - // promise resolution, which means we're guaranteed to return after - // any micro-tasks that get enqueued by the resolution handlers above. - yield new Promise(resolve => setTimeout(resolve, 0)); -}); - - -add_task(function* test_post_unload_listeners() { - let context = new StubContext(); - - let fireEvent; - let onEvent = new EventManager(context, "onEvent", fire => { - fireEvent = fire; - return () => {}; - }); - - let fireSingleton; - let onSingleton = new SingletonEventManager(context, "onSingleton", callback => { - fireSingleton = () => { - Promise.resolve().then(callback); - }; - return () => {}; - }); - - let fail = event => { - ok(false, `Unexpected event: ${event}`); - }; - - // Check that event listeners aren't called after they've been removed. - onEvent.addListener(fail); - onSingleton.addListener(fail); - - let promises = [ - new Promise(resolve => onEvent.addListener(resolve)), - new Promise(resolve => onSingleton.addListener(resolve)), - ]; - - fireEvent("onEvent"); - fireSingleton("onSingleton"); - - // Both `fireEvent` calls are dispatched asynchronously, so they won't - // have fired by this point. The `fail` listeners that we remove now - // should not be called, even though the events have already been - // enqueued. - onEvent.removeListener(fail); - onSingleton.removeListener(fail); - - // Wait for the remaining listeners to be called, which should always - // happen after the `fail` listeners would normally be called. - yield Promise.all(promises); - - // Check that event listeners aren't called after the context has - // unloaded. - onEvent.addListener(fail); - onSingleton.addListener(fail); - - // The EventManager `fire` callback always dispatches events - // asynchronously, so we need to test that any pending event callbacks - // aren't fired after the context unloads. We also need to test that - // any `fire` calls that happen *after* the context is unloaded also - // do not trigger callbacks. - fireEvent("onEvent"); - Promise.resolve("onEvent").then(fireEvent); - - fireSingleton("onSingleton"); - Promise.resolve("onSingleton").then(fireSingleton); - - context.unload(); - - // The `setTimeout` ensures that we return to the event loop after - // promise resolution, which means we're guaranteed to return after - // any micro-tasks that get enqueued by the resolution handlers above. - yield new Promise(resolve => setTimeout(resolve, 0)); -}); - -class Context extends BaseContext { - constructor(principal) { - let fakeExtension = {id: "test@web.extension"}; - super("testEnv", fakeExtension); - Object.defineProperty(this, "principal", { - value: principal, - configurable: true, - }); - this.sandbox = Cu.Sandbox(principal, {wantXrays: false}); - } - - get cloneScope() { - return this.sandbox; - } -} - -let ssm = Services.scriptSecurityManager; -const PRINCIPAL1 = ssm.createCodebasePrincipalFromOrigin("http://www.example.org"); -const PRINCIPAL2 = ssm.createCodebasePrincipalFromOrigin("http://www.somethingelse.org"); - -// Test that toJSON() works in the json sandbox -add_task(function* test_stringify_toJSON() { - let context = new Context(PRINCIPAL1); - let obj = Cu.evalInSandbox("({hidden: true, toJSON() { return {visible: true}; } })", context.sandbox); - - let stringified = context.jsonStringify(obj); - let expected = JSON.stringify({visible: true}); - equal(stringified, expected, "Stringified object with toJSON() method is as expected"); -}); - -// Test that stringifying in inaccessible property throws -add_task(function* test_stringify_inaccessible() { - let context = new Context(PRINCIPAL1); - let sandbox = context.sandbox; - let sandbox2 = Cu.Sandbox(PRINCIPAL2); - - Cu.waiveXrays(sandbox).subobj = Cu.evalInSandbox("({ subobject: true })", sandbox2); - let obj = Cu.evalInSandbox("({ local: true, nested: subobj })", sandbox); - Assert.throws(() => { - context.jsonStringify(obj); - }); -}); - -add_task(function* test_stringify_accessible() { - // Test that an accessible property from another global is included - let principal = Cu.getObjectPrincipal(Cu.Sandbox([PRINCIPAL1, PRINCIPAL2])); - let context = new Context(principal); - let sandbox = context.sandbox; - let sandbox2 = Cu.Sandbox(PRINCIPAL2); - - Cu.waiveXrays(sandbox).subobj = Cu.evalInSandbox("({ subobject: true })", sandbox2); - let obj = Cu.evalInSandbox("({ local: true, nested: subobj })", sandbox); - let stringified = context.jsonStringify(obj); - - let expected = JSON.stringify({local: true, nested: {subobject: true}}); - equal(stringified, expected, "Stringified object with accessible property is as expected"); -}); - diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads.js deleted file mode 100644 index 058b9b18c..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads.js +++ /dev/null @@ -1,76 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_downloads_api_namespace_and_permissions() { - function backgroundScript() { - browser.test.assertTrue(!!browser.downloads, "`downloads` API is present."); - browser.test.assertTrue(!!browser.downloads.FilenameConflictAction, - "`downloads.FilenameConflictAction` enum is present."); - browser.test.assertTrue(!!browser.downloads.InterruptReason, - "`downloads.InterruptReason` enum is present."); - browser.test.assertTrue(!!browser.downloads.DangerType, - "`downloads.DangerType` enum is present."); - browser.test.assertTrue(!!browser.downloads.State, - "`downloads.State` enum is present."); - browser.test.notifyPass("downloads tests"); - } - - let extensionData = { - background: backgroundScript, - manifest: { - permissions: ["downloads", "downloads.open", "downloads.shelf"], - }, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - yield extension.awaitFinish("downloads tests"); - yield extension.unload(); -}); - -add_task(function* test_downloads_open_permission() { - function backgroundScript() { - browser.test.assertFalse("open" in browser.downloads, - "`downloads.open` permission is required."); - browser.test.notifyPass("downloads tests"); - } - - let extensionData = { - background: backgroundScript, - manifest: { - permissions: ["downloads"], - }, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - yield extension.awaitFinish("downloads tests"); - yield extension.unload(); -}); - -add_task(function* test_downloads_open() { - async function backgroundScript() { - await browser.test.assertRejects( - browser.downloads.open(10), - "Invalid download id 10", - "The error is informative."); - - browser.test.notifyPass("downloads tests"); - - // TODO: Once downloads.{pause,cancel,resume} lands (bug 1245602) test that this gives a good - // error when called with an incompleted download. - } - - let extensionData = { - background: backgroundScript, - manifest: { - permissions: ["downloads", "downloads.open"], - }, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - yield extension.awaitFinish("downloads tests"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js deleted file mode 100644 index 37ddd4d7c..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js +++ /dev/null @@ -1,354 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -/* global OS */ - -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Downloads.jsm"); - -const gServer = createHttpServer(); -gServer.registerDirectory("/data/", do_get_file("data")); - -const WINDOWS = AppConstants.platform == "win"; - -const BASE = `http://localhost:${gServer.identity.primaryPort}/data`; -const FILE_NAME = "file_download.txt"; -const FILE_URL = BASE + "/" + FILE_NAME; -const FILE_NAME_UNIQUE = "file_download(1).txt"; -const FILE_LEN = 46; - -let downloadDir; - -function setup() { - downloadDir = FileUtils.getDir("TmpD", ["downloads"]); - downloadDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - do_print(`Using download directory ${downloadDir.path}`); - - Services.prefs.setIntPref("browser.download.folderList", 2); - Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, downloadDir); - - do_register_cleanup(() => { - Services.prefs.clearUserPref("browser.download.folderList"); - Services.prefs.clearUserPref("browser.download.dir"); - - let entries = downloadDir.directoryEntries; - while (entries.hasMoreElements()) { - let entry = entries.getNext().QueryInterface(Ci.nsIFile); - ok(false, `Leftover file ${entry.path} in download directory`); - entry.remove(false); - } - - downloadDir.remove(false); - }); -} - -function backgroundScript() { - let blobUrl; - browser.test.onMessage.addListener(async (msg, ...args) => { - if (msg == "download.request") { - let options = args[0]; - - if (options.blobme) { - let blob = new Blob(options.blobme); - delete options.blobme; - blobUrl = options.url = window.URL.createObjectURL(blob); - } - - try { - let id = await browser.downloads.download(options); - browser.test.sendMessage("download.done", {status: "success", id}); - } catch (error) { - browser.test.sendMessage("download.done", {status: "error", errmsg: error.message}); - } - } else if (msg == "killTheBlob") { - window.URL.revokeObjectURL(blobUrl); - blobUrl = null; - } - }); - - browser.test.sendMessage("ready"); -} - -// This function is a bit of a sledgehammer, it looks at every download -// the browser knows about and waits for all active downloads to complete. -// But we only start one at a time and only do a handful in total, so -// this lets us test download() without depending on anything else. -async function waitForDownloads() { - let list = await Downloads.getList(Downloads.ALL); - let downloads = await list.getAll(); - - let inprogress = downloads.filter(dl => !dl.stopped); - return Promise.all(inprogress.map(dl => dl.whenSucceeded())); -} - -// Create a file in the downloads directory. -function touch(filename) { - let file = downloadDir.clone(); - file.append(filename); - file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); -} - -// Remove a file in the downloads directory. -function remove(filename, recursive = false) { - let file = downloadDir.clone(); - file.append(filename); - file.remove(recursive); -} - -add_task(function* test_downloads() { - setup(); - - let extension = ExtensionTestUtils.loadExtension({ - background: `(${backgroundScript})()`, - manifest: { - permissions: ["downloads"], - }, - }); - - function download(options) { - extension.sendMessage("download.request", options); - return extension.awaitMessage("download.done"); - } - - async function testDownload(options, localFile, expectedSize, description) { - let msg = await download(options); - equal(msg.status, "success", `downloads.download() works with ${description}`); - - await waitForDownloads(); - - let localPath = downloadDir.clone(); - let parts = Array.isArray(localFile) ? localFile : [localFile]; - - parts.map(p => localPath.append(p)); - equal(localPath.fileSize, expectedSize, "Downloaded file has expected size"); - localPath.remove(false); - } - - yield extension.startup(); - yield extension.awaitMessage("ready"); - do_print("extension started"); - - // Call download() with just the url property. - yield testDownload({url: FILE_URL}, FILE_NAME, FILE_LEN, "just source"); - - // Call download() with a filename property. - yield testDownload({ - url: FILE_URL, - filename: "newpath.txt", - }, "newpath.txt", FILE_LEN, "source and filename"); - - // Call download() with a filename with subdirs. - yield testDownload({ - url: FILE_URL, - filename: "sub/dir/file", - }, ["sub", "dir", "file"], FILE_LEN, "source and filename with subdirs"); - - // Call download() with a filename with existing subdirs. - yield testDownload({ - url: FILE_URL, - filename: "sub/dir/file2", - }, ["sub", "dir", "file2"], FILE_LEN, "source and filename with existing subdirs"); - - // Only run Windows path separator test on Windows. - if (WINDOWS) { - // Call download() with a filename with Windows path separator. - yield testDownload({ - url: FILE_URL, - filename: "sub\\dir\\file3", - }, ["sub", "dir", "file3"], FILE_LEN, "filename with Windows path separator"); - } - remove("sub", true); - - // Call download(), filename with subdir, skipping parts. - yield testDownload({ - url: FILE_URL, - filename: "skip//part", - }, ["skip", "part"], FILE_LEN, "source, filename, with subdir, skipping parts"); - remove("skip", true); - - // Check conflictAction of "uniquify". - touch(FILE_NAME); - yield testDownload({ - url: FILE_URL, - conflictAction: "uniquify", - }, FILE_NAME_UNIQUE, FILE_LEN, "conflictAction=uniquify"); - // todo check that preexisting file was not modified? - remove(FILE_NAME); - - // Check conflictAction of "overwrite". - touch(FILE_NAME); - yield testDownload({ - url: FILE_URL, - conflictAction: "overwrite", - }, FILE_NAME, FILE_LEN, "conflictAction=overwrite"); - - // Try to download in invalid url - yield download({url: "this is not a valid URL"}).then(msg => { - equal(msg.status, "error", "downloads.download() fails with invalid url"); - ok(/not a valid URL/.test(msg.errmsg), "error message for invalid url is correct"); - }); - - // Try to download to an empty path. - yield download({ - url: FILE_URL, - filename: "", - }).then(msg => { - equal(msg.status, "error", "downloads.download() fails with empty filename"); - equal(msg.errmsg, "filename must not be empty", "error message for empty filename is correct"); - }); - - // Try to download to an absolute path. - const absolutePath = OS.Path.join(WINDOWS ? "\\tmp" : "/tmp", "file_download.txt"); - yield download({ - url: FILE_URL, - filename: absolutePath, - }).then(msg => { - equal(msg.status, "error", "downloads.download() fails with absolute filename"); - equal(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`); - }); - - if (WINDOWS) { - yield download({ - url: FILE_URL, - filename: "C:\\file_download.txt", - }).then(msg => { - equal(msg.status, "error", "downloads.download() fails with absolute filename"); - equal(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct"); - }); - } - - // Try to download to a relative path containing .. - yield download({ - url: FILE_URL, - filename: OS.Path.join("..", "file_download.txt"), - }).then(msg => { - equal(msg.status, "error", "downloads.download() fails with back-references"); - equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct"); - }); - - // Try to download to a long relative path containing .. - yield download({ - url: FILE_URL, - filename: OS.Path.join("foo", "..", "..", "file_download.txt"), - }).then(msg => { - equal(msg.status, "error", "downloads.download() fails with back-references"); - equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct"); - }); - - // Try to download a blob url - const BLOB_STRING = "Hello, world"; - yield testDownload({ - blobme: [BLOB_STRING], - filename: FILE_NAME, - }, FILE_NAME, BLOB_STRING.length, "blob url"); - extension.sendMessage("killTheBlob"); - - // Try to download a blob url without a given filename - yield testDownload({ - blobme: [BLOB_STRING], - }, "download", BLOB_STRING.length, "blob url with no filename"); - extension.sendMessage("killTheBlob"); - - yield extension.unload(); -}); - -add_task(function* test_download_post() { - const server = createHttpServer(); - const url = `http://localhost:${server.identity.primaryPort}/post-log`; - - let received; - server.registerPathHandler("/post-log", request => { - received = request; - }); - - // Confirm received vs. expected values. - function confirm(method, headers = {}, body) { - equal(received.method, method, "method is correct"); - - for (let name in headers) { - ok(received.hasHeader(name), `header ${name} received`); - equal(received.getHeader(name), headers[name], `header ${name} is correct`); - } - - if (body) { - const str = NetUtil.readInputStreamToString(received.bodyInputStream, - received.bodyInputStream.available()); - equal(str, body, "body is correct"); - } - } - - function background() { - browser.test.onMessage.addListener(async options => { - try { - await browser.downloads.download(options); - } catch (err) { - browser.test.sendMessage("done", {err: err.message}); - } - }); - browser.downloads.onChanged.addListener(({state}) => { - if (state && state.current === "complete") { - browser.test.sendMessage("done", {ok: true}); - } - }); - } - - const manifest = {permissions: ["downloads"]}; - const extension = ExtensionTestUtils.loadExtension({background, manifest}); - yield extension.startup(); - - function download(options) { - options.url = url; - options.conflictAction = "overwrite"; - - extension.sendMessage(options); - return extension.awaitMessage("done"); - } - - // Test method option. - let result = yield download({}); - ok(result.ok, "download works without the method option, defaults to GET"); - confirm("GET"); - - result = yield download({method: "PUT"}); - ok(!result.ok, "download rejected with PUT method"); - ok(/method: Invalid enumeration/.test(result.err), "descriptive error message"); - - result = yield download({method: "POST"}); - ok(result.ok, "download works with POST method"); - confirm("POST"); - - // Test body option values. - result = yield download({body: []}); - ok(!result.ok, "download rejected because of non-string body"); - ok(/body: Expected string/.test(result.err), "descriptive error message"); - - result = yield download({method: "POST", body: "of work"}); - ok(result.ok, "download works with POST method and body"); - confirm("POST", {"Content-Length": 7}, "of work"); - - // Test custom headers. - result = yield download({headers: [{name: "X-Custom"}]}); - ok(!result.ok, "download rejected because of missing header value"); - ok(/"value" is required/.test(result.err), "descriptive error message"); - - result = yield download({headers: [{name: "X-Custom", value: "13"}]}); - ok(result.ok, "download works with a custom header"); - confirm("GET", {"X-Custom": "13"}); - - // Test forbidden headers. - result = yield download({headers: [{name: "DNT", value: "1"}]}); - ok(!result.ok, "download rejected because of forbidden header name DNT"); - ok(/Forbidden request header/.test(result.err), "descriptive error message"); - - result = yield download({headers: [{name: "Proxy-Connection", value: "keep"}]}); - ok(!result.ok, "download rejected because of forbidden header name prefix Proxy-"); - ok(/Forbidden request header/.test(result.err), "descriptive error message"); - - result = yield download({headers: [{name: "Sec-ret", value: "13"}]}); - ok(!result.ok, "download rejected because of forbidden header name prefix Sec-"); - ok(/Forbidden request header/.test(result.err), "descriptive error message"); - - remove("post-log"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js deleted file mode 100644 index d08aab666..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js +++ /dev/null @@ -1,862 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -Cu.import("resource://gre/modules/Downloads.jsm"); - -const server = createHttpServer(); -server.registerDirectory("/data/", do_get_file("data")); - -const ROOT = `http://localhost:${server.identity.primaryPort}`; -const BASE = `${ROOT}/data`; -const TXT_FILE = "file_download.txt"; -const TXT_URL = BASE + "/" + TXT_FILE; - -// Keep these in sync with code in interruptible.sjs -const INT_PARTIAL_LEN = 15; -const INT_TOTAL_LEN = 31; - -const TEST_DATA = "This is 31 bytes of sample data"; -const TOTAL_LEN = TEST_DATA.length; -const PARTIAL_LEN = 15; - -// A handler to let us systematically test pausing/resuming/canceling -// of downloads. This target represents a small text file but a simple -// GET will stall after sending part of the data, to give the test code -// a chance to pause or do other operations on an in-progress download. -// A resumed download (ie, a GET with a Range: header) will allow the -// download to complete. -function handleRequest(request, response) { - response.setHeader("Content-Type", "text/plain", false); - - if (request.hasHeader("Range")) { - let start, end; - let matches = request.getHeader("Range") - .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/); - if (matches != null) { - start = matches[1] ? parseInt(matches[1], 10) : 0; - end = matches[2] ? parseInt(matches[2], 10) : (TOTAL_LEN - 1); - } - - if (end == undefined || end >= TOTAL_LEN) { - response.setStatusLine(request.httpVersion, 416, "Requested Range Not Satisfiable"); - response.setHeader("Content-Range", `*/${TOTAL_LEN}`, false); - response.finish(); - return; - } - - response.setStatusLine(request.httpVersion, 206, "Partial Content"); - response.setHeader("Content-Range", `${start}-${end}/${TOTAL_LEN}`, false); - response.write(TEST_DATA.slice(start, end + 1)); - } else { - response.processAsync(); - response.setHeader("Content-Length", `${TOTAL_LEN}`, false); - response.write(TEST_DATA.slice(0, PARTIAL_LEN)); - } - - do_register_cleanup(() => { - try { - response.finish(); - } catch (e) { - // This will throw, but we don't care at this point. - } - }); -} - -server.registerPathHandler("/interruptible.html", handleRequest); - -let interruptibleCount = 0; -function getInterruptibleUrl() { - let n = interruptibleCount++; - return `${ROOT}/interruptible.html?count=${n}`; -} - -function backgroundScript() { - let events = new Set(); - let eventWaiter = null; - - browser.downloads.onCreated.addListener(data => { - events.add({type: "onCreated", data}); - if (eventWaiter) { - eventWaiter(); - } - }); - - browser.downloads.onChanged.addListener(data => { - events.add({type: "onChanged", data}); - if (eventWaiter) { - eventWaiter(); - } - }); - - browser.downloads.onErased.addListener(data => { - events.add({type: "onErased", data}); - if (eventWaiter) { - eventWaiter(); - } - }); - - // Returns a promise that will resolve when the given list of expected - // events have all been seen. By default, succeeds only if the exact list - // of expected events is seen in the given order. options.exact can be - // set to false to allow other events and options.inorder can be set to - // false to allow the events to arrive in any order. - function waitForEvents(expected, options = {}) { - function compare(a, b) { - if (typeof b == "object" && b != null) { - if (typeof a != "object") { - return false; - } - return Object.keys(b).every(fld => compare(a[fld], b[fld])); - } - return (a == b); - } - - const exact = ("exact" in options) ? options.exact : true; - const inorder = ("inorder" in options) ? options.inorder : true; - return new Promise((resolve, reject) => { - function check() { - function fail(msg) { - browser.test.fail(msg); - reject(new Error(msg)); - } - if (events.size < expected.length) { - return; - } - if (exact && expected.length < events.size) { - fail(`Got ${events.size} events but only expected ${expected.length}`); - return; - } - - let remaining = new Set(events); - if (inorder) { - for (let event of events) { - if (compare(event, expected[0])) { - expected.shift(); - remaining.delete(event); - } - } - } else { - expected = expected.filter(val => { - for (let remainingEvent of remaining) { - if (compare(remainingEvent, val)) { - remaining.delete(remainingEvent); - return false; - } - } - return true; - }); - } - - // Events that did occur have been removed from expected so if - // expected is empty, we're done. If we didn't see all the - // expected events and we're not looking for an exact match, - // then we just may not have seen the event yet, so return without - // failing and check() will be called again when a new event arrives. - if (expected.length == 0) { - events = remaining; - eventWaiter = null; - resolve(); - } else if (exact) { - fail(`Mismatched event: expecting ${JSON.stringify(expected[0])} but got ${JSON.stringify(Array.from(remaining)[0])}`); - } - } - eventWaiter = check; - check(); - }); - } - - browser.test.onMessage.addListener(async (msg, ...args) => { - let match = msg.match(/(\w+).request$/); - if (!match) { - return; - } - - let what = match[1]; - if (what == "waitForEvents") { - try { - await waitForEvents(...args); - browser.test.sendMessage("waitForEvents.done", {status: "success"}); - } catch (error) { - browser.test.sendMessage("waitForEvents.done", {status: "error", errmsg: error.message}); - } - } else if (what == "clearEvents") { - events = new Set(); - browser.test.sendMessage("clearEvents.done", {status: "success"}); - } else { - try { - let result = await browser.downloads[what](...args); - browser.test.sendMessage(`${what}.done`, {status: "success", result}); - } catch (error) { - browser.test.sendMessage(`${what}.done`, {status: "error", errmsg: error.message}); - } - } - }); - - browser.test.sendMessage("ready"); -} - -let downloadDir; -let extension; - -async function clearDownloads(callback) { - let list = await Downloads.getList(Downloads.ALL); - let downloads = await list.getAll(); - - await Promise.all(downloads.map(download => list.remove(download))); - - return downloads; -} - -function runInExtension(what, ...args) { - extension.sendMessage(`${what}.request`, ...args); - return extension.awaitMessage(`${what}.done`); -} - -// This is pretty simplistic, it looks for a progress update for a -// download of the given url in which the total bytes are exactly equal -// to the given value. Unless you know exactly how data will arrive from -// the server (eg see interruptible.sjs), it probably isn't very useful. -async function waitForProgress(url, bytes) { - let list = await Downloads.getList(Downloads.ALL); - - return new Promise(resolve => { - const view = { - onDownloadChanged(download) { - if (download.source.url == url && download.currentBytes == bytes) { - list.removeView(view); - resolve(); - } - }, - }; - list.addView(view); - }); -} - -add_task(function* setup() { - const nsIFile = Ci.nsIFile; - downloadDir = FileUtils.getDir("TmpD", ["downloads"]); - downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - do_print(`downloadDir ${downloadDir.path}`); - - Services.prefs.setIntPref("browser.download.folderList", 2); - Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir); - - do_register_cleanup(() => { - Services.prefs.clearUserPref("browser.download.folderList"); - Services.prefs.clearUserPref("browser.download.dir"); - downloadDir.remove(true); - - return clearDownloads(); - }); - - yield clearDownloads().then(downloads => { - do_print(`removed ${downloads.length} pre-existing downloads from history`); - }); - - extension = ExtensionTestUtils.loadExtension({ - background: backgroundScript, - manifest: { - permissions: ["downloads"], - }, - }); - - yield extension.startup(); - yield extension.awaitMessage("ready"); -}); - -add_task(function* test_events() { - let msg = yield runInExtension("download", {url: TXT_URL}); - equal(msg.status, "success", "download() succeeded"); - const id = msg.result; - - msg = yield runInExtension("waitForEvents", [ - {type: "onCreated", data: {id, url: TXT_URL}}, - { - type: "onChanged", - data: { - id, - state: { - previous: "in_progress", - current: "complete", - }, - }, - }, - ]); - equal(msg.status, "success", "got onCreated and onChanged events"); -}); - -add_task(function* test_cancel() { - let url = getInterruptibleUrl(); - do_print(url); - let msg = yield runInExtension("download", {url}); - equal(msg.status, "success", "download() succeeded"); - const id = msg.result; - - let progressPromise = waitForProgress(url, INT_PARTIAL_LEN); - - msg = yield runInExtension("waitForEvents", [ - {type: "onCreated", data: {id}}, - ]); - equal(msg.status, "success", "got created and changed events"); - - yield progressPromise; - do_print(`download reached ${INT_PARTIAL_LEN} bytes`); - - msg = yield runInExtension("cancel", id); - equal(msg.status, "success", "cancel() succeeded"); - - // This sequence of events is bogus (bug 1256243) - msg = yield runInExtension("waitForEvents", [ - { - type: "onChanged", - data: { - state: { - previous: "in_progress", - current: "interrupted", - }, - paused: { - previous: false, - current: true, - }, - }, - }, { - type: "onChanged", - data: { - id, - error: { - previous: null, - current: "USER_CANCELED", - }, - }, - }, { - type: "onChanged", - data: { - id, - paused: { - previous: true, - current: false, - }, - }, - }, - ]); - equal(msg.status, "success", "got onChanged events corresponding to cancel()"); - - msg = yield runInExtension("search", {error: "USER_CANCELED"}); - equal(msg.status, "success", "search() succeeded"); - equal(msg.result.length, 1, "search() found 1 download"); - equal(msg.result[0].id, id, "download.id is correct"); - equal(msg.result[0].state, "interrupted", "download.state is correct"); - equal(msg.result[0].paused, false, "download.paused is correct"); - equal(msg.result[0].canResume, false, "download.canResume is correct"); - equal(msg.result[0].error, "USER_CANCELED", "download.error is correct"); - equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); - equal(msg.result[0].exists, false, "download.exists is correct"); - - msg = yield runInExtension("pause", id); - equal(msg.status, "error", "cannot pause a canceled download"); - - msg = yield runInExtension("resume", id); - equal(msg.status, "error", "cannot resume a canceled download"); -}); - -add_task(function* test_pauseresume() { - let url = getInterruptibleUrl(); - let msg = yield runInExtension("download", {url}); - equal(msg.status, "success", "download() succeeded"); - const id = msg.result; - - let progressPromise = waitForProgress(url, INT_PARTIAL_LEN); - - msg = yield runInExtension("waitForEvents", [ - {type: "onCreated", data: {id}}, - ]); - equal(msg.status, "success", "got created and changed events"); - - yield progressPromise; - do_print(`download reached ${INT_PARTIAL_LEN} bytes`); - - msg = yield runInExtension("pause", id); - equal(msg.status, "success", "pause() succeeded"); - - msg = yield runInExtension("waitForEvents", [ - { - type: "onChanged", - data: { - id, - state: { - previous: "in_progress", - current: "interrupted", - }, - paused: { - previous: false, - current: true, - }, - canResume: { - previous: false, - current: true, - }, - }, - }, { - type: "onChanged", - data: { - id, - error: { - previous: null, - current: "USER_CANCELED", - }, - }, - }]); - equal(msg.status, "success", "got onChanged event corresponding to pause"); - - msg = yield runInExtension("search", {paused: true}); - equal(msg.status, "success", "search() succeeded"); - equal(msg.result.length, 1, "search() found 1 download"); - equal(msg.result[0].id, id, "download.id is correct"); - equal(msg.result[0].state, "interrupted", "download.state is correct"); - equal(msg.result[0].paused, true, "download.paused is correct"); - equal(msg.result[0].canResume, true, "download.canResume is correct"); - equal(msg.result[0].error, "USER_CANCELED", "download.error is correct"); - equal(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct"); - equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); - equal(msg.result[0].exists, false, "download.exists is correct"); - - msg = yield runInExtension("search", {error: "USER_CANCELED"}); - equal(msg.status, "success", "search() succeeded"); - let found = msg.result.filter(item => item.id == id); - equal(found.length, 1, "search() by error found the paused download"); - - msg = yield runInExtension("pause", id); - equal(msg.status, "error", "cannot pause an already paused download"); - - msg = yield runInExtension("resume", id); - equal(msg.status, "success", "resume() succeeded"); - - msg = yield runInExtension("waitForEvents", [ - { - type: "onChanged", - data: { - id, - state: { - previous: "interrupted", - current: "in_progress", - }, - paused: { - previous: true, - current: false, - }, - canResume: { - previous: true, - current: false, - }, - error: { - previous: "USER_CANCELED", - current: null, - }, - }, - }, - { - type: "onChanged", - data: { - id, - state: { - previous: "in_progress", - current: "complete", - }, - }, - }, - ]); - equal(msg.status, "success", "got onChanged events for resume and complete"); - - msg = yield runInExtension("search", {id}); - equal(msg.status, "success", "search() succeeded"); - equal(msg.result.length, 1, "search() found 1 download"); - equal(msg.result[0].state, "complete", "download.state is correct"); - equal(msg.result[0].paused, false, "download.paused is correct"); - equal(msg.result[0].canResume, false, "download.canResume is correct"); - equal(msg.result[0].error, null, "download.error is correct"); - equal(msg.result[0].bytesReceived, INT_TOTAL_LEN, "download.bytesReceived is correct"); - equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); - equal(msg.result[0].exists, true, "download.exists is correct"); - - msg = yield runInExtension("pause", id); - equal(msg.status, "error", "cannot pause a completed download"); - - msg = yield runInExtension("resume", id); - equal(msg.status, "error", "cannot resume a completed download"); -}); - -add_task(function* test_pausecancel() { - let url = getInterruptibleUrl(); - let msg = yield runInExtension("download", {url}); - equal(msg.status, "success", "download() succeeded"); - const id = msg.result; - - let progressPromise = waitForProgress(url, INT_PARTIAL_LEN); - - msg = yield runInExtension("waitForEvents", [ - {type: "onCreated", data: {id}}, - ]); - equal(msg.status, "success", "got created and changed events"); - - yield progressPromise; - do_print(`download reached ${INT_PARTIAL_LEN} bytes`); - - msg = yield runInExtension("pause", id); - equal(msg.status, "success", "pause() succeeded"); - - msg = yield runInExtension("waitForEvents", [ - { - type: "onChanged", - data: { - id, - state: { - previous: "in_progress", - current: "interrupted", - }, - paused: { - previous: false, - current: true, - }, - canResume: { - previous: false, - current: true, - }, - }, - }, { - type: "onChanged", - data: { - id, - error: { - previous: null, - current: "USER_CANCELED", - }, - }, - }]); - equal(msg.status, "success", "got onChanged event corresponding to pause"); - - msg = yield runInExtension("search", {paused: true}); - equal(msg.status, "success", "search() succeeded"); - equal(msg.result.length, 1, "search() found 1 download"); - equal(msg.result[0].id, id, "download.id is correct"); - equal(msg.result[0].state, "interrupted", "download.state is correct"); - equal(msg.result[0].paused, true, "download.paused is correct"); - equal(msg.result[0].canResume, true, "download.canResume is correct"); - equal(msg.result[0].error, "USER_CANCELED", "download.error is correct"); - equal(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct"); - equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); - equal(msg.result[0].exists, false, "download.exists is correct"); - - msg = yield runInExtension("search", {error: "USER_CANCELED"}); - equal(msg.status, "success", "search() succeeded"); - let found = msg.result.filter(item => item.id == id); - equal(found.length, 1, "search() by error found the paused download"); - - msg = yield runInExtension("cancel", id); - equal(msg.status, "success", "cancel() succeeded"); - - msg = yield runInExtension("waitForEvents", [ - { - type: "onChanged", - data: { - id, - paused: { - previous: true, - current: false, - }, - canResume: { - previous: true, - current: false, - }, - }, - }, - ]); - equal(msg.status, "success", "got onChanged event for cancel"); - - msg = yield runInExtension("search", {id}); - equal(msg.status, "success", "search() succeeded"); - equal(msg.result.length, 1, "search() found 1 download"); - equal(msg.result[0].state, "interrupted", "download.state is correct"); - equal(msg.result[0].paused, false, "download.paused is correct"); - equal(msg.result[0].canResume, false, "download.canResume is correct"); - equal(msg.result[0].error, "USER_CANCELED", "download.error is correct"); - equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); - equal(msg.result[0].exists, false, "download.exists is correct"); -}); - -add_task(function* test_pause_resume_cancel_badargs() { - let BAD_ID = 1000; - - let msg = yield runInExtension("pause", BAD_ID); - equal(msg.status, "error", "pause() failed with a bad download id"); - ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive"); - - msg = yield runInExtension("resume", BAD_ID); - equal(msg.status, "error", "resume() failed with a bad download id"); - ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive"); - - msg = yield runInExtension("cancel", BAD_ID); - equal(msg.status, "error", "cancel() failed with a bad download id"); - ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive"); -}); - -add_task(function* test_file_removal() { - let msg = yield runInExtension("download", {url: TXT_URL}); - equal(msg.status, "success", "download() succeeded"); - const id = msg.result; - - msg = yield runInExtension("waitForEvents", [ - {type: "onCreated", data: {id, url: TXT_URL}}, - { - type: "onChanged", - data: { - id, - state: { - previous: "in_progress", - current: "complete", - }, - }, - }, - ]); - - equal(msg.status, "success", "got onCreated and onChanged events"); - - msg = yield runInExtension("removeFile", id); - equal(msg.status, "success", "removeFile() succeeded"); - - msg = yield runInExtension("removeFile", id); - equal(msg.status, "error", "removeFile() fails since the file was already removed."); - ok(/file doesn't exist/.test(msg.errmsg), "removeFile() failed on removed file."); - - msg = yield runInExtension("removeFile", 1000); - ok(/Invalid download id/.test(msg.errmsg), "removeFile() failed due to non-existent id"); -}); - -add_task(function* test_removal_of_incomplete_download() { - let url = getInterruptibleUrl(); - let msg = yield runInExtension("download", {url}); - equal(msg.status, "success", "download() succeeded"); - const id = msg.result; - - let progressPromise = waitForProgress(url, INT_PARTIAL_LEN); - - msg = yield runInExtension("waitForEvents", [ - {type: "onCreated", data: {id}}, - ]); - equal(msg.status, "success", "got created and changed events"); - - yield progressPromise; - do_print(`download reached ${INT_PARTIAL_LEN} bytes`); - - msg = yield runInExtension("pause", id); - equal(msg.status, "success", "pause() succeeded"); - - msg = yield runInExtension("waitForEvents", [ - { - type: "onChanged", - data: { - id, - state: { - previous: "in_progress", - current: "interrupted", - }, - paused: { - previous: false, - current: true, - }, - canResume: { - previous: false, - current: true, - }, - }, - }, { - type: "onChanged", - data: { - id, - error: { - previous: null, - current: "USER_CANCELED", - }, - }, - }]); - equal(msg.status, "success", "got onChanged event corresponding to pause"); - - msg = yield runInExtension("removeFile", id); - equal(msg.status, "error", "removeFile() on paused download failed"); - - ok(/Cannot remove incomplete download/.test(msg.errmsg), "removeFile() failed due to download being incomplete"); - - msg = yield runInExtension("resume", id); - equal(msg.status, "success", "resume() succeeded"); - - msg = yield runInExtension("waitForEvents", [ - { - type: "onChanged", - data: { - id, - state: { - previous: "interrupted", - current: "in_progress", - }, - paused: { - previous: true, - current: false, - }, - canResume: { - previous: true, - current: false, - }, - error: { - previous: "USER_CANCELED", - current: null, - }, - }, - }, - { - type: "onChanged", - data: { - id, - state: { - previous: "in_progress", - current: "complete", - }, - }, - }, - ]); - equal(msg.status, "success", "got onChanged events for resume and complete"); - - msg = yield runInExtension("removeFile", id); - equal(msg.status, "success", "removeFile() succeeded following completion of resumed download."); -}); - -// Test erase(). We don't do elaborate testing of the query handling -// since it uses the exact same engine as search() which is tested -// more thoroughly in test_chrome_ext_downloads_search.html -add_task(function* test_erase() { - yield clearDownloads(); - - yield runInExtension("clearEvents"); - - function* download() { - let msg = yield runInExtension("download", {url: TXT_URL}); - equal(msg.status, "success", "download succeeded"); - let id = msg.result; - - msg = yield runInExtension("waitForEvents", [{ - type: "onChanged", data: {id, state: {current: "complete"}}, - }], {exact: false}); - equal(msg.status, "success", "download finished"); - - return id; - } - - let ids = {}; - ids.dl1 = yield download(); - ids.dl2 = yield download(); - ids.dl3 = yield download(); - - let msg = yield runInExtension("search", {}); - equal(msg.status, "success", "search succeded"); - equal(msg.result.length, 3, "search found 3 downloads"); - - msg = yield runInExtension("clearEvents"); - - msg = yield runInExtension("erase", {id: ids.dl1}); - equal(msg.status, "success", "erase by id succeeded"); - - msg = yield runInExtension("waitForEvents", [ - {type: "onErased", data: ids.dl1}, - ]); - equal(msg.status, "success", "received onErased event"); - - msg = yield runInExtension("search", {}); - equal(msg.status, "success", "search succeded"); - equal(msg.result.length, 2, "search found 2 downloads"); - - msg = yield runInExtension("erase", {}); - equal(msg.status, "success", "erase everything succeeded"); - - msg = yield runInExtension("waitForEvents", [ - {type: "onErased", data: ids.dl2}, - {type: "onErased", data: ids.dl3}, - ], {inorder: false}); - equal(msg.status, "success", "received 2 onErased events"); - - msg = yield runInExtension("search", {}); - equal(msg.status, "success", "search succeded"); - equal(msg.result.length, 0, "search found 0 downloads"); -}); - -function loadImage(img, data) { - return new Promise((resolve) => { - img.src = data; - img.onload = resolve; - }); -} - -add_task(function* test_getFileIcon() { - let webNav = Services.appShell.createWindowlessBrowser(false); - let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); - - let system = Services.scriptSecurityManager.getSystemPrincipal(); - docShell.createAboutBlankContentViewer(system); - - let img = webNav.document.createElement("img"); - - let msg = yield runInExtension("download", {url: TXT_URL}); - equal(msg.status, "success", "download() succeeded"); - const id = msg.result; - - msg = yield runInExtension("getFileIcon", id); - equal(msg.status, "success", "getFileIcon() succeeded"); - yield loadImage(img, msg.result); - equal(img.height, 32, "returns an icon with the right height"); - equal(img.width, 32, "returns an icon with the right width"); - - msg = yield runInExtension("waitForEvents", [ - {type: "onCreated", data: {id, url: TXT_URL}}, - {type: "onChanged"}, - ]); - equal(msg.status, "success", "got events"); - - msg = yield runInExtension("getFileIcon", id); - equal(msg.status, "success", "getFileIcon() succeeded"); - yield loadImage(img, msg.result); - equal(img.height, 32, "returns an icon with the right height after download"); - equal(img.width, 32, "returns an icon with the right width after download"); - - msg = yield runInExtension("getFileIcon", id + 100); - equal(msg.status, "error", "getFileIcon() failed"); - ok(msg.errmsg.includes("Invalid download id"), "download id is invalid"); - - msg = yield runInExtension("getFileIcon", id, {size: 127}); - equal(msg.status, "success", "getFileIcon() succeeded"); - yield loadImage(img, msg.result); - equal(img.height, 127, "returns an icon with the right custom height"); - equal(img.width, 127, "returns an icon with the right custom width"); - - msg = yield runInExtension("getFileIcon", id, {size: 1}); - equal(msg.status, "success", "getFileIcon() succeeded"); - yield loadImage(img, msg.result); - equal(img.height, 1, "returns an icon with the right custom height"); - equal(img.width, 1, "returns an icon with the right custom width"); - - msg = yield runInExtension("getFileIcon", id, {size: "foo"}); - equal(msg.status, "error", "getFileIcon() fails"); - ok(msg.errmsg.includes("Error processing size"), "size is not a number"); - - msg = yield runInExtension("getFileIcon", id, {size: 0}); - equal(msg.status, "error", "getFileIcon() fails"); - ok(msg.errmsg.includes("Error processing size"), "size is too small"); - - msg = yield runInExtension("getFileIcon", id, {size: 128}); - equal(msg.status, "error", "getFileIcon() fails"); - ok(msg.errmsg.includes("Error processing size"), "size is too big"); - - webNav.close(); -}); - -add_task(function* cleanup() { - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js deleted file mode 100644 index 4caa82456..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js +++ /dev/null @@ -1,402 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -Cu.import("resource://gre/modules/Downloads.jsm"); - -const server = createHttpServer(); -server.registerDirectory("/data/", do_get_file("data")); - -const BASE = `http://localhost:${server.identity.primaryPort}/data`; -const TXT_FILE = "file_download.txt"; -const TXT_URL = BASE + "/" + TXT_FILE; -const TXT_LEN = 46; -const HTML_FILE = "file_download.html"; -const HTML_URL = BASE + "/" + HTML_FILE; -const HTML_LEN = 117; -const BIG_LEN = 1000; // something bigger both TXT_LEN and HTML_LEN - -function backgroundScript() { - let complete = new Map(); - - function waitForComplete(id) { - if (complete.has(id)) { - return complete.get(id).promise; - } - - let promise = new Promise(resolve => { - complete.set(id, {resolve}); - }); - complete.get(id).promise = promise; - return promise; - } - - browser.downloads.onChanged.addListener(change => { - if (change.state && change.state.current == "complete") { - // Make sure we have a promise. - waitForComplete(change.id); - complete.get(change.id).resolve(); - } - }); - - browser.test.onMessage.addListener(async (msg, ...args) => { - if (msg == "download.request") { - try { - let id = await browser.downloads.download(args[0]); - browser.test.sendMessage("download.done", {status: "success", id}); - } catch (error) { - browser.test.sendMessage("download.done", {status: "error", errmsg: error.message}); - } - } else if (msg == "search.request") { - try { - let downloads = await browser.downloads.search(args[0]); - browser.test.sendMessage("search.done", {status: "success", downloads}); - } catch (error) { - browser.test.sendMessage("search.done", {status: "error", errmsg: error.message}); - } - } else if (msg == "waitForComplete.request") { - await waitForComplete(args[0]); - browser.test.sendMessage("waitForComplete.done"); - } - }); - - browser.test.sendMessage("ready"); -} - -async function clearDownloads(callback) { - let list = await Downloads.getList(Downloads.ALL); - let downloads = await list.getAll(); - - await Promise.all(downloads.map(download => list.remove(download))); - - return downloads; -} - -add_task(function* test_search() { - const nsIFile = Ci.nsIFile; - let downloadDir = FileUtils.getDir("TmpD", ["downloads"]); - downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - do_print(`downloadDir ${downloadDir.path}`); - - function downloadPath(filename) { - let path = downloadDir.clone(); - path.append(filename); - return path.path; - } - - Services.prefs.setIntPref("browser.download.folderList", 2); - Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir); - - do_register_cleanup(async () => { - Services.prefs.clearUserPref("browser.download.folderList"); - Services.prefs.clearUserPref("browser.download.dir"); - await cleanupDir(downloadDir); - await clearDownloads(); - }); - - yield clearDownloads().then(downloads => { - do_print(`removed ${downloads.length} pre-existing downloads from history`); - }); - - let extension = ExtensionTestUtils.loadExtension({ - background: backgroundScript, - manifest: { - permissions: ["downloads"], - }, - }); - - async function download(options) { - extension.sendMessage("download.request", options); - let result = await extension.awaitMessage("download.done"); - - if (result.status == "success") { - do_print(`wait for onChanged event to indicate ${result.id} is complete`); - extension.sendMessage("waitForComplete.request", result.id); - - await extension.awaitMessage("waitForComplete.done"); - } - - return result; - } - - function search(query) { - extension.sendMessage("search.request", query); - return extension.awaitMessage("search.done"); - } - - yield extension.startup(); - yield extension.awaitMessage("ready"); - - // Do some downloads... - const time1 = new Date(); - - let downloadIds = {}; - let msg = yield download({url: TXT_URL}); - equal(msg.status, "success", "download() succeeded"); - downloadIds.txt1 = msg.id; - - const TXT_FILE2 = "NewFile.txt"; - msg = yield download({url: TXT_URL, filename: TXT_FILE2}); - equal(msg.status, "success", "download() succeeded"); - downloadIds.txt2 = msg.id; - - const time2 = new Date(); - - msg = yield download({url: HTML_URL}); - equal(msg.status, "success", "download() succeeded"); - downloadIds.html1 = msg.id; - - const HTML_FILE2 = "renamed.html"; - msg = yield download({url: HTML_URL, filename: HTML_FILE2}); - equal(msg.status, "success", "download() succeeded"); - downloadIds.html2 = msg.id; - - const time3 = new Date(); - - // Search for each individual download and check - // the corresponding DownloadItem. - function* checkDownloadItem(id, expect) { - let item = yield search({id}); - equal(item.status, "success", "search() succeeded"); - equal(item.downloads.length, 1, "search() found exactly 1 download"); - - Object.keys(expect).forEach(function(field) { - equal(item.downloads[0][field], expect[field], `DownloadItem.${field} is correct"`); - }); - } - yield checkDownloadItem(downloadIds.txt1, { - url: TXT_URL, - filename: downloadPath(TXT_FILE), - mime: "text/plain", - state: "complete", - bytesReceived: TXT_LEN, - totalBytes: TXT_LEN, - fileSize: TXT_LEN, - exists: true, - }); - - yield checkDownloadItem(downloadIds.txt2, { - url: TXT_URL, - filename: downloadPath(TXT_FILE2), - mime: "text/plain", - state: "complete", - bytesReceived: TXT_LEN, - totalBytes: TXT_LEN, - fileSize: TXT_LEN, - exists: true, - }); - - yield checkDownloadItem(downloadIds.html1, { - url: HTML_URL, - filename: downloadPath(HTML_FILE), - mime: "text/html", - state: "complete", - bytesReceived: HTML_LEN, - totalBytes: HTML_LEN, - fileSize: HTML_LEN, - exists: true, - }); - - yield checkDownloadItem(downloadIds.html2, { - url: HTML_URL, - filename: downloadPath(HTML_FILE2), - mime: "text/html", - state: "complete", - bytesReceived: HTML_LEN, - totalBytes: HTML_LEN, - fileSize: HTML_LEN, - exists: true, - }); - - function* checkSearch(query, expected, description, exact) { - let item = yield search(query); - equal(item.status, "success", "search() succeeded"); - equal(item.downloads.length, expected.length, `search() for ${description} found exactly ${expected.length} downloads`); - - let receivedIds = item.downloads.map(i => i.id); - if (exact) { - receivedIds.forEach((id, idx) => { - equal(id, downloadIds[expected[idx]], `search() for ${description} returned ${expected[idx]} in position ${idx}`); - }); - } else { - Object.keys(downloadIds).forEach(key => { - const id = downloadIds[key]; - const thisExpected = expected.includes(key); - equal(receivedIds.includes(id), thisExpected, - `search() for ${description} ${thisExpected ? "includes" : "does not include"} ${key}`); - }); - } - } - - // Check that search with an invalid id returns nothing. - // NB: for now ids are not persistent and we start numbering them at 1 - // so a sufficiently large number will be unused. - const INVALID_ID = 1000; - yield checkSearch({id: INVALID_ID}, [], "invalid id"); - - // Check that search on url works. - yield checkSearch({url: TXT_URL}, ["txt1", "txt2"], "url"); - - // Check that regexp on url works. - const HTML_REGEX = "[downlad]{8}\.html+$"; - yield checkSearch({urlRegex: HTML_REGEX}, ["html1", "html2"], "url regexp"); - - // Check that compatible url+regexp works - yield checkSearch({url: HTML_URL, urlRegex: HTML_REGEX}, ["html1", "html2"], "compatible url+urlRegex"); - - // Check that incompatible url+regexp works - yield checkSearch({url: TXT_URL, urlRegex: HTML_REGEX}, [], "incompatible url+urlRegex"); - - // Check that search on filename works. - yield checkSearch({filename: downloadPath(TXT_FILE)}, ["txt1"], "filename"); - - // Check that regexp on filename works. - yield checkSearch({filenameRegex: HTML_REGEX}, ["html1"], "filename regex"); - - // Check that compatible filename+regexp works - yield checkSearch({filename: downloadPath(HTML_FILE), filenameRegex: HTML_REGEX}, ["html1"], "compatible filename+filename regex"); - - // Check that incompatible filename+regexp works - yield checkSearch({filename: downloadPath(TXT_FILE), filenameRegex: HTML_REGEX}, [], "incompatible filename+filename regex"); - - // Check that simple positive search terms work. - yield checkSearch({query: ["file_download"]}, ["txt1", "txt2", "html1", "html2"], - "term file_download"); - yield checkSearch({query: ["NewFile"]}, ["txt2"], "term NewFile"); - - // Check that positive search terms work case-insensitive. - yield checkSearch({query: ["nEwfILe"]}, ["txt2"], "term nEwfiLe"); - - // Check that negative search terms work. - yield checkSearch({query: ["-txt"]}, ["html1", "html2"], "term -txt"); - - // Check that positive and negative search terms together work. - yield checkSearch({query: ["html", "-renamed"]}, ["html1"], "postive and negative terms"); - - function* checkSearchWithDate(query, expected, description) { - const fields = Object.keys(query); - if (fields.length != 1 || !(query[fields[0]] instanceof Date)) { - throw new Error("checkSearchWithDate expects exactly one Date field"); - } - const field = fields[0]; - const date = query[field]; - - let newquery = {}; - - // Check as a Date - newquery[field] = date; - yield checkSearch(newquery, expected, `${description} as Date`); - - // Check as numeric milliseconds - newquery[field] = date.valueOf(); - yield checkSearch(newquery, expected, `${description} as numeric ms`); - - // Check as stringified milliseconds - newquery[field] = date.valueOf().toString(); - yield checkSearch(newquery, expected, `${description} as string ms`); - - // Check as ISO string - newquery[field] = date.toISOString(); - yield checkSearch(newquery, expected, `${description} as iso string`); - } - - // Check startedBefore - yield checkSearchWithDate({startedBefore: time1}, [], "before time1"); - yield checkSearchWithDate({startedBefore: time2}, ["txt1", "txt2"], "before time2"); - yield checkSearchWithDate({startedBefore: time3}, ["txt1", "txt2", "html1", "html2"], "before time3"); - - // Check startedAfter - yield checkSearchWithDate({startedAfter: time1}, ["txt1", "txt2", "html1", "html2"], "after time1"); - yield checkSearchWithDate({startedAfter: time2}, ["html1", "html2"], "after time2"); - yield checkSearchWithDate({startedAfter: time3}, [], "after time3"); - - // Check simple search on totalBytes - yield checkSearch({totalBytes: TXT_LEN}, ["txt1", "txt2"], "totalBytes"); - yield checkSearch({totalBytes: HTML_LEN}, ["html1", "html2"], "totalBytes"); - - // Check simple test on totalBytes{Greater,Less} - // (NB: TXT_LEN < HTML_LEN < BIG_LEN) - yield checkSearch({totalBytesGreater: 0}, ["txt1", "txt2", "html1", "html2"], "totalBytesGreater than 0"); - yield checkSearch({totalBytesGreater: TXT_LEN}, ["html1", "html2"], `totalBytesGreater than ${TXT_LEN}`); - yield checkSearch({totalBytesGreater: HTML_LEN}, [], `totalBytesGreater than ${HTML_LEN}`); - yield checkSearch({totalBytesLess: TXT_LEN}, [], `totalBytesLess than ${TXT_LEN}`); - yield checkSearch({totalBytesLess: HTML_LEN}, ["txt1", "txt2"], `totalBytesLess than ${HTML_LEN}`); - yield checkSearch({totalBytesLess: BIG_LEN}, ["txt1", "txt2", "html1", "html2"], `totalBytesLess than ${BIG_LEN}`); - - // Check good combinations of totalBytes*. - yield checkSearch({totalBytes: HTML_LEN, totalBytesGreater: TXT_LEN}, ["html1", "html2"], "totalBytes and totalBytesGreater"); - yield checkSearch({totalBytes: TXT_LEN, totalBytesLess: HTML_LEN}, ["txt1", "txt2"], "totalBytes and totalBytesGreater"); - yield checkSearch({totalBytes: HTML_LEN, totalBytesLess: BIG_LEN, totalBytesGreater: 0}, ["html1", "html2"], "totalBytes and totalBytesLess and totalBytesGreater"); - - // Check bad combination of totalBytes*. - yield checkSearch({totalBytesLess: TXT_LEN, totalBytesGreater: HTML_LEN}, [], "bad totalBytesLess, totalBytesGreater combination"); - yield checkSearch({totalBytes: TXT_LEN, totalBytesGreater: HTML_LEN}, [], "bad totalBytes, totalBytesGreater combination"); - yield checkSearch({totalBytes: HTML_LEN, totalBytesLess: TXT_LEN}, [], "bad totalBytes, totalBytesLess combination"); - - // Check mime. - yield checkSearch({mime: "text/plain"}, ["txt1", "txt2"], "mime text/plain"); - yield checkSearch({mime: "text/html"}, ["html1", "html2"], "mime text/htmlplain"); - yield checkSearch({mime: "video/webm"}, [], "mime video/webm"); - - // Check fileSize. - yield checkSearch({fileSize: TXT_LEN}, ["txt1", "txt2"], "fileSize"); - yield checkSearch({fileSize: HTML_LEN}, ["html1", "html2"], "fileSize"); - - // Fields like bytesReceived, paused, state, exists are meaningful - // for downloads that are in progress but have not yet completed. - // todo: add tests for these when we have better support for in-progress - // downloads (e.g., after pause(), resume() and cancel() are implemented) - - // Check multiple query properties. - // We could make this testing arbitrarily complicated... - // We already tested combining fields with obvious interactions above - // (e.g., filename and filenameRegex or startTime and startedBefore/After) - // so now just throw as many fields as we can at a single search and - // make sure a simple case still works. - yield checkSearch({ - url: TXT_URL, - urlRegex: "download", - filename: downloadPath(TXT_FILE), - filenameRegex: "download", - query: ["download"], - startedAfter: time1.valueOf().toString(), - startedBefore: time2.valueOf().toString(), - totalBytes: TXT_LEN, - totalBytesGreater: 0, - totalBytesLess: BIG_LEN, - mime: "text/plain", - fileSize: TXT_LEN, - }, ["txt1"], "many properties"); - - // Check simple orderBy (forward and backward). - yield checkSearch({orderBy: ["startTime"]}, ["txt1", "txt2", "html1", "html2"], "orderBy startTime", true); - yield checkSearch({orderBy: ["-startTime"]}, ["html2", "html1", "txt2", "txt1"], "orderBy -startTime", true); - - // Check orderBy with multiple fields. - // NB: TXT_URL and HTML_URL differ only in extension and .html precedes .txt - yield checkSearch({orderBy: ["url", "-startTime"]}, ["html2", "html1", "txt2", "txt1"], "orderBy with multiple fields", true); - - // Check orderBy with limit. - yield checkSearch({orderBy: ["url"], limit: 1}, ["html1"], "orderBy with limit", true); - - // Check bad arguments. - function* checkBadSearch(query, pattern, description) { - let item = yield search(query); - equal(item.status, "error", "search() failed"); - ok(pattern.test(item.errmsg), `error message for ${description} was correct (${item.errmsg}).`); - } - - yield checkBadSearch("myquery", /Incorrect argument type/, "query is not an object"); - yield checkBadSearch({bogus: "boo"}, /Unexpected property/, "query contains an unknown field"); - yield checkBadSearch({query: "query string"}, /Expected array/, "query.query is a string"); - yield checkBadSearch({startedBefore: "i am not a time"}, /Type error/, "query.startedBefore is not a valid time"); - yield checkBadSearch({startedAfter: "i am not a time"}, /Type error/, "query.startedAfter is not a valid time"); - yield checkBadSearch({endedBefore: "i am not a time"}, /Type error/, "query.endedBefore is not a valid time"); - yield checkBadSearch({endedAfter: "i am not a time"}, /Type error/, "query.endedAfter is not a valid time"); - yield checkBadSearch({urlRegex: "["}, /Invalid urlRegex/, "query.urlRegexp is not a valid regular expression"); - yield checkBadSearch({filenameRegex: "["}, /Invalid filenameRegex/, "query.filenameRegexp is not a valid regular expression"); - yield checkBadSearch({orderBy: "startTime"}, /Expected array/, "query.orderBy is not an array"); - yield checkBadSearch({orderBy: ["bogus"]}, /Invalid orderBy field/, "query.orderBy references a non-existent field"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js b/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js deleted file mode 100644 index bc6bfcd68..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js +++ /dev/null @@ -1,175 +0,0 @@ -"use strict"; - -/* globals browser */ - -XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", - "resource://gre/modules/AddonManager.jsm"); - -function promiseAddonStartup() { - const {Management} = Cu.import("resource://gre/modules/Extension.jsm"); - - return new Promise(resolve => { - let listener = (evt, extension) => { - Management.off("startup", listener); - resolve(extension); - }; - - Management.on("startup", listener); - }); -} - -add_task(function* setup() { - yield ExtensionTestUtils.startAddonManager(); -}); - -add_task(function* test_experiments_api() { - let apiAddonFile = Extension.generateZipFile({ - "install.rdf": ` - - - - - - - - - `, - - "api.js": String.raw` - Components.utils.import("resource://gre/modules/Services.jsm"); - - Services.obs.notifyObservers(null, "webext-api-loaded", ""); - - class API extends ExtensionAPI { - getAPI(context) { - return { - meh: { - hello(text) { - Services.obs.notifyObservers(null, "webext-api-hello", text); - } - } - } - } - } - `, - - "schema.json": [ - { - "namespace": "meh", - "description": "All full of meh.", - "permissions": ["experiments.meh"], - "functions": [ - { - "name": "hello", - "type": "function", - "description": "Hates you. This is all.", - "parameters": [ - {"type": "string", "name": "text"}, - ], - }, - ], - }, - ], - }); - - let addonFile = Extension.generateXPI({ - manifest: { - applications: {gecko: {id: "meh@web.extension"}}, - permissions: ["experiments.meh"], - }, - - background() { - // The test code below checks that hello() is called at the right - // time with the string "Here I am". Verify that the api schema is - // being correctly interpreted by calling hello() with bad arguments - // and only calling hello() with the magic string if the call with - // bad arguments throws. - try { - browser.meh.hello("I should not see this", "since two arguments are bad"); - } catch (err) { - browser.meh.hello("Here I am"); - } - }, - }); - - let boringAddonFile = Extension.generateXPI({ - manifest: { - applications: {gecko: {id: "boring@web.extension"}}, - }, - background() { - if (browser.meh) { - browser.meh.hello("Here I should not be"); - } - }, - }); - - do_register_cleanup(() => { - for (let file of [apiAddonFile, addonFile, boringAddonFile]) { - Services.obs.notifyObservers(file, "flush-cache-entry", null); - file.remove(false); - } - }); - - - let resolveHello; - let observer = (subject, topic, data) => { - if (topic == "webext-api-loaded") { - ok(!!resolveHello, "Should not see API loaded until dependent extension loads"); - } else if (topic == "webext-api-hello") { - resolveHello(data); - } - }; - - Services.obs.addObserver(observer, "webext-api-loaded", false); - Services.obs.addObserver(observer, "webext-api-hello", false); - do_register_cleanup(() => { - Services.obs.removeObserver(observer, "webext-api-loaded"); - Services.obs.removeObserver(observer, "webext-api-hello"); - }); - - - // Install API add-on. - let apiAddon = yield AddonManager.installTemporaryAddon(apiAddonFile); - - let {APIs} = Cu.import("resource://gre/modules/ExtensionManagement.jsm", {}); - ok(APIs.apis.has("meh"), "Should have meh API."); - - - // Install boring WebExtension add-on. - let boringAddon = yield AddonManager.installTemporaryAddon(boringAddonFile); - yield promiseAddonStartup(); - - - // Install interesting WebExtension add-on. - let promise = new Promise(resolve => { - resolveHello = resolve; - }); - - let addon = yield AddonManager.installTemporaryAddon(addonFile); - yield promiseAddonStartup(); - - let hello = yield promise; - equal(hello, "Here I am", "Should get hello from add-on"); - - // Cleanup. - apiAddon.uninstall(); - - boringAddon.userDisabled = true; - yield new Promise(do_execute_soon); - - equal(addon.appDisabled, true, "Add-on should be app-disabled after its dependency is removed."); - - addon.uninstall(); - boringAddon.uninstall(); -}); - diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_extension.js b/toolkit/components/extensions/test/xpcshell/test_ext_extension.js deleted file mode 100644 index f18845f6a..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_extension.js +++ /dev/null @@ -1,55 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_is_allowed_incognito_access() { - async function background() { - let allowed = await browser.extension.isAllowedIncognitoAccess(); - - browser.test.assertEq(true, allowed, "isAllowedIncognitoAccess is true"); - browser.test.notifyPass("isAllowedIncognitoAccess"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: {}, - }); - - yield extension.startup(); - yield extension.awaitFinish("isAllowedIncognitoAccess"); - yield extension.unload(); -}); - -add_task(function* test_in_incognito_context_false() { - function background() { - browser.test.assertEq(false, browser.extension.inIncognitoContext, "inIncognitoContext returned false"); - browser.test.notifyPass("inIncognitoContext"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: {}, - }); - - yield extension.startup(); - yield extension.awaitFinish("inIncognitoContext"); - yield extension.unload(); -}); - -add_task(function* test_is_allowed_file_scheme_access() { - async function background() { - let allowed = await browser.extension.isAllowedFileSchemeAccess(); - - browser.test.assertEq(false, allowed, "isAllowedFileSchemeAccess is false"); - browser.test.notifyPass("isAllowedFileSchemeAccess"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: {}, - }); - - yield extension.startup(); - yield extension.awaitFinish("isAllowedFileSchemeAccess"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_idle.js b/toolkit/components/extensions/test/xpcshell/test_ext_idle.js deleted file mode 100644 index 89bcac217..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_idle.js +++ /dev/null @@ -1,202 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -Cu.import("resource://testing-common/MockRegistrar.jsm"); - -let idleService = { - _observers: new Set(), - _activity: { - addCalls: [], - removeCalls: [], - observerFires: [], - }, - _reset: function() { - this._observers.clear(); - this._activity.addCalls = []; - this._activity.removeCalls = []; - this._activity.observerFires = []; - }, - _fireObservers: function(state) { - for (let observer of this._observers.values()) { - observer.observe(observer, state, null); - this._activity.observerFires.push(state); - } - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIIdleService]), - idleTime: 19999, - addIdleObserver: function(observer, time) { - this._observers.add(observer); - this._activity.addCalls.push(time); - }, - removeIdleObserver: function(observer, time) { - this._observers.delete(observer); - this._activity.removeCalls.push(time); - }, -}; - -function checkActivity(expectedActivity) { - let {expectedAdd, expectedRemove, expectedFires} = expectedActivity; - let {addCalls, removeCalls, observerFires} = idleService._activity; - equal(expectedAdd.length, addCalls.length, "idleService.addIdleObserver was called the expected number of times"); - equal(expectedRemove.length, removeCalls.length, "idleService.removeIdleObserver was called the expected number of times"); - equal(expectedFires.length, observerFires.length, "idle observer was fired the expected number of times"); - deepEqual(addCalls, expectedAdd, "expected interval passed to idleService.addIdleObserver"); - deepEqual(removeCalls, expectedRemove, "expected interval passed to idleService.removeIdleObserver"); - deepEqual(observerFires, expectedFires, "expected topic passed to idle observer"); -} - -add_task(function* setup() { - let fakeIdleService = MockRegistrar.register("@mozilla.org/widget/idleservice;1", idleService); - do_register_cleanup(() => { - MockRegistrar.unregister(fakeIdleService); - }); -}); - -add_task(function* testQueryStateActive() { - function background() { - browser.idle.queryState(20).then(status => { - browser.test.assertEq("active", status, "Idle status is active"); - browser.test.notifyPass("idle"); - }, - err => { - browser.test.fail(`Error: ${err} :: ${err.stack}`); - browser.test.notifyFail("idle"); - }); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - permissions: ["idle"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("idle"); - yield extension.unload(); -}); - -add_task(function* testQueryStateIdle() { - function background() { - browser.idle.queryState(15).then(status => { - browser.test.assertEq("idle", status, "Idle status is idle"); - browser.test.notifyPass("idle"); - }, - err => { - browser.test.fail(`Error: ${err} :: ${err.stack}`); - browser.test.notifyFail("idle"); - }); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - permissions: ["idle"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("idle"); - yield extension.unload(); -}); - -add_task(function* testOnlySetDetectionInterval() { - function background() { - browser.idle.setDetectionInterval(99); - browser.test.sendMessage("detectionIntervalSet"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - permissions: ["idle"], - }, - }); - - idleService._reset(); - yield extension.startup(); - yield extension.awaitMessage("detectionIntervalSet"); - idleService._fireObservers("idle"); - checkActivity({expectedAdd: [], expectedRemove: [], expectedFires: []}); - yield extension.unload(); -}); - -add_task(function* testSetDetectionIntervalBeforeAddingListener() { - function background() { - browser.idle.setDetectionInterval(99); - browser.idle.onStateChanged.addListener(newState => { - browser.test.assertEq("idle", newState, "listener fired with the expected state"); - browser.test.sendMessage("listenerFired"); - }); - browser.test.sendMessage("listenerAdded"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - permissions: ["idle"], - }, - }); - - idleService._reset(); - yield extension.startup(); - yield extension.awaitMessage("listenerAdded"); - idleService._fireObservers("idle"); - yield extension.awaitMessage("listenerFired"); - checkActivity({expectedAdd: [99], expectedRemove: [], expectedFires: ["idle"]}); - yield extension.unload(); -}); - -add_task(function* testSetDetectionIntervalAfterAddingListener() { - function background() { - browser.idle.onStateChanged.addListener(newState => { - browser.test.assertEq("idle", newState, "listener fired with the expected state"); - browser.test.sendMessage("listenerFired"); - }); - browser.idle.setDetectionInterval(99); - browser.test.sendMessage("detectionIntervalSet"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - permissions: ["idle"], - }, - }); - - idleService._reset(); - yield extension.startup(); - yield extension.awaitMessage("detectionIntervalSet"); - idleService._fireObservers("idle"); - yield extension.awaitMessage("listenerFired"); - checkActivity({expectedAdd: [60, 99], expectedRemove: [60], expectedFires: ["idle"]}); - yield extension.unload(); -}); - -add_task(function* testOnlyAddingListener() { - function background() { - browser.idle.onStateChanged.addListener(newState => { - browser.test.assertEq("active", newState, "listener fired with the expected state"); - browser.test.sendMessage("listenerFired"); - }); - browser.test.sendMessage("listenerAdded"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - permissions: ["idle"], - }, - }); - - idleService._reset(); - yield extension.startup(); - yield extension.awaitMessage("listenerAdded"); - idleService._fireObservers("active"); - yield extension.awaitMessage("listenerFired"); - // check that "idle-daily" topic does not cause a listener to fire - idleService._fireObservers("idle-daily"); - checkActivity({expectedAdd: [60], expectedRemove: [], expectedFires: ["active", "idle-daily"]}); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_json_parser.js b/toolkit/components/extensions/test/xpcshell/test_ext_json_parser.js deleted file mode 100644 index 652f41315..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_json_parser.js +++ /dev/null @@ -1,37 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_json_parser() { - const ID = "json@test.web.extension"; - - let xpi = Extension.generateXPI({ - files: { - "manifest.json": String.raw`{ - // This is a manifest. - "applications": {"gecko": {"id": "${ID}"}}, - "name": "This \" is // not a comment", - "version": "0.1\\" // , "description": "This is not a description" - }`, - }, - }); - - let expectedManifest = { - "applications": {"gecko": {"id": ID}}, - "name": "This \" is // not a comment", - "version": "0.1\\", - }; - - let fileURI = Services.io.newFileURI(xpi); - let uri = NetUtil.newURI(`jar:${fileURI.spec}!/`); - - let extension = new ExtensionData(uri); - - yield extension.readManifest(); - - Assert.deepEqual(extension.rawManifest, expectedManifest, - "Manifest with correctly-filtered comments"); - - Services.obs.notifyObservers(xpi, "flush-cache-entry", null); - xpi.remove(false); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js b/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js deleted file mode 100644 index 63d5361a1..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js +++ /dev/null @@ -1,168 +0,0 @@ -"use strict"; - -/* globals browser */ - -Cu.import("resource://gre/modules/Extension.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -const {LegacyExtensionContext} = Cu.import("resource://gre/modules/LegacyExtensionsUtils.jsm"); - -/** - * This test case ensures that LegacyExtensionContext instances: - * - expose the expected API object and can join the messaging - * of a webextension given its addon id - * - the exposed API object can receive a port related to a `runtime.connect` - * Port created in the webextension's background page - * - the received Port instance can exchange messages with the background page - * - the received Port receive a disconnect event when the webextension is - * shutting down - */ -add_task(function* test_legacy_extension_context() { - function background() { - let bgURL = window.location.href; - - let extensionInfo = { - bgURL, - // Extract the assigned uuid from the background page url. - uuid: window.location.hostname, - }; - - browser.test.sendMessage("webextension-ready", extensionInfo); - - let port; - - browser.test.onMessage.addListener(async msg => { - if (msg == "do-send-message") { - let reply = await browser.runtime.sendMessage("webextension -> legacy_extension message"); - - browser.test.assertEq("legacy_extension -> webextension reply", reply, - "Got the expected message from the LegacyExtensionContext"); - browser.test.sendMessage("got-reply-message"); - } else if (msg == "do-connect") { - port = browser.runtime.connect(); - - port.onMessage.addListener(portMsg => { - browser.test.assertEq("legacy_extension -> webextension port message", portMsg, - "Got the expected message from the LegacyExtensionContext"); - port.postMessage("webextension -> legacy_extension port message"); - }); - } else if (msg == "do-disconnect") { - port.disconnect(); - } - }); - } - - let extensionData = { - background, - }; - - let extension = Extension.generate(extensionData); - - let waitForExtensionInfo = new Promise((resolve, reject) => { - extension.on("test-message", function testMessageListener(kind, msg, ...args) { - if (msg != "webextension-ready") { - reject(new Error(`Got an unexpected test-message: ${msg}`)); - } else { - extension.off("test-message", testMessageListener); - resolve(args[0]); - } - }); - }); - - // Connect to the target extension as an external context - // using the given custom sender info. - let legacyContext; - - extension.on("startup", function onStartup() { - extension.off("startup", onStartup); - legacyContext = new LegacyExtensionContext(extension); - extension.callOnClose({ - close: () => legacyContext.unload(), - }); - }); - - yield extension.startup(); - - let extensionInfo = yield waitForExtensionInfo; - - equal(legacyContext.envType, "legacy_extension", - "LegacyExtensionContext instance has the expected type"); - - ok(legacyContext.api, "Got the expected API object"); - ok(legacyContext.api.browser, "Got the expected browser property"); - - let waitMessage = new Promise(resolve => { - const {browser} = legacyContext.api; - browser.runtime.onMessage.addListener((singleMsg, msgSender) => { - resolve({singleMsg, msgSender}); - - // Send a reply to the sender. - return Promise.resolve("legacy_extension -> webextension reply"); - }); - }); - - extension.testMessage("do-send-message"); - - let {singleMsg, msgSender} = yield waitMessage; - equal(singleMsg, "webextension -> legacy_extension message", - "Got the expected message"); - ok(msgSender, "Got a message sender object"); - - equal(msgSender.id, extensionInfo.uuid, "The sender has the expected id property"); - equal(msgSender.url, extensionInfo.bgURL, "The sender has the expected url property"); - - // Wait confirmation that the reply has been received. - yield new Promise((resolve, reject) => { - extension.on("test-message", function testMessageListener(kind, msg, ...args) { - if (msg != "got-reply-message") { - reject(new Error(`Got an unexpected test-message: ${msg}`)); - } else { - extension.off("test-message", testMessageListener); - resolve(); - } - }); - }); - - let waitConnectPort = new Promise(resolve => { - let {browser} = legacyContext.api; - browser.runtime.onConnect.addListener(port => { - resolve(port); - }); - }); - - extension.testMessage("do-connect"); - - let port = yield waitConnectPort; - - ok(port, "Got the Port API object"); - ok(port.sender, "The port has a sender property"); - equal(port.sender.id, extensionInfo.uuid, - "The port sender has the expected id property"); - equal(port.sender.url, extensionInfo.bgURL, - "The port sender has the expected url property"); - - let waitPortMessage = new Promise(resolve => { - port.onMessage.addListener((msg) => { - resolve(msg); - }); - }); - - port.postMessage("legacy_extension -> webextension port message"); - - let msg = yield waitPortMessage; - - equal(msg, "webextension -> legacy_extension port message", - "LegacyExtensionContext received the expected message from the webextension"); - - let waitForDisconnect = new Promise(resolve => { - port.onDisconnect.addListener(resolve); - }); - - extension.testMessage("do-disconnect"); - - yield waitForDisconnect; - - do_print("Got the disconnect event on unload"); - - yield extension.shutdown(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_embedding.js b/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_embedding.js deleted file mode 100644 index ea5d78524..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_embedding.js +++ /dev/null @@ -1,188 +0,0 @@ -"use strict"; - -/* globals browser */ - -Cu.import("resource://gre/modules/LegacyExtensionsUtils.jsm"); - -// Import EmbeddedExtensionManager to be able to check that the -// tacked instances are cleared after the embedded extension shutdown. -const { - EmbeddedExtensionManager, -} = Cu.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {}); - -/** - * This test case ensures that the LegacyExtensionsUtils.EmbeddedExtension: - * - load the embedded webextension resources from a "/webextension/" dir - * inside the XPI. - * - EmbeddedExtension.prototype.api returns an API object which exposes - * a working `runtime.onConnect` event object (e.g. the API can receive a port - * when the embedded webextension is started and it can exchange messages - * with the background page). - * - EmbeddedExtension.prototype.startup/shutdown methods manage the embedded - * webextension lifecycle as expected. - */ -add_task(function* test_embedded_webextension_utils() { - function backgroundScript() { - let port = browser.runtime.connect(); - - port.onMessage.addListener((msg) => { - if (msg == "legacy_extension -> webextension") { - port.postMessage("webextension -> legacy_extension"); - port.disconnect(); - } - }); - } - - const id = "@test.embedded.web.extension"; - - // Extensions.generateXPI is used here (and in the other hybrid addons tests in this same - // test dir) to be able to generate an xpi with the directory layout that we expect from - // an hybrid legacy+webextension addon (where all the embedded webextension resources are - // loaded from a 'webextension/' directory). - let fakeHybridAddonFile = Extension.generateZipFile({ - "webextension/manifest.json": { - applications: {gecko: {id}}, - name: "embedded webextension name", - manifest_version: 2, - version: "1.0", - background: { - scripts: ["bg.js"], - }, - }, - "webextension/bg.js": `new ${backgroundScript}`, - }); - - // Remove the generated xpi file and flush the its jar cache - // on cleanup. - do_register_cleanup(() => { - Services.obs.notifyObservers(fakeHybridAddonFile, "flush-cache-entry", null); - fakeHybridAddonFile.remove(false); - }); - - let fileURI = Services.io.newFileURI(fakeHybridAddonFile); - let resourceURI = Services.io.newURI(`jar:${fileURI.spec}!/`, null, null); - - let embeddedExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({ - id, resourceURI, - }); - - ok(embeddedExtension, "Got the embeddedExtension object"); - - equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1, - "Got the expected number of tracked embedded extension instances"); - - do_print("waiting embeddedExtension.startup"); - let embeddedExtensionAPI = yield embeddedExtension.startup(); - ok(embeddedExtensionAPI, "Got the embeddedExtensionAPI object"); - - let waitConnectPort = new Promise(resolve => { - let {browser} = embeddedExtensionAPI; - browser.runtime.onConnect.addListener(port => { - resolve(port); - }); - }); - - let port = yield waitConnectPort; - - ok(port, "Got the Port API object"); - - let waitPortMessage = new Promise(resolve => { - port.onMessage.addListener((msg) => { - resolve(msg); - }); - }); - - port.postMessage("legacy_extension -> webextension"); - - let msg = yield waitPortMessage; - - equal(msg, "webextension -> legacy_extension", - "LegacyExtensionContext received the expected message from the webextension"); - - let waitForDisconnect = new Promise(resolve => { - port.onDisconnect.addListener(resolve); - }); - - do_print("Wait for the disconnect port event"); - yield waitForDisconnect; - do_print("Got the disconnect port event"); - - yield embeddedExtension.shutdown(); - - equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0, - "EmbeddedExtension instances has been untracked from the EmbeddedExtensionManager"); -}); - -function* createManifestErrorTestCase(id, xpi, expectedError) { - // Remove the generated xpi file and flush the its jar cache - // on cleanup. - do_register_cleanup(() => { - Services.obs.notifyObservers(xpi, "flush-cache-entry", null); - xpi.remove(false); - }); - - let fileURI = Services.io.newFileURI(xpi); - let resourceURI = Services.io.newURI(`jar:${fileURI.spec}!/`, null, null); - - let embeddedExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({ - id, resourceURI, - }); - - yield Assert.rejects(embeddedExtension.startup(), expectedError, - "embedded extension startup rejected"); - - // Shutdown a "never-started" addon with an embedded webextension should not - // raise any exception, and if it does this test will fail. - yield embeddedExtension.shutdown(); -} - -add_task(function* test_startup_error_empty_manifest() { - const id = "empty-manifest@test.embedded.web.extension"; - const files = { - "webextension/manifest.json": ``, - }; - const expectedError = "(NS_BASE_STREAM_CLOSED)"; - - let fakeHybridAddonFile = Extension.generateZipFile(files); - - yield createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError); -}); - -add_task(function* test_startup_error_invalid_json_manifest() { - const id = "invalid-json-manifest@test.embedded.web.extension"; - const files = { - "webextension/manifest.json": `{ "name": }`, - }; - const expectedError = "JSON.parse:"; - - let fakeHybridAddonFile = Extension.generateZipFile(files); - - yield createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError); -}); - -add_task(function* test_startup_error_blocking_validation_errors() { - const id = "blocking-manifest-validation-error@test.embedded.web.extension"; - const files = { - "webextension/manifest.json": { - name: "embedded webextension name", - manifest_version: 2, - version: "1.0", - background: { - scripts: {}, - }, - }, - }; - - function expectedError(actual) { - if (actual.errors && actual.errors.length == 1 && - actual.errors[0].startsWith("Reading manifest:")) { - return true; - } - - return false; - } - - let fakeHybridAddonFile = Extension.generateZipFile(files); - - yield createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_localStorage.js b/toolkit/components/extensions/test/xpcshell/test_ext_localStorage.js deleted file mode 100644 index 0f0b41085..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_localStorage.js +++ /dev/null @@ -1,50 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -function backgroundScript() { - let hasRun = localStorage.getItem("has-run"); - let result; - if (!hasRun) { - localStorage.setItem("has-run", "yup"); - localStorage.setItem("test-item", "item1"); - result = "item1"; - } else { - let data = localStorage.getItem("test-item"); - if (data == "item1") { - localStorage.setItem("test-item", "item2"); - result = "item2"; - } else if (data == "item2") { - localStorage.removeItem("test-item"); - result = "deleted"; - } else if (!data) { - localStorage.clear(); - result = "cleared"; - } - } - browser.test.sendMessage("result", result); - browser.test.notifyPass("localStorage"); -} - -const ID = "test-webextension@mozilla.com"; -let extensionData = { - manifest: {applications: {gecko: {id: ID}}}, - background: backgroundScript, -}; - -add_task(function* test_localStorage() { - const RESULTS = ["item1", "item2", "deleted", "cleared", "item1"]; - - for (let expected of RESULTS) { - let extension = ExtensionTestUtils.loadExtension(extensionData); - - yield extension.startup(); - - let actual = yield extension.awaitMessage("result"); - - yield extension.awaitFinish("localStorage"); - yield extension.unload(); - - equal(actual, expected, "got expected localStorage data"); - } -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_management.js b/toolkit/components/extensions/test/xpcshell/test_ext_management.js deleted file mode 100644 index b19554a57..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_management.js +++ /dev/null @@ -1,20 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_management_schema() { - function background() { - browser.test.assertTrue(browser.management, "browser.management API exists"); - browser.test.notifyPass("management-schema"); - } - - let extension = ExtensionTestUtils.loadExtension({ - manifest: { - permissions: ["management"], - }, - background: `(${background})()`, - }); - yield extension.startup(); - yield extension.awaitFinish("management-schema"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js b/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js deleted file mode 100644 index 7d80a9c23..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js +++ /dev/null @@ -1,135 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -Cu.import("resource://gre/modules/AddonManager.jsm"); -Cu.import("resource://testing-common/AddonTestUtils.jsm"); -Cu.import("resource://testing-common/MockRegistrar.jsm"); - -const {promiseAddonByID} = AddonTestUtils; -const id = "uninstall_self_test@tests.mozilla.com"; - -const manifest = { - applications: { - gecko: { - id, - }, - }, - name: "test extension name", - version: "1.0", -}; - -const waitForUninstalled = () => new Promise(resolve => { - const listener = { - onUninstalled: (addon) => { - equal(addon.id, id, "The expected add-on has been uninstalled"); - AddonManager.getAddonByID(addon.id, checkedAddon => { - equal(checkedAddon, null, "Add-on no longer exists"); - AddonManager.removeAddonListener(listener); - resolve(); - }); - }, - }; - AddonManager.addAddonListener(listener); -}); - -let promptService = { - _response: null, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]), - confirmEx: function(...args) { - this._confirmExArgs = args; - return this._response; - }, -}; - -add_task(function* setup() { - let fakePromptService = MockRegistrar.register("@mozilla.org/embedcomp/prompt-service;1", promptService); - do_register_cleanup(() => { - MockRegistrar.unregister(fakePromptService); - }); - yield ExtensionTestUtils.startAddonManager(); -}); - -add_task(function* test_management_uninstall_no_prompt() { - function background() { - browser.test.onMessage.addListener(msg => { - browser.management.uninstallSelf(); - }); - } - - let extension = ExtensionTestUtils.loadExtension({ - manifest, - background, - useAddonManager: "temporary", - }); - - yield extension.startup(); - let addon = yield promiseAddonByID(id); - notEqual(addon, null, "Add-on is installed"); - extension.sendMessage("uninstall"); - yield waitForUninstalled(); - yield extension.markUnloaded(); - Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null); -}); - -add_task(function* test_management_uninstall_prompt_uninstall() { - promptService._response = 0; - - function background() { - browser.test.onMessage.addListener(msg => { - browser.management.uninstallSelf({showConfirmDialog: true}); - }); - } - - let extension = ExtensionTestUtils.loadExtension({ - manifest, - background, - useAddonManager: "temporary", - }); - - yield extension.startup(); - let addon = yield promiseAddonByID(id); - notEqual(addon, null, "Add-on is installed"); - extension.sendMessage("uninstall"); - yield waitForUninstalled(); - yield extension.markUnloaded(); - - // Test localization strings - equal(promptService._confirmExArgs[1], `Uninstall ${manifest.name}`); - equal(promptService._confirmExArgs[2], - `The extension “${manifest.name}†is requesting to be uninstalled. What would you like to do?`); - equal(promptService._confirmExArgs[4], "Uninstall"); - equal(promptService._confirmExArgs[5], "Keep Installed"); - Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null); -}); - -add_task(function* test_management_uninstall_prompt_keep() { - promptService._response = 1; - - function background() { - browser.test.onMessage.addListener(async msg => { - await browser.test.assertRejects( - browser.management.uninstallSelf({showConfirmDialog: true}), - "User cancelled uninstall of extension", - "Expected rejection when user declines uninstall"); - - browser.test.sendMessage("uninstall-rejected"); - }); - } - - let extension = ExtensionTestUtils.loadExtension({ - manifest, - background, - useAddonManager: "temporary", - }); - - yield extension.startup(); - let addon = yield promiseAddonByID(id); - notEqual(addon, null, "Add-on is installed"); - extension.sendMessage("uninstall"); - yield extension.awaitMessage("uninstall-rejected"); - addon = yield promiseAddonByID(id); - notEqual(addon, null, "Add-on remains installed"); - yield extension.unload(); - Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_manifest_content_security_policy.js b/toolkit/components/extensions/test/xpcshell/test_ext_manifest_content_security_policy.js deleted file mode 100644 index 2b0084980..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_manifest_content_security_policy.js +++ /dev/null @@ -1,30 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - - -add_task(function* test_manifest_csp() { - let normalized = yield ExtensionTestUtils.normalizeManifest({ - "content_security_policy": "script-src 'self'; object-src 'none'", - }); - - equal(normalized.error, undefined, "Should not have an error"); - equal(normalized.errors.length, 0, "Should not have warnings"); - equal(normalized.value.content_security_policy, - "script-src 'self'; object-src 'none'", - "Should have the expected poilcy string"); - - - normalized = yield ExtensionTestUtils.normalizeManifest({ - "content_security_policy": "object-src 'none'", - }); - - equal(normalized.error, undefined, "Should not have an error"); - - Assert.deepEqual(normalized.errors, - ["Error processing content_security_policy: SyntaxError: Policy is missing a required \u2018script-src\u2019 directive"], - "Should have the expected warning"); - - equal(normalized.value.content_security_policy, null, - "Invalid policy string should be omitted"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_manifest_incognito.js b/toolkit/components/extensions/test/xpcshell/test_ext_manifest_incognito.js deleted file mode 100644 index 94649692e..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_manifest_incognito.js +++ /dev/null @@ -1,27 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - - -add_task(function* test_manifest_incognito() { - let normalized = yield ExtensionTestUtils.normalizeManifest({ - "incognito": "spanning", - }); - - equal(normalized.error, undefined, "Should not have an error"); - equal(normalized.errors.length, 0, "Should not have warnings"); - equal(normalized.value.incognito, - "spanning", - "Should have the expected incognito string"); - - normalized = yield ExtensionTestUtils.normalizeManifest({ - "incognito": "split", - }); - - equal(normalized.error, undefined, "Should not have an error"); - Assert.deepEqual(normalized.errors, - ['Error processing incognito: Invalid enumeration value "split"'], - "Should have the expected warning"); - equal(normalized.value.incognito, null, - "Invalid incognito string should be omitted"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_manifest_minimum_chrome_version.js b/toolkit/components/extensions/test/xpcshell/test_ext_manifest_minimum_chrome_version.js deleted file mode 100644 index fad5661bb..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_manifest_minimum_chrome_version.js +++ /dev/null @@ -1,13 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - - -add_task(function* test_manifest_minimum_chrome_version() { - let normalized = yield ExtensionTestUtils.normalizeManifest({ - "minimum_chrome_version": "42", - }); - - equal(normalized.error, undefined, "Should not have an error"); - equal(normalized.errors.length, 0, "Should not have warnings"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js deleted file mode 100644 index 5a6b628f5..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js +++ /dev/null @@ -1,514 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -/* globals chrome */ - -const PREF_MAX_READ = "webextensions.native-messaging.max-input-message-bytes"; -const PREF_MAX_WRITE = "webextensions.native-messaging.max-output-message-bytes"; - -const ECHO_BODY = String.raw` - import struct - import sys - - while True: - rawlen = sys.stdin.read(4) - if len(rawlen) == 0: - sys.exit(0) - msglen = struct.unpack('@I', rawlen)[0] - msg = sys.stdin.read(msglen) - - sys.stdout.write(struct.pack('@I', msglen)) - sys.stdout.write(msg) -`; - -const INFO_BODY = String.raw` - import json - import os - import struct - import sys - - msg = json.dumps({"args": sys.argv, "cwd": os.getcwd()}) - sys.stdout.write(struct.pack('@I', len(msg))) - sys.stdout.write(msg) - sys.exit(0) -`; - -const STDERR_LINES = ["hello stderr", "this should be a separate line"]; -let STDERR_MSG = STDERR_LINES.join("\\n"); - -const STDERR_BODY = String.raw` - import sys - sys.stderr.write("${STDERR_MSG}") -`; - -const SCRIPTS = [ - { - name: "echo", - description: "a native app that echoes back messages it receives", - script: ECHO_BODY.replace(/^ {2}/gm, ""), - }, - { - name: "info", - description: "a native app that gives some info about how it was started", - script: INFO_BODY.replace(/^ {2}/gm, ""), - }, - { - name: "stderr", - description: "a native app that writes to stderr and then exits", - script: STDERR_BODY.replace(/^ {2}/gm, ""), - }, -]; - -add_task(function* setup() { - yield setupHosts(SCRIPTS); -}); - -// Test the basic operation of native messaging with a simple -// script that echoes back whatever message is sent to it. -add_task(function* test_happy_path() { - function background() { - let port = browser.runtime.connectNative("echo"); - port.onMessage.addListener(msg => { - browser.test.sendMessage("message", msg); - }); - browser.test.onMessage.addListener((what, payload) => { - if (what == "send") { - if (payload._json) { - let json = payload._json; - payload.toJSON = () => json; - delete payload._json; - } - port.postMessage(payload); - } - }); - browser.test.sendMessage("ready"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - yield extension.awaitMessage("ready"); - const tests = [ - { - data: "this is a string", - what: "simple string", - }, - { - data: "Это юникода", - what: "unicode string", - }, - { - data: {test: "hello"}, - what: "simple object", - }, - { - data: { - what: "An object with a few properties", - number: 123, - bool: true, - nested: {what: "another object"}, - }, - what: "object with several properties", - }, - - { - data: { - ignoreme: true, - _json: {data: "i have a tojson method"}, - }, - expected: {data: "i have a tojson method"}, - what: "object with toJSON() method", - }, - ]; - for (let test of tests) { - extension.sendMessage("send", test.data); - let response = yield extension.awaitMessage("message"); - let expected = test.expected || test.data; - deepEqual(response, expected, `Echoed a message of type ${test.what}`); - } - - let procCount = yield getSubprocessCount(); - equal(procCount, 1, "subprocess is still running"); - let exitPromise = waitForSubprocessExit(); - yield extension.unload(); - yield exitPromise; -}); - -if (AppConstants.platform == "win") { - // "relative.echo" has a relative path in the host manifest. - add_task(function* test_relative_path() { - function background() { - let port = browser.runtime.connectNative("relative.echo"); - let MSG = "test relative echo path"; - port.onMessage.addListener(msg => { - browser.test.assertEq(MSG, msg, "Got expected message back"); - browser.test.sendMessage("done"); - }); - port.postMessage(MSG); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - yield extension.awaitMessage("done"); - - let procCount = yield getSubprocessCount(); - equal(procCount, 1, "subprocess is still running"); - let exitPromise = waitForSubprocessExit(); - yield extension.unload(); - yield exitPromise; - }); -} - -// Test sendNativeMessage() -add_task(function* test_sendNativeMessage() { - async function background() { - let MSG = {test: "hello world"}; - - // Check error handling - await browser.test.assertRejects( - browser.runtime.sendNativeMessage("nonexistent", MSG), - /Attempt to postMessage on disconnected port/, - "sendNativeMessage() to a nonexistent app failed"); - - // Check regular message exchange - let reply = await browser.runtime.sendNativeMessage("echo", MSG); - - let expected = JSON.stringify(MSG); - let received = JSON.stringify(reply); - browser.test.assertEq(expected, received, "Received echoed native message"); - - browser.test.sendMessage("finished"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - yield extension.awaitMessage("finished"); - - // With sendNativeMessage(), the subprocess should be disconnected - // after exchanging a single message. - yield waitForSubprocessExit(); - - yield extension.unload(); -}); - -// Test calling Port.disconnect() -add_task(function* test_disconnect() { - function background() { - let port = browser.runtime.connectNative("echo"); - port.onMessage.addListener((msg, msgPort) => { - browser.test.assertEq(port, msgPort, "onMessage handler should receive the port as the second argument"); - browser.test.sendMessage("message", msg); - }); - port.onDisconnect.addListener(msgPort => { - browser.test.fail("onDisconnect should not be called for disconnect()"); - }); - browser.test.onMessage.addListener((what, payload) => { - if (what == "send") { - if (payload._json) { - let json = payload._json; - payload.toJSON = () => json; - delete payload._json; - } - port.postMessage(payload); - } else if (what == "disconnect") { - try { - port.disconnect(); - browser.test.sendMessage("disconnect-result", {success: true}); - } catch (err) { - browser.test.sendMessage("disconnect-result", { - success: false, - errmsg: err.message, - }); - } - } - }); - browser.test.sendMessage("ready"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - yield extension.awaitMessage("ready"); - - extension.sendMessage("send", "test"); - let response = yield extension.awaitMessage("message"); - equal(response, "test", "Echoed a string"); - - let procCount = yield getSubprocessCount(); - equal(procCount, 1, "subprocess is running"); - - extension.sendMessage("disconnect"); - response = yield extension.awaitMessage("disconnect-result"); - equal(response.success, true, "disconnect succeeded"); - - do_print("waiting for subprocess to exit"); - yield waitForSubprocessExit(); - procCount = yield getSubprocessCount(); - equal(procCount, 0, "subprocess is no longer running"); - - extension.sendMessage("disconnect"); - response = yield extension.awaitMessage("disconnect-result"); - equal(response.success, true, "second call to disconnect silently ignored"); - - yield extension.unload(); -}); - -// Test the limit on message size for writing -add_task(function* test_write_limit() { - Services.prefs.setIntPref(PREF_MAX_WRITE, 10); - function clearPref() { - Services.prefs.clearUserPref(PREF_MAX_WRITE); - } - do_register_cleanup(clearPref); - - function background() { - const PAYLOAD = "0123456789A"; - let port = browser.runtime.connectNative("echo"); - try { - port.postMessage(PAYLOAD); - browser.test.sendMessage("result", null); - } catch (ex) { - browser.test.sendMessage("result", ex.message); - } - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - - let errmsg = yield extension.awaitMessage("result"); - notEqual(errmsg, null, "native postMessage() failed for overly large message"); - - yield extension.unload(); - yield waitForSubprocessExit(); - - clearPref(); -}); - -// Test the limit on message size for reading -add_task(function* test_read_limit() { - Services.prefs.setIntPref(PREF_MAX_READ, 10); - function clearPref() { - Services.prefs.clearUserPref(PREF_MAX_READ); - } - do_register_cleanup(clearPref); - - function background() { - const PAYLOAD = "0123456789A"; - let port = browser.runtime.connectNative("echo"); - port.onDisconnect.addListener(msgPort => { - browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument"); - browser.test.assertEq("Native application tried to send a message of 13 bytes, which exceeds the limit of 10 bytes.", port.error && port.error.message); - browser.test.sendMessage("result", "disconnected"); - }); - port.onMessage.addListener(msg => { - browser.test.sendMessage("result", "message"); - }); - port.postMessage(PAYLOAD); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - - let result = yield extension.awaitMessage("result"); - equal(result, "disconnected", "native port disconnected on receiving large message"); - - yield extension.unload(); - yield waitForSubprocessExit(); - - clearPref(); -}); - -// Test that an extension without the nativeMessaging permission cannot -// use native messaging. -add_task(function* test_ext_permission() { - function background() { - browser.test.assertFalse("connectNative" in chrome.runtime, "chrome.runtime.connectNative does not exist without nativeMessaging permission"); - browser.test.assertFalse("connectNative" in browser.runtime, "browser.runtime.connectNative does not exist without nativeMessaging permission"); - browser.test.assertFalse("sendNativeMessage" in chrome.runtime, "chrome.runtime.sendNativeMessage does not exist without nativeMessaging permission"); - browser.test.assertFalse("sendNativeMessage" in browser.runtime, "browser.runtime.sendNativeMessage does not exist without nativeMessaging permission"); - browser.test.sendMessage("finished"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: {}, - }); - - yield extension.startup(); - yield extension.awaitMessage("finished"); - yield extension.unload(); -}); - -// Test that an extension that is not listed in allowed_extensions for -// a native application cannot use that application. -add_task(function* test_app_permission() { - function background() { - let port = browser.runtime.connectNative("echo"); - port.onDisconnect.addListener(msgPort => { - browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument"); - browser.test.assertEq("This extension does not have permission to use native application echo (or the application is not installed)", port.error && port.error.message); - browser.test.sendMessage("result", "disconnected"); - }); - port.onMessage.addListener(msg => { - browser.test.sendMessage("result", "message"); - }); - port.postMessage({test: "test"}); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - permissions: ["nativeMessaging"], - }, - }, "somethingelse@tests.mozilla.org"); - - yield extension.startup(); - - let result = yield extension.awaitMessage("result"); - equal(result, "disconnected", "connectNative() failed without native app permission"); - - yield extension.unload(); - - let procCount = yield getSubprocessCount(); - equal(procCount, 0, "No child process was started"); -}); - -// Test that the command-line arguments and working directory for the -// native application are as expected. -add_task(function* test_child_process() { - function background() { - let port = browser.runtime.connectNative("info"); - port.onMessage.addListener(msg => { - browser.test.sendMessage("result", msg); - }); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - - let msg = yield extension.awaitMessage("result"); - equal(msg.args.length, 2, "Received one command line argument"); - equal(msg.args[1], getPath("info.json"), "Command line argument is the path to the native host manifest"); - equal(msg.cwd.replace(/^\/private\//, "/"), tmpDir.path, - "Working directory is the directory containing the native appliation"); - - let exitPromise = waitForSubprocessExit(); - yield extension.unload(); - yield exitPromise; -}); - -add_task(function* test_stderr() { - function background() { - let port = browser.runtime.connectNative("stderr"); - port.onDisconnect.addListener(msgPort => { - browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument"); - browser.test.assertEq(null, port.error, "Normal application exit is not an error"); - browser.test.sendMessage("finished"); - }); - } - - let {messages} = yield promiseConsoleOutput(function* () { - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - yield extension.awaitMessage("finished"); - yield extension.unload(); - - yield waitForSubprocessExit(); - }); - - let lines = STDERR_LINES.map(line => messages.findIndex(msg => msg.message.includes(line))); - notEqual(lines[0], -1, "Saw first line of stderr output on the console"); - notEqual(lines[1], -1, "Saw second line of stderr output on the console"); - notEqual(lines[0], lines[1], "Stderr output lines are separated in the console"); -}); - -// Test that calling connectNative() multiple times works -// (bug 1313980 was a previous regression in this area) -add_task(function* test_multiple_connects() { - async function background() { - function once() { - return new Promise(resolve => { - let MSG = "hello"; - let port = browser.runtime.connectNative("echo"); - - port.onMessage.addListener(msg => { - browser.test.assertEq(MSG, msg, "Got expected message back"); - port.disconnect(); - resolve(); - }); - port.postMessage(MSG); - }); - } - - await once(); - await once(); - browser.test.notifyPass("multiple-connect"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("multiple-connect"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging_perf.js b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging_perf.js deleted file mode 100644 index 693f67dde..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging_perf.js +++ /dev/null @@ -1,128 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry", - "resource://testing-common/MockRegistry.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); - -Cu.import("resource://gre/modules/Subprocess.jsm"); - -const MAX_ROUND_TRIP_TIME_MS = AppConstants.DEBUG || AppConstants.ASAN ? 36 : 18; -const MAX_RETRIES = 5; - - -const ECHO_BODY = String.raw` - import struct - import sys - - while True: - rawlen = sys.stdin.read(4) - if len(rawlen) == 0: - sys.exit(0) - - msglen = struct.unpack('@I', rawlen)[0] - msg = sys.stdin.read(msglen) - - sys.stdout.write(struct.pack('@I', msglen)) - sys.stdout.write(msg) -`; - -const SCRIPTS = [ - { - name: "echo", - description: "A native app that echoes back messages it receives", - script: ECHO_BODY.replace(/^ {2}/gm, ""), - }, -]; - -add_task(function* setup() { - yield setupHosts(SCRIPTS); -}); - -add_task(function* test_round_trip_perf() { - let extension = ExtensionTestUtils.loadExtension({ - background() { - browser.test.onMessage.addListener(msg => { - if (msg != "run-tests") { - return; - } - - let port = browser.runtime.connectNative("echo"); - - function next() { - port.postMessage({ - "Lorem": { - "ipsum": { - "dolor": [ - "sit amet", - "consectetur adipiscing elit", - "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - ], - "Ut enim": [ - "ad minim veniam", - "quis nostrud exercitation ullamco", - "laboris nisi ut aliquip ex ea commodo consequat.", - ], - "Duis": [ - "aute irure dolor in reprehenderit in", - "voluptate velit esse cillum dolore eu", - "fugiat nulla pariatur.", - ], - "Excepteur": [ - "sint occaecat cupidatat non proident", - "sunt in culpa qui officia deserunt", - "mollit anim id est laborum.", - ], - }, - }, - }); - } - - const COUNT = 1000; - let now; - function finish() { - let roundTripTime = (Date.now() - now) / COUNT; - - port.disconnect(); - browser.test.sendMessage("result", roundTripTime); - } - - let count = 0; - port.onMessage.addListener(() => { - if (count == 0) { - // Skip the first round, since it includes the time it takes - // the app to start up. - now = Date.now(); - } - - if (count++ <= COUNT) { - next(); - } else { - finish(); - } - }); - - next(); - }); - }, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - - let roundTripTime = Infinity; - for (let i = 0; i < MAX_RETRIES && roundTripTime > MAX_ROUND_TRIP_TIME_MS; i++) { - extension.sendMessage("run-tests"); - roundTripTime = yield extension.awaitMessage("result"); - } - - yield extension.unload(); - - ok(roundTripTime <= MAX_ROUND_TRIP_TIME_MS, - `Expected round trip time (${roundTripTime}ms) to be less than ${MAX_ROUND_TRIP_TIME_MS}ms`); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging_unresponsive.js b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging_unresponsive.js deleted file mode 100644 index a75a1d49d..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging_unresponsive.js +++ /dev/null @@ -1,82 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -const WONTDIE_BODY = String.raw` - import signal - import struct - import sys - import time - - signal.signal(signal.SIGTERM, signal.SIG_IGN) - - def spin(): - while True: - try: - signal.pause() - except AttributeError: - time.sleep(5) - - while True: - rawlen = sys.stdin.read(4) - if len(rawlen) == 0: - spin() - - msglen = struct.unpack('@I', rawlen)[0] - msg = sys.stdin.read(msglen) - - sys.stdout.write(struct.pack('@I', msglen)) - sys.stdout.write(msg) -`; - -const SCRIPTS = [ - { - name: "wontdie", - description: "a native app that does not exit when stdin closes or on SIGTERM", - script: WONTDIE_BODY.replace(/^ {2}/gm, ""), - }, -]; - -add_task(function* setup() { - yield setupHosts(SCRIPTS); -}); - - -// Test that an unresponsive native application still gets killed eventually -add_task(function* test_unresponsive_native_app() { - // XXX expose GRACEFUL_SHUTDOWN_TIME as a pref and reduce it - // just for this test? - - function background() { - let port = browser.runtime.connectNative("wontdie"); - - const MSG = "echo me"; - // bounce a message to make sure the process actually starts - port.onMessage.addListener(msg => { - browser.test.assertEq(msg, MSG, "Received echoed message"); - browser.test.sendMessage("ready"); - }); - port.postMessage(MSG); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - applications: {gecko: {id: ID}}, - permissions: ["nativeMessaging"], - }, - }); - - yield extension.startup(); - yield extension.awaitMessage("ready"); - - let procCount = yield getSubprocessCount(); - equal(procCount, 1, "subprocess is running"); - - let exitPromise = waitForSubprocessExit(); - yield extension.unload(); - yield exitPromise; - - procCount = yield getSubprocessCount(); - equal(procCount, 0, "subprocess was succesfully killed"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_onmessage_removelistener.js b/toolkit/components/extensions/test/xpcshell/test_ext_onmessage_removelistener.js deleted file mode 100644 index 6f8b553fc..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_onmessage_removelistener.js +++ /dev/null @@ -1,30 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -function backgroundScript() { - function listener() { - browser.test.notifyFail("listener should not be invoked"); - } - - browser.runtime.onMessage.addListener(listener); - browser.runtime.onMessage.removeListener(listener); - browser.runtime.sendMessage("hello"); - - // Make sure that, if we somehow fail to remove the listener, then we'll run - // the listener before the test is marked as passing. - setTimeout(function() { - browser.test.notifyPass("onmessage_removelistener"); - }, 0); -} - -let extensionData = { - background: backgroundScript, -}; - -add_task(function* test_contentscript() { - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - yield extension.awaitFinish("onmessage_removelistener"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_connect_no_receiver.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_connect_no_receiver.js deleted file mode 100644 index 2a1342cde..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_connect_no_receiver.js +++ /dev/null @@ -1,23 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_connect_without_listener() { - function background() { - let port = browser.runtime.connect(); - port.onDisconnect.addListener(() => { - browser.test.assertEq("Could not establish connection. Receiving end does not exist.", port.error && port.error.message); - browser.test.notifyPass("port.onDisconnect was called"); - }); - } - let extensionData = { - background, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - - yield extension.awaitFinish("port.onDisconnect was called"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_getBrowserInfo.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_getBrowserInfo.js deleted file mode 100644 index a280206fa..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_getBrowserInfo.js +++ /dev/null @@ -1,26 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"; - -add_task(function* setup() { - ExtensionTestUtils.mockAppInfo(); -}); - -add_task(function* test_getBrowserInfo() { - async function background() { - let info = await browser.runtime.getBrowserInfo(); - - browser.test.assertEq(info.name, "XPCShell", "name is valid"); - browser.test.assertEq(info.vendor, "Mozilla", "vendor is Mozilla"); - browser.test.assertEq(info.version, "48", "version is correct"); - browser.test.assertEq(info.buildID, "20160315", "buildID is correct"); - - browser.test.notifyPass("runtime.getBrowserInfo"); - } - - const extension = ExtensionTestUtils.loadExtension({background}); - yield extension.startup(); - yield extension.awaitFinish("runtime.getBrowserInfo"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_getPlatformInfo.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_getPlatformInfo.js deleted file mode 100644 index 29bad0c10..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_getPlatformInfo.js +++ /dev/null @@ -1,25 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -function backgroundScript() { - browser.runtime.getPlatformInfo(info => { - let validOSs = ["mac", "win", "android", "cros", "linux", "openbsd"]; - let validArchs = ["arm", "x86-32", "x86-64"]; - - browser.test.assertTrue(validOSs.indexOf(info.os) != -1, "OS is valid"); - browser.test.assertTrue(validArchs.indexOf(info.arch) != -1, "Architecture is valid"); - browser.test.notifyPass("runtime.getPlatformInfo"); - }); -} - -let extensionData = { - background: backgroundScript, -}; - -add_task(function* test_contentscript() { - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - yield extension.awaitFinish("runtime.getPlatformInfo"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js deleted file mode 100644 index fa6461412..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js +++ /dev/null @@ -1,337 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -XPCOMUtils.defineLazyGetter(this, "Management", () => { - const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {}); - return Management; -}); - -const { - createAppInfo, - createTempWebExtensionFile, - promiseAddonByID, - promiseAddonEvent, - promiseCompleteAllInstalls, - promiseFindAddonUpdates, - promiseRestartManager, - promiseShutdownManager, - promiseStartupManager, -} = AddonTestUtils; - -AddonTestUtils.init(this); - -// Allow for unsigned addons. -AddonTestUtils.overrideCertDB(); - -createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42"); - -function awaitEvent(eventName) { - return new Promise(resolve => { - let listener = (_eventName, ...args) => { - if (_eventName === eventName) { - Management.off(eventName, listener); - resolve(...args); - } - }; - - Management.on(eventName, listener); - }); -} - -function background() { - let onInstalledDetails = null; - let onStartupFired = false; - - browser.runtime.onInstalled.addListener(details => { - onInstalledDetails = details; - }); - - browser.runtime.onStartup.addListener(() => { - onStartupFired = true; - }); - - browser.test.onMessage.addListener(message => { - if (message === "get-on-installed-details") { - onInstalledDetails = onInstalledDetails || {fired: false}; - browser.test.sendMessage("on-installed-details", onInstalledDetails); - } else if (message === "did-on-startup-fire") { - browser.test.sendMessage("on-startup-fired", onStartupFired); - } else if (message === "reload-extension") { - browser.runtime.reload(); - } - }); - - browser.runtime.onUpdateAvailable.addListener(details => { - browser.test.sendMessage("reloading"); - browser.runtime.reload(); - }); -} - -function* expectEvents(extension, {onStartupFired, onInstalledFired, onInstalledReason}) { - extension.sendMessage("get-on-installed-details"); - let details = yield extension.awaitMessage("on-installed-details"); - if (onInstalledFired) { - equal(details.reason, onInstalledReason, "runtime.onInstalled fired with the correct reason"); - } else { - equal(details.fired, onInstalledFired, "runtime.onInstalled should not have fired"); - } - - extension.sendMessage("did-on-startup-fire"); - let fired = yield extension.awaitMessage("on-startup-fired"); - equal(fired, onStartupFired, `Expected runtime.onStartup to ${onStartupFired ? "" : "not "} fire`); -} - -add_task(function* test_should_fire_on_addon_update() { - const EXTENSION_ID = "test_runtime_on_installed_addon_update@tests.mozilla.org"; - - const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; - - // The test extension uses an insecure update url. - Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); - - const testServer = createHttpServer(); - const port = testServer.identity.primaryPort; - - let extension = ExtensionTestUtils.loadExtension({ - useAddonManager: "permanent", - manifest: { - "version": "1.0", - "applications": { - "gecko": { - "id": EXTENSION_ID, - "update_url": `http://localhost:${port}/test_update.json`, - }, - }, - }, - background, - }); - - testServer.registerPathHandler("/test_update.json", (request, response) => { - response.write(`{ - "addons": { - "${EXTENSION_ID}": { - "updates": [ - { - "version": "2.0", - "update_link": "http://localhost:${port}/addons/test_runtime_on_installed-2.0.xpi" - } - ] - } - } - }`); - }); - - let webExtensionFile = createTempWebExtensionFile({ - manifest: { - version: "2.0", - applications: { - gecko: { - id: EXTENSION_ID, - }, - }, - }, - background, - }); - - testServer.registerFile("/addons/test_runtime_on_installed-2.0.xpi", webExtensionFile); - - yield promiseStartupManager(); - - yield extension.startup(); - - yield expectEvents(extension, { - onStartupFired: false, - onInstalledFired: true, - onInstalledReason: "install", - }); - - let addon = yield promiseAddonByID(EXTENSION_ID); - equal(addon.version, "1.0", "The installed addon has the correct version"); - - let update = yield promiseFindAddonUpdates(addon); - let install = update.updateAvailable; - - let promiseInstalled = promiseAddonEvent("onInstalled"); - yield promiseCompleteAllInstalls([install]); - - yield extension.awaitMessage("reloading"); - - let startupPromise = awaitEvent("ready"); - - let [updated_addon] = yield promiseInstalled; - equal(updated_addon.version, "2.0", "The updated addon has the correct version"); - - extension.extension = yield startupPromise; - extension.attachListeners(); - - yield expectEvents(extension, { - onStartupFired: false, - onInstalledFired: true, - onInstalledReason: "update", - }); - - yield extension.unload(); - - yield updated_addon.uninstall(); - yield promiseShutdownManager(); -}); - -add_task(function* test_should_fire_on_browser_update() { - const EXTENSION_ID = "test_runtime_on_installed_browser_update@tests.mozilla.org"; - - yield promiseStartupManager(); - - let extension = ExtensionTestUtils.loadExtension({ - useAddonManager: "permanent", - manifest: { - "version": "1.0", - "applications": { - "gecko": { - "id": EXTENSION_ID, - }, - }, - }, - background, - }); - - yield extension.startup(); - - yield expectEvents(extension, { - onStartupFired: false, - onInstalledFired: true, - onInstalledReason: "install", - }); - - let startupPromise = awaitEvent("ready"); - yield promiseRestartManager("1"); - extension.extension = yield startupPromise; - extension.attachListeners(); - - yield expectEvents(extension, { - onStartupFired: true, - onInstalledFired: false, - }); - - // Update the browser. - startupPromise = awaitEvent("ready"); - yield promiseRestartManager("2"); - extension.extension = yield startupPromise; - extension.attachListeners(); - - yield expectEvents(extension, { - onStartupFired: true, - onInstalledFired: true, - onInstalledReason: "browser_update", - }); - - // Restart the browser. - startupPromise = awaitEvent("ready"); - yield promiseRestartManager("2"); - extension.extension = yield startupPromise; - extension.attachListeners(); - - yield expectEvents(extension, { - onStartupFired: true, - onInstalledFired: false, - }); - - // Update the browser again. - startupPromise = awaitEvent("ready"); - yield promiseRestartManager("3"); - extension.extension = yield startupPromise; - extension.attachListeners(); - - yield expectEvents(extension, { - onStartupFired: true, - onInstalledFired: true, - onInstalledReason: "browser_update", - }); - - yield extension.unload(); - - yield promiseShutdownManager(); -}); - -add_task(function* test_should_not_fire_on_reload() { - const EXTENSION_ID = "test_runtime_on_installed_reload@tests.mozilla.org"; - - yield promiseStartupManager(); - - let extension = ExtensionTestUtils.loadExtension({ - useAddonManager: "permanent", - manifest: { - "version": "1.0", - "applications": { - "gecko": { - "id": EXTENSION_ID, - }, - }, - }, - background, - }); - - yield extension.startup(); - - yield expectEvents(extension, { - onStartupFired: false, - onInstalledFired: true, - onInstalledReason: "install", - }); - - let startupPromise = awaitEvent("ready"); - extension.sendMessage("reload-extension"); - extension.extension = yield startupPromise; - extension.attachListeners(); - - yield expectEvents(extension, { - onStartupFired: false, - onInstalledFired: false, - }); - - yield extension.unload(); - yield promiseShutdownManager(); -}); - -add_task(function* test_should_not_fire_on_restart() { - const EXTENSION_ID = "test_runtime_on_installed_restart@tests.mozilla.org"; - - yield promiseStartupManager(); - - let extension = ExtensionTestUtils.loadExtension({ - useAddonManager: "permanent", - manifest: { - "version": "1.0", - "applications": { - "gecko": { - "id": EXTENSION_ID, - }, - }, - }, - background, - }); - - yield extension.startup(); - - yield expectEvents(extension, { - onStartupFired: false, - onInstalledFired: true, - onInstalledReason: "install", - }); - - let addon = yield promiseAddonByID(EXTENSION_ID); - addon.userDisabled = true; - - let startupPromise = awaitEvent("ready"); - addon.userDisabled = false; - extension.extension = yield startupPromise; - extension.attachListeners(); - - yield expectEvents(extension, { - onStartupFired: false, - onInstalledFired: false, - }); - - yield extension.markUnloaded(); - yield promiseShutdownManager(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js deleted file mode 100644 index fec8e13dd..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js +++ /dev/null @@ -1,79 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* tabsSendMessageReply() { - function background() { - browser.runtime.onMessage.addListener((msg, sender, respond) => { - if (msg == "respond-now") { - respond(msg); - } else if (msg == "respond-soon") { - setTimeout(() => { respond(msg); }, 0); - return true; - } else if (msg == "respond-promise") { - return Promise.resolve(msg); - } else if (msg == "respond-never") { - return; - } else if (msg == "respond-error") { - return Promise.reject(new Error(msg)); - } else if (msg == "throw-error") { - throw new Error(msg); - } - }); - - browser.runtime.onMessage.addListener((msg, sender, respond) => { - if (msg == "respond-now") { - respond("hello"); - } else if (msg == "respond-now-2") { - respond(msg); - } - }); - - let childFrame = document.createElement("iframe"); - childFrame.src = "extensionpage.html"; - document.body.appendChild(childFrame); - } - - function senderScript() { - Promise.all([ - browser.runtime.sendMessage("respond-now"), - browser.runtime.sendMessage("respond-now-2"), - new Promise(resolve => browser.runtime.sendMessage("respond-soon", resolve)), - browser.runtime.sendMessage("respond-promise"), - browser.runtime.sendMessage("respond-never"), - new Promise(resolve => { - browser.runtime.sendMessage("respond-never", response => { resolve(response); }); - }), - - browser.runtime.sendMessage("respond-error").catch(error => Promise.resolve({error})), - browser.runtime.sendMessage("throw-error").catch(error => Promise.resolve({error})), - ]).then(([respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondNever2, respondError, throwError]) => { - browser.test.assertEq("respond-now", respondNow, "Got the expected immediate response"); - browser.test.assertEq("respond-now-2", respondNow2, "Got the expected immediate response from the second listener"); - browser.test.assertEq("respond-soon", respondSoon, "Got the expected delayed response"); - browser.test.assertEq("respond-promise", respondPromise, "Got the expected promise response"); - browser.test.assertEq(undefined, respondNever, "Got the expected no-response resolution"); - browser.test.assertEq(undefined, respondNever2, "Got the expected no-response resolution"); - - browser.test.assertEq("respond-error", respondError.error.message, "Got the expected error response"); - browser.test.assertEq("throw-error", throwError.error.message, "Got the expected thrown error response"); - - browser.test.notifyPass("sendMessage"); - }).catch(e => { - browser.test.fail(`Error: ${e} :: ${e.stack}`); - browser.test.notifyFail("sendMessage"); - }); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - files: { - "senderScript.js": senderScript, - "extensionpage.html": ``, - }, - }); - - yield extension.startup(); - yield extension.awaitFinish("sendMessage"); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js deleted file mode 100644 index f1a8d5a36..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js +++ /dev/null @@ -1,59 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_sendMessage_error() { - async function background() { - let circ = {}; - circ.circ = circ; - let testCases = [ - // [arguments, expected error string], - [[], "runtime.sendMessage's message argument is missing"], - [[null, null, null, null], "runtime.sendMessage's last argument is not a function"], - [[null, null, 1], "runtime.sendMessage's options argument is invalid"], - [[1, null, null], "runtime.sendMessage's extensionId argument is invalid"], - [[null, null, null, null, null], "runtime.sendMessage received too many arguments"], - - // Even when the parameters are accepted, we still expect an error - // because there is no onMessage listener. - [[null, null, null], "Could not establish connection. Receiving end does not exist."], - - // Structural cloning doesn't work with DOM but we fall back - // JSON serialization, so we don't expect another error. - [[null, location, null], "Could not establish connection. Receiving end does not exist."], - - // Structured cloning supports cyclic self-references. - [[null, [circ, location], null], "cyclic object value"], - // JSON serialization does not support cyclic references. - [[null, circ, null], "Could not establish connection. Receiving end does not exist."], - // (the last two tests shows whether sendMessage is implemented as structured cloning). - ]; - - // Repeat all tests with the undefined value instead of null. - for (let [args, expectedError] of testCases.slice()) { - args = args.map(arg => arg === null ? undefined : arg); - testCases.push([args, expectedError]); - } - - for (let [args, expectedError] of testCases) { - let description = `runtime.sendMessage(${args.map(String).join(", ")})`; - - await browser.test.assertRejects( - browser.runtime.sendMessage(...args), - expectedError, - `expected error message for ${description}`); - } - - browser.test.notifyPass("sendMessage parameter validation"); - } - let extensionData = { - background, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - - yield extension.awaitFinish("sendMessage parameter validation"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_no_receiver.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_no_receiver.js deleted file mode 100644 index f906333d2..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_no_receiver.js +++ /dev/null @@ -1,54 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_sendMessage_without_listener() { - async function background() { - await browser.test.assertRejects( - browser.runtime.sendMessage("msg"), - "Could not establish connection. Receiving end does not exist.", - "sendMessage callback was invoked"); - - browser.test.notifyPass("sendMessage callback was invoked"); - } - let extensionData = { - background, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - - yield extension.awaitFinish("sendMessage callback was invoked"); - - yield extension.unload(); -}); - -add_task(function* test_chrome_sendMessage_without_listener() { - function background() { - /* globals chrome */ - browser.test.assertEq(null, chrome.runtime.lastError, "no lastError before call"); - let retval = chrome.runtime.sendMessage("msg"); - browser.test.assertEq(null, chrome.runtime.lastError, "no lastError after call"); - browser.test.assertEq(undefined, retval, "return value of chrome.runtime.sendMessage without callback"); - - let isAsyncCall = false; - retval = chrome.runtime.sendMessage("msg", reply => { - browser.test.assertEq(undefined, reply, "no reply"); - browser.test.assertTrue(isAsyncCall, "chrome.runtime.sendMessage's callback must be called asynchronously"); - browser.test.assertEq(undefined, retval, "return value of chrome.runtime.sendMessage with callback"); - browser.test.assertEq("Could not establish connection. Receiving end does not exist.", chrome.runtime.lastError.message); - browser.test.notifyPass("finished chrome.runtime.sendMessage"); - }); - isAsyncCall = true; - } - let extensionData = { - background, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - - yield extension.awaitFinish("finished chrome.runtime.sendMessage"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_self.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_self.js deleted file mode 100644 index e4f5e951f..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_self.js +++ /dev/null @@ -1,51 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ - -"use strict"; - -add_task(function* test_sendMessage_to_self_should_not_trigger_onMessage() { - async function background() { - browser.runtime.onMessage.addListener(msg => { - browser.test.assertEq("msg from child", msg); - browser.test.notifyPass("sendMessage did not call same-frame onMessage"); - }); - - browser.test.onMessage.addListener(msg => { - browser.test.assertEq("sendMessage with a listener in another frame", msg); - browser.runtime.sendMessage("should only reach another frame"); - }); - - await browser.test.assertRejects( - browser.runtime.sendMessage("should not trigger same-frame onMessage"), - "Could not establish connection. Receiving end does not exist."); - - let anotherFrame = document.createElement("iframe"); - anotherFrame.src = browser.extension.getURL("extensionpage.html"); - document.body.appendChild(anotherFrame); - } - - function lastScript() { - browser.runtime.onMessage.addListener(msg => { - browser.test.assertEq("should only reach another frame", msg); - browser.runtime.sendMessage("msg from child"); - }); - browser.test.sendMessage("sendMessage callback called"); - } - - let extensionData = { - background, - files: { - "lastScript.js": lastScript, - "extensionpage.html": ``, - }, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - - yield extension.awaitMessage("sendMessage callback called"); - extension.sendMessage("sendMessage with a listener in another frame"); - yield extension.awaitFinish("sendMessage did not call same-frame onMessage"); - - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js deleted file mode 100644 index d838be5b5..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js +++ /dev/null @@ -1,1427 +0,0 @@ -"use strict"; - -Components.utils.import("resource://gre/modules/Schemas.jsm"); -Components.utils.import("resource://gre/modules/BrowserUtils.jsm"); -Components.utils.import("resource://gre/modules/ExtensionCommon.jsm"); - -let {LocalAPIImplementation, SchemaAPIInterface} = ExtensionCommon; - -let json = [ - {namespace: "testing", - - properties: { - PROP1: {value: 20}, - prop2: {type: "string"}, - prop3: { - $ref: "submodule", - }, - prop4: { - $ref: "submodule", - unsupported: true, - }, - }, - - types: [ - { - id: "type1", - type: "string", - "enum": ["value1", "value2", "value3"], - }, - - { - id: "type2", - type: "object", - properties: { - prop1: {type: "integer"}, - prop2: {type: "array", items: {"$ref": "type1"}}, - }, - }, - - { - id: "basetype1", - type: "object", - properties: { - prop1: {type: "string"}, - }, - }, - - { - id: "basetype2", - choices: [ - {type: "integer"}, - ], - }, - - { - $extend: "basetype1", - properties: { - prop2: {type: "string"}, - }, - }, - - { - $extend: "basetype2", - choices: [ - {type: "string"}, - ], - }, - - { - id: "submodule", - type: "object", - functions: [ - { - name: "sub_foo", - type: "function", - parameters: [], - returns: "integer", - }, - ], - }, - ], - - functions: [ - { - name: "foo", - type: "function", - parameters: [ - {name: "arg1", type: "integer", optional: true, default: 99}, - {name: "arg2", type: "boolean", optional: true}, - ], - }, - - { - name: "bar", - type: "function", - parameters: [ - {name: "arg1", type: "integer", optional: true}, - {name: "arg2", type: "boolean"}, - ], - }, - - { - name: "baz", - type: "function", - parameters: [ - {name: "arg1", type: "object", properties: { - prop1: {type: "string"}, - prop2: {type: "integer", optional: true}, - prop3: {type: "integer", unsupported: true}, - }}, - ], - }, - - { - name: "qux", - type: "function", - parameters: [ - {name: "arg1", "$ref": "type1"}, - ], - }, - - { - name: "quack", - type: "function", - parameters: [ - {name: "arg1", "$ref": "type2"}, - ], - }, - - { - name: "quora", - type: "function", - parameters: [ - {name: "arg1", type: "function"}, - ], - }, - - { - name: "quileute", - type: "function", - parameters: [ - {name: "arg1", type: "integer", optional: true}, - {name: "arg2", type: "integer"}, - ], - }, - - { - name: "queets", - type: "function", - unsupported: true, - parameters: [], - }, - - { - name: "quintuplets", - type: "function", - parameters: [ - {name: "obj", type: "object", properties: [], additionalProperties: {type: "integer"}}, - ], - }, - - { - name: "quasar", - type: "function", - parameters: [ - {name: "abc", type: "object", properties: { - func: {type: "function", parameters: [ - {name: "x", type: "integer"}, - ]}, - }}, - ], - }, - - { - name: "quosimodo", - type: "function", - parameters: [ - {name: "xyz", type: "object", additionalProperties: {type: "any"}}, - ], - }, - - { - name: "patternprop", - type: "function", - parameters: [ - { - name: "obj", - type: "object", - properties: {"prop1": {type: "string", pattern: "^\\d+$"}}, - patternProperties: { - "(?i)^prop\\d+$": {type: "string"}, - "^foo\\d+$": {type: "string"}, - }, - }, - ], - }, - - { - name: "pattern", - type: "function", - parameters: [ - {name: "arg", type: "string", pattern: "(?i)^[0-9a-f]+$"}, - ], - }, - - { - name: "format", - type: "function", - parameters: [ - { - name: "arg", - type: "object", - properties: { - url: {type: "string", "format": "url", "optional": true}, - relativeUrl: {type: "string", "format": "relativeUrl", "optional": true}, - strictRelativeUrl: {type: "string", "format": "strictRelativeUrl", "optional": true}, - }, - }, - ], - }, - - { - name: "formatDate", - type: "function", - parameters: [ - { - name: "arg", - type: "object", - properties: { - date: {type: "string", format: "date", optional: true}, - }, - }, - ], - }, - - { - name: "deep", - type: "function", - parameters: [ - { - name: "arg", - type: "object", - properties: { - foo: { - type: "object", - properties: { - bar: { - type: "array", - items: { - type: "object", - properties: { - baz: { - type: "object", - properties: { - required: {type: "integer"}, - optional: {type: "string", optional: true}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - ], - }, - - { - name: "errors", - type: "function", - parameters: [ - { - name: "arg", - type: "object", - properties: { - warn: { - type: "string", - pattern: "^\\d+$", - optional: true, - onError: "warn", - }, - ignore: { - type: "string", - pattern: "^\\d+$", - optional: true, - onError: "ignore", - }, - default: { - type: "string", - pattern: "^\\d+$", - optional: true, - }, - }, - }, - ], - }, - - { - name: "localize", - type: "function", - parameters: [ - { - name: "arg", - type: "object", - properties: { - foo: {type: "string", "preprocess": "localize", "optional": true}, - bar: {type: "string", "optional": true}, - url: {type: "string", "preprocess": "localize", "format": "url", "optional": true}, - }, - }, - ], - }, - - { - name: "extended1", - type: "function", - parameters: [ - {name: "val", $ref: "basetype1"}, - ], - }, - - { - name: "extended2", - type: "function", - parameters: [ - {name: "val", $ref: "basetype2"}, - ], - }, - ], - - events: [ - { - name: "onFoo", - type: "function", - }, - - { - name: "onBar", - type: "function", - extraParameters: [{ - name: "filter", - type: "integer", - optional: true, - default: 1, - }], - }, - ], - }, - { - namespace: "foreign", - properties: { - foreignRef: {$ref: "testing.submodule"}, - }, - }, - { - namespace: "inject", - properties: { - PROP1: {value: "should inject"}, - }, - }, - { - namespace: "do-not-inject", - properties: { - PROP1: {value: "should not inject"}, - }, - }, -]; - -let tallied = null; - -function tally(kind, ns, name, args) { - tallied = [kind, ns, name, args]; -} - -function verify(...args) { - do_check_eq(JSON.stringify(tallied), JSON.stringify(args)); - tallied = null; -} - -let talliedErrors = []; - -function checkErrors(errors) { - do_check_eq(talliedErrors.length, errors.length, "Got expected number of errors"); - for (let [i, error] of errors.entries()) { - do_check_true(i in talliedErrors && talliedErrors[i].includes(error), - `${JSON.stringify(error)} is a substring of error ${JSON.stringify(talliedErrors[i])}`); - } - - talliedErrors.length = 0; -} - -let permissions = new Set(); - -class TallyingAPIImplementation extends SchemaAPIInterface { - constructor(namespace, name) { - super(); - this.namespace = namespace; - this.name = name; - } - - callFunction(args) { - tally("call", this.namespace, this.name, args); - } - - callFunctionNoReturn(args) { - tally("call", this.namespace, this.name, args); - } - - getProperty() { - tally("get", this.namespace, this.name); - } - - setProperty(value) { - tally("set", this.namespace, this.name, value); - } - - addListener(listener, args) { - tally("addListener", this.namespace, this.name, [listener, args]); - } - - removeListener(listener) { - tally("removeListener", this.namespace, this.name, [listener]); - } - - hasListener(listener) { - tally("hasListener", this.namespace, this.name, [listener]); - } -} - -let wrapper = { - url: "moz-extension://b66e3509-cdb3-44f6-8eb8-c8b39b3a1d27/", - - checkLoadURL(url) { - return !url.startsWith("chrome:"); - }, - - preprocessors: { - localize(value, context) { - return value.replace(/__MSG_(.*?)__/g, (m0, m1) => `${m1.toUpperCase()}`); - }, - }, - - logError(message) { - talliedErrors.push(message); - }, - - hasPermission(permission) { - return permissions.has(permission); - }, - - shouldInject(ns) { - return ns != "do-not-inject"; - }, - - getImplementation(namespace, name) { - return new TallyingAPIImplementation(namespace, name); - }, -}; - -add_task(function* () { - let url = "data:," + JSON.stringify(json); - yield Schemas.load(url); - - let root = {}; - tallied = null; - Schemas.inject(root, wrapper); - do_check_eq(tallied, null); - - do_check_eq(root.testing.PROP1, 20, "simple value property"); - do_check_eq(root.testing.type1.VALUE1, "value1", "enum type"); - do_check_eq(root.testing.type1.VALUE2, "value2", "enum type"); - - do_check_eq("inject" in root, true, "namespace 'inject' should be injected"); - do_check_eq("do-not-inject" in root, false, "namespace 'do-not-inject' should not be injected"); - - root.testing.foo(11, true); - verify("call", "testing", "foo", [11, true]); - - root.testing.foo(true); - verify("call", "testing", "foo", [99, true]); - - root.testing.foo(null, true); - verify("call", "testing", "foo", [99, true]); - - root.testing.foo(undefined, true); - verify("call", "testing", "foo", [99, true]); - - root.testing.foo(11); - verify("call", "testing", "foo", [11, null]); - - Assert.throws(() => root.testing.bar(11), - /Incorrect argument types/, - "should throw without required arg"); - - Assert.throws(() => root.testing.bar(11, true, 10), - /Incorrect argument types/, - "should throw with too many arguments"); - - root.testing.bar(true); - verify("call", "testing", "bar", [null, true]); - - root.testing.baz({prop1: "hello", prop2: 22}); - verify("call", "testing", "baz", [{prop1: "hello", prop2: 22}]); - - root.testing.baz({prop1: "hello"}); - verify("call", "testing", "baz", [{prop1: "hello", prop2: null}]); - - root.testing.baz({prop1: "hello", prop2: null}); - verify("call", "testing", "baz", [{prop1: "hello", prop2: null}]); - - Assert.throws(() => root.testing.baz({prop2: 12}), - /Property "prop1" is required/, - "should throw without required property"); - - Assert.throws(() => root.testing.baz({prop1: "hi", prop3: 12}), - /Property "prop3" is unsupported by Firefox/, - "should throw with unsupported property"); - - Assert.throws(() => root.testing.baz({prop1: "hi", prop4: 12}), - /Unexpected property "prop4"/, - "should throw with unexpected property"); - - Assert.throws(() => root.testing.baz({prop1: 12}), - /Expected string instead of 12/, - "should throw with wrong type"); - - root.testing.qux("value2"); - verify("call", "testing", "qux", ["value2"]); - - Assert.throws(() => root.testing.qux("value4"), - /Invalid enumeration value "value4"/, - "should throw for invalid enum value"); - - root.testing.quack({prop1: 12, prop2: ["value1", "value3"]}); - verify("call", "testing", "quack", [{prop1: 12, prop2: ["value1", "value3"]}]); - - Assert.throws(() => root.testing.quack({prop1: 12, prop2: ["value1", "value3", "value4"]}), - /Invalid enumeration value "value4"/, - "should throw for invalid array type"); - - function f() {} - root.testing.quora(f); - do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["call", "testing", "quora"])); - do_check_eq(tallied[3][0], f); - tallied = null; - - let g = () => 0; - root.testing.quora(g); - do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["call", "testing", "quora"])); - do_check_eq(tallied[3][0], g); - tallied = null; - - root.testing.quileute(10); - verify("call", "testing", "quileute", [null, 10]); - - Assert.throws(() => root.testing.queets(), - /queets is not a function/, - "should throw for unsupported functions"); - - root.testing.quintuplets({a: 10, b: 20, c: 30}); - verify("call", "testing", "quintuplets", [{a: 10, b: 20, c: 30}]); - - Assert.throws(() => root.testing.quintuplets({a: 10, b: 20, c: 30, d: "hi"}), - /Expected integer instead of "hi"/, - "should throw for wrong additionalProperties type"); - - root.testing.quasar({func: f}); - do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["call", "testing", "quasar"])); - do_check_eq(tallied[3][0].func, f); - tallied = null; - - root.testing.quosimodo({a: 10, b: 20, c: 30}); - verify("call", "testing", "quosimodo", [{a: 10, b: 20, c: 30}]); - tallied = null; - - Assert.throws(() => root.testing.quosimodo(10), - /Incorrect argument types/, - "should throw for wrong type"); - - root.testing.patternprop({prop1: "12", prop2: "42", Prop3: "43", foo1: "x"}); - verify("call", "testing", "patternprop", [{prop1: "12", prop2: "42", Prop3: "43", foo1: "x"}]); - tallied = null; - - root.testing.patternprop({prop1: "12"}); - verify("call", "testing", "patternprop", [{prop1: "12"}]); - tallied = null; - - Assert.throws(() => root.testing.patternprop({prop1: "12", foo1: null}), - /Expected string instead of null/, - "should throw for wrong property type"); - - Assert.throws(() => root.testing.patternprop({prop1: "xx", prop2: "yy"}), - /String "xx" must match \/\^\\d\+\$\//, - "should throw for wrong property type"); - - Assert.throws(() => root.testing.patternprop({prop1: "12", prop2: 42}), - /Expected string instead of 42/, - "should throw for wrong property type"); - - Assert.throws(() => root.testing.patternprop({prop1: "12", prop2: null}), - /Expected string instead of null/, - "should throw for wrong property type"); - - Assert.throws(() => root.testing.patternprop({prop1: "12", propx: "42"}), - /Unexpected property "propx"/, - "should throw for unexpected property"); - - Assert.throws(() => root.testing.patternprop({prop1: "12", Foo1: "x"}), - /Unexpected property "Foo1"/, - "should throw for unexpected property"); - - root.testing.pattern("DEADbeef"); - verify("call", "testing", "pattern", ["DEADbeef"]); - tallied = null; - - Assert.throws(() => root.testing.pattern("DEADcow"), - /String "DEADcow" must match \/\^\[0-9a-f\]\+\$\/i/, - "should throw for non-match"); - - root.testing.format({url: "http://foo/bar", - relativeUrl: "http://foo/bar"}); - verify("call", "testing", "format", [{url: "http://foo/bar", - relativeUrl: "http://foo/bar", - strictRelativeUrl: null}]); - tallied = null; - - root.testing.format({relativeUrl: "foo.html", strictRelativeUrl: "foo.html"}); - verify("call", "testing", "format", [{url: null, - relativeUrl: `${wrapper.url}foo.html`, - strictRelativeUrl: `${wrapper.url}foo.html`}]); - tallied = null; - - for (let format of ["url", "relativeUrl"]) { - Assert.throws(() => root.testing.format({[format]: "chrome://foo/content/"}), - /Access denied/, - "should throw for access denied"); - } - - for (let urlString of ["//foo.html", "http://foo/bar.html"]) { - Assert.throws(() => root.testing.format({strictRelativeUrl: urlString}), - /must be a relative URL/, - "should throw for non-relative URL"); - } - - const dates = [ - "2016-03-04", - "2016-03-04T08:00:00Z", - "2016-03-04T08:00:00.000Z", - "2016-03-04T08:00:00-08:00", - "2016-03-04T08:00:00.000-08:00", - "2016-03-04T08:00:00+08:00", - "2016-03-04T08:00:00.000+08:00", - "2016-03-04T08:00:00+0800", - "2016-03-04T08:00:00-0800", - ]; - dates.forEach(str => { - root.testing.formatDate({date: str}); - verify("call", "testing", "formatDate", [{date: str}]); - }); - - // Make sure that a trivial change to a valid date invalidates it. - dates.forEach(str => { - Assert.throws(() => root.testing.formatDate({date: "0" + str}), - /Invalid date string/, - "should throw for invalid iso date string"); - Assert.throws(() => root.testing.formatDate({date: str + "0"}), - /Invalid date string/, - "should throw for invalid iso date string"); - }); - - const badDates = [ - "I do not look anything like a date string", - "2016-99-99", - "2016-03-04T25:00:00Z", - ]; - badDates.forEach(str => { - Assert.throws(() => root.testing.formatDate({date: str}), - /Invalid date string/, - "should throw for invalid iso date string"); - }); - - root.testing.deep({foo: {bar: [{baz: {required: 12, optional: "42"}}]}}); - verify("call", "testing", "deep", [{foo: {bar: [{baz: {required: 12, optional: "42"}}]}}]); - tallied = null; - - Assert.throws(() => root.testing.deep({foo: {bar: [{baz: {optional: "42"}}]}}), - /Type error for parameter arg \(Error processing foo\.bar\.0\.baz: Property "required" is required\) for testing\.deep/, - "should throw with the correct object path"); - - Assert.throws(() => root.testing.deep({foo: {bar: [{baz: {required: 12, optional: 42}}]}}), - /Type error for parameter arg \(Error processing foo\.bar\.0\.baz\.optional: Expected string instead of 42\) for testing\.deep/, - "should throw with the correct object path"); - - - talliedErrors.length = 0; - - root.testing.errors({warn: "0123", ignore: "0123", default: "0123"}); - verify("call", "testing", "errors", [{warn: "0123", ignore: "0123", default: "0123"}]); - checkErrors([]); - - root.testing.errors({warn: "0123", ignore: "x123", default: "0123"}); - verify("call", "testing", "errors", [{warn: "0123", ignore: null, default: "0123"}]); - checkErrors([]); - - root.testing.errors({warn: "x123", ignore: "0123", default: "0123"}); - verify("call", "testing", "errors", [{warn: null, ignore: "0123", default: "0123"}]); - checkErrors([ - 'String "x123" must match /^\\d+$/', - ]); - - - root.testing.onFoo.addListener(f); - do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["addListener", "testing", "onFoo"])); - do_check_eq(tallied[3][0], f); - do_check_eq(JSON.stringify(tallied[3][1]), JSON.stringify([])); - tallied = null; - - root.testing.onFoo.removeListener(f); - do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["removeListener", "testing", "onFoo"])); - do_check_eq(tallied[3][0], f); - tallied = null; - - root.testing.onFoo.hasListener(f); - do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["hasListener", "testing", "onFoo"])); - do_check_eq(tallied[3][0], f); - tallied = null; - - Assert.throws(() => root.testing.onFoo.addListener(10), - /Invalid listener/, - "addListener with non-function should throw"); - - root.testing.onBar.addListener(f, 10); - do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["addListener", "testing", "onBar"])); - do_check_eq(tallied[3][0], f); - do_check_eq(JSON.stringify(tallied[3][1]), JSON.stringify([10])); - tallied = null; - - root.testing.onBar.addListener(f); - do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["addListener", "testing", "onBar"])); - do_check_eq(tallied[3][0], f); - do_check_eq(JSON.stringify(tallied[3][1]), JSON.stringify([1])); - tallied = null; - - Assert.throws(() => root.testing.onBar.addListener(f, "hi"), - /Incorrect argument types/, - "addListener with wrong extra parameter should throw"); - - let target = {prop1: 12, prop2: ["value1", "value3"]}; - let proxy = new Proxy(target, {}); - Assert.throws(() => root.testing.quack(proxy), - /Expected a plain JavaScript object, got a Proxy/, - "should throw when passing a Proxy"); - - if (Symbol.toStringTag) { - let stringTarget = {prop1: 12, prop2: ["value1", "value3"]}; - stringTarget[Symbol.toStringTag] = () => "[object Object]"; - let stringProxy = new Proxy(stringTarget, {}); - Assert.throws(() => root.testing.quack(stringProxy), - /Expected a plain JavaScript object, got a Proxy/, - "should throw when passing a Proxy"); - } - - - root.testing.localize({foo: "__MSG_foo__", bar: "__MSG_foo__", url: "__MSG_http://example.com/__"}); - verify("call", "testing", "localize", [{foo: "FOO", bar: "__MSG_foo__", url: "http://example.com/"}]); - tallied = null; - - - Assert.throws(() => root.testing.localize({url: "__MSG_/foo/bar__"}), - /\/FOO\/BAR is not a valid URL\./, - "should throw for invalid URL"); - - - root.testing.extended1({prop1: "foo", prop2: "bar"}); - verify("call", "testing", "extended1", [{prop1: "foo", prop2: "bar"}]); - tallied = null; - - Assert.throws(() => root.testing.extended1({prop1: "foo", prop2: 12}), - /Expected string instead of 12/, - "should throw for wrong property type"); - - Assert.throws(() => root.testing.extended1({prop1: "foo"}), - /Property "prop2" is required/, - "should throw for missing property"); - - Assert.throws(() => root.testing.extended1({prop1: "foo", prop2: "bar", prop3: "xxx"}), - /Unexpected property "prop3"/, - "should throw for extra property"); - - - root.testing.extended2("foo"); - verify("call", "testing", "extended2", ["foo"]); - tallied = null; - - root.testing.extended2(12); - verify("call", "testing", "extended2", [12]); - tallied = null; - - Assert.throws(() => root.testing.extended2(true), - /Incorrect argument types/, - "should throw for wrong argument type"); - - root.testing.prop3.sub_foo(); - verify("call", "testing.prop3", "sub_foo", []); - tallied = null; - - Assert.throws(() => root.testing.prop4.sub_foo(), - /root.testing.prop4 is undefined/, - "should throw for unsupported submodule"); - - root.foreign.foreignRef.sub_foo(); - verify("call", "foreign.foreignRef", "sub_foo", []); - tallied = null; -}); - -let deprecatedJson = [ - {namespace: "deprecated", - - properties: { - accessor: { - type: "string", - writable: true, - deprecated: "This is not the property you are looking for", - }, - }, - - types: [ - { - "id": "Type", - "type": "string", - }, - ], - - functions: [ - { - name: "property", - type: "function", - parameters: [ - { - name: "arg", - type: "object", - properties: { - foo: { - type: "string", - }, - }, - additionalProperties: { - type: "any", - deprecated: "Unknown property", - }, - }, - ], - }, - - { - name: "value", - type: "function", - parameters: [ - { - name: "arg", - choices: [ - { - type: "integer", - }, - { - type: "string", - deprecated: "Please use an integer, not ${value}", - }, - ], - }, - ], - }, - - { - name: "choices", - type: "function", - parameters: [ - { - name: "arg", - deprecated: "You have no choices", - choices: [ - { - type: "integer", - }, - ], - }, - ], - }, - - { - name: "ref", - type: "function", - parameters: [ - { - name: "arg", - choices: [ - { - $ref: "Type", - deprecated: "Deprecated alias", - }, - ], - }, - ], - }, - - { - name: "method", - type: "function", - deprecated: "Do not call this method", - parameters: [ - ], - }, - ], - - events: [ - { - name: "onDeprecated", - type: "function", - deprecated: "This event does not work", - }, - ], - }, -]; - -add_task(function* testDeprecation() { - let url = "data:," + JSON.stringify(deprecatedJson); - yield Schemas.load(url); - - let root = {}; - Schemas.inject(root, wrapper); - - talliedErrors.length = 0; - - - root.deprecated.property({foo: "bar", xxx: "any", yyy: "property"}); - verify("call", "deprecated", "property", [{foo: "bar", xxx: "any", yyy: "property"}]); - checkErrors([ - "Error processing xxx: Unknown property", - "Error processing yyy: Unknown property", - ]); - - root.deprecated.value(12); - verify("call", "deprecated", "value", [12]); - checkErrors([]); - - root.deprecated.value("12"); - verify("call", "deprecated", "value", ["12"]); - checkErrors(["Please use an integer, not \"12\""]); - - root.deprecated.choices(12); - verify("call", "deprecated", "choices", [12]); - checkErrors(["You have no choices"]); - - root.deprecated.ref("12"); - verify("call", "deprecated", "ref", ["12"]); - checkErrors(["Deprecated alias"]); - - root.deprecated.method(); - verify("call", "deprecated", "method", []); - checkErrors(["Do not call this method"]); - - - void root.deprecated.accessor; - verify("get", "deprecated", "accessor", null); - checkErrors(["This is not the property you are looking for"]); - - root.deprecated.accessor = "x"; - verify("set", "deprecated", "accessor", "x"); - checkErrors(["This is not the property you are looking for"]); - - - root.deprecated.onDeprecated.addListener(() => {}); - checkErrors(["This event does not work"]); - - root.deprecated.onDeprecated.removeListener(() => {}); - checkErrors(["This event does not work"]); - - root.deprecated.onDeprecated.hasListener(() => {}); - checkErrors(["This event does not work"]); -}); - - -let choicesJson = [ - {namespace: "choices", - - types: [ - ], - - functions: [ - { - name: "meh", - type: "function", - parameters: [ - { - name: "arg", - choices: [ - { - type: "string", - enum: ["foo", "bar", "baz"], - }, - { - type: "string", - pattern: "florg.*meh", - }, - { - type: "integer", - minimum: 12, - maximum: 42, - }, - ], - }, - ], - }, - - { - name: "foo", - type: "function", - parameters: [ - { - name: "arg", - choices: [ - { - type: "object", - properties: { - blurg: { - type: "string", - unsupported: true, - optional: true, - }, - }, - additionalProperties: { - type: "string", - }, - }, - { - type: "string", - }, - { - type: "array", - minItems: 2, - maxItems: 3, - items: { - type: "integer", - }, - }, - ], - }, - ], - }, - - { - name: "bar", - type: "function", - parameters: [ - { - name: "arg", - choices: [ - { - type: "object", - properties: { - baz: { - type: "string", - }, - }, - }, - { - type: "array", - items: { - type: "integer", - }, - }, - ], - }, - ], - }, - ]}, -]; - -add_task(function* testChoices() { - let url = "data:," + JSON.stringify(choicesJson); - yield Schemas.load(url); - - let root = {}; - Schemas.inject(root, wrapper); - - talliedErrors.length = 0; - - Assert.throws(() => root.choices.meh("frog"), - /Value must either: be one of \["foo", "bar", "baz"\], match the pattern \/florg\.\*meh\/, or be an integer value/); - - Assert.throws(() => root.choices.meh(4), - /be a string value, or be at least 12/); - - Assert.throws(() => root.choices.meh(43), - /be a string value, or be no greater than 42/); - - - Assert.throws(() => root.choices.foo([]), - /be an object value, be a string value, or have at least 2 items/); - - Assert.throws(() => root.choices.foo([1, 2, 3, 4]), - /be an object value, be a string value, or have at most 3 items/); - - Assert.throws(() => root.choices.foo({foo: 12}), - /.foo must be a string value, be a string value, or be an array value/); - - Assert.throws(() => root.choices.foo({blurg: "foo"}), - /not contain an unsupported "blurg" property, be a string value, or be an array value/); - - - Assert.throws(() => root.choices.bar({}), - /contain the required "baz" property, or be an array value/); - - Assert.throws(() => root.choices.bar({baz: "x", quux: "y"}), - /not contain an unexpected "quux" property, or be an array value/); - - Assert.throws(() => root.choices.bar({baz: "x", quux: "y", foo: "z"}), - /not contain the unexpected properties \[foo, quux\], or be an array value/); -}); - - -let permissionsJson = [ - {namespace: "noPerms", - - types: [], - - functions: [ - { - name: "noPerms", - type: "function", - parameters: [], - }, - - { - name: "fooPerm", - type: "function", - permissions: ["foo"], - parameters: [], - }, - ]}, - - {namespace: "fooPerm", - - permissions: ["foo"], - - types: [], - - functions: [ - { - name: "noPerms", - type: "function", - parameters: [], - }, - - { - name: "fooBarPerm", - type: "function", - permissions: ["foo.bar"], - parameters: [], - }, - ]}, -]; - -add_task(function* testPermissions() { - let url = "data:," + JSON.stringify(permissionsJson); - yield Schemas.load(url); - - let root = {}; - Schemas.inject(root, wrapper); - - equal(typeof root.noPerms, "object", "noPerms namespace should exist"); - equal(typeof root.noPerms.noPerms, "function", "noPerms.noPerms method should exist"); - - ok(!("fooPerm" in root.noPerms), "noPerms.fooPerm should not method exist"); - - ok(!("fooPerm" in root), "fooPerm namespace should not exist"); - - - do_print('Add "foo" permission'); - permissions.add("foo"); - - root = {}; - Schemas.inject(root, wrapper); - - equal(typeof root.noPerms, "object", "noPerms namespace should exist"); - equal(typeof root.noPerms.noPerms, "function", "noPerms.noPerms method should exist"); - equal(typeof root.noPerms.fooPerm, "function", "noPerms.fooPerm method should exist"); - - equal(typeof root.fooPerm, "object", "fooPerm namespace should exist"); - equal(typeof root.fooPerm.noPerms, "function", "noPerms.noPerms method should exist"); - - ok(!("fooBarPerm" in root.fooPerm), "fooPerm.fooBarPerm method should not exist"); - - - do_print('Add "foo.bar" permission'); - permissions.add("foo.bar"); - - root = {}; - Schemas.inject(root, wrapper); - - equal(typeof root.noPerms, "object", "noPerms namespace should exist"); - equal(typeof root.noPerms.noPerms, "function", "noPerms.noPerms method should exist"); - equal(typeof root.noPerms.fooPerm, "function", "noPerms.fooPerm method should exist"); - - equal(typeof root.fooPerm, "object", "fooPerm namespace should exist"); - equal(typeof root.fooPerm.noPerms, "function", "noPerms.noPerms method should exist"); - equal(typeof root.fooPerm.fooBarPerm, "function", "noPerms.fooBarPerm method should exist"); -}); - -let nestedNamespaceJson = [ - { - "namespace": "nested.namespace", - "types": [ - { - "id": "CustomType", - "type": "object", - "events": [ - { - "name": "onEvent", - }, - ], - "properties": { - "url": { - "type": "string", - }, - }, - "functions": [ - { - "name": "functionOnCustomType", - "type": "function", - "parameters": [ - { - "name": "title", - "type": "string", - }, - ], - }, - ], - }, - ], - "properties": { - "instanceOfCustomType": { - "$ref": "CustomType", - }, - }, - "functions": [ - { - "name": "create", - "type": "function", - "parameters": [ - { - "name": "title", - "type": "string", - }, - ], - }, - ], - }, -]; - -add_task(function* testNestedNamespace() { - let url = "data:," + JSON.stringify(nestedNamespaceJson); - - yield Schemas.load(url); - - let root = {}; - Schemas.inject(root, wrapper); - - talliedErrors.length = 0; - - ok(root.nested, "The root object contains the first namespace level"); - ok(root.nested.namespace, "The first level object contains the second namespace level"); - - ok(root.nested.namespace.create, "Got the expected function in the nested namespace"); - do_check_eq(typeof root.nested.namespace.create, "function", - "The property is a function as expected"); - - let {instanceOfCustomType} = root.nested.namespace; - - ok(instanceOfCustomType, - "Got the expected instance of the CustomType defined in the schema"); - ok(instanceOfCustomType.functionOnCustomType, - "Got the expected method in the CustomType instance"); - - // TODO: test support events and properties in a SubModuleType defined in the schema, - // once implemented, e.g.: - // - // ok(instanceOfCustomType.url, - // "Got the expected property defined in the CustomType instance) - // - // ok(instanceOfCustomType.onEvent && - // instanceOfCustomType.onEvent.addListener && - // typeof instanceOfCustomType.onEvent.addListener == "function", - // "Got the expected event defined in the CustomType instance"); -}); - -add_task(function* testLocalAPIImplementation() { - let countGet2 = 0; - let countProp3 = 0; - let countProp3SubFoo = 0; - - let testingApiObj = { - get PROP1() { - // PROP1 is a schema-defined constant. - throw new Error("Unexpected get PROP1"); - }, - get prop2() { - ++countGet2; - return "prop2 val"; - }, - get prop3() { - throw new Error("Unexpected get prop3"); - }, - set prop3(v) { - // prop3 is a submodule, defined as a function, so the API should not pass - // through assignment to prop3. - throw new Error("Unexpected set prop3"); - }, - }; - let submoduleApiObj = { - get sub_foo() { - ++countProp3; - return () => { - return ++countProp3SubFoo; - }; - }, - }; - - let localWrapper = { - shouldInject(ns) { - return ns == "testing" || ns == "testing.prop3"; - }, - getImplementation(ns, name) { - do_check_true(ns == "testing" || ns == "testing.prop3"); - if (ns == "testing.prop3" && name == "sub_foo") { - // It is fine to use `null` here because we don't call async functions. - return new LocalAPIImplementation(submoduleApiObj, name, null); - } - // It is fine to use `null` here because we don't call async functions. - return new LocalAPIImplementation(testingApiObj, name, null); - }, - }; - - let root = {}; - Schemas.inject(root, localWrapper); - do_check_eq(countGet2, 0); - do_check_eq(countProp3, 0); - do_check_eq(countProp3SubFoo, 0); - - do_check_eq(root.testing.PROP1, 20); - - do_check_eq(root.testing.prop2, "prop2 val"); - do_check_eq(countGet2, 1); - - do_check_eq(root.testing.prop2, "prop2 val"); - do_check_eq(countGet2, 2); - - do_print(JSON.stringify(root.testing)); - do_check_eq(root.testing.prop3.sub_foo(), 1); - do_check_eq(countProp3, 1); - do_check_eq(countProp3SubFoo, 1); - - do_check_eq(root.testing.prop3.sub_foo(), 2); - do_check_eq(countProp3, 2); - do_check_eq(countProp3SubFoo, 2); - - root.testing.prop3.sub_foo = () => { return "overwritten"; }; - do_check_eq(root.testing.prop3.sub_foo(), "overwritten"); - - root.testing.prop3 = {sub_foo() { return "overwritten again"; }}; - do_check_eq(root.testing.prop3.sub_foo(), "overwritten again"); - do_check_eq(countProp3SubFoo, 2); -}); - - -let defaultsJson = [ - {namespace: "defaultsJson", - - types: [], - - functions: [ - { - name: "defaultFoo", - type: "function", - parameters: [ - {name: "arg", type: "object", optional: true, properties: { - prop1: {type: "integer", optional: true}, - }, default: {prop1: 1}}, - ], - returns: { - type: "object", - }, - }, - ]}, -]; - -add_task(function* testDefaults() { - let url = "data:," + JSON.stringify(defaultsJson); - yield Schemas.load(url); - - let testingApiObj = { - defaultFoo: function(arg) { - if (Object.keys(arg) != "prop1") { - throw new Error(`Received the expected default object, default: ${JSON.stringify(arg)}`); - } - arg.newProp = 1; - return arg; - }, - }; - - let localWrapper = { - shouldInject(ns) { - return true; - }, - getImplementation(ns, name) { - return new LocalAPIImplementation(testingApiObj, name, null); - }, - }; - - let root = {}; - Schemas.inject(root, localWrapper); - - deepEqual(root.defaultsJson.defaultFoo(), {prop1: 1, newProp: 1}); - deepEqual(root.defaultsJson.defaultFoo({prop1: 2}), {prop1: 2, newProp: 1}); - deepEqual(root.defaultsJson.defaultFoo(), {prop1: 1, newProp: 1}); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_allowed_contexts.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_allowed_contexts.js deleted file mode 100644 index 606459764..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_allowed_contexts.js +++ /dev/null @@ -1,147 +0,0 @@ -"use strict"; - -Components.utils.import("resource://gre/modules/Schemas.jsm"); - -let schemaJson = [ - { - namespace: "noAllowedContexts", - properties: { - prop1: {type: "object"}, - prop2: {type: "object", allowedContexts: ["test_zero", "test_one"]}, - prop3: {type: "number", value: 1}, - prop4: {type: "number", value: 1, allowedContexts: ["numeric_one"]}, - }, - }, - { - namespace: "defaultContexts", - defaultContexts: ["test_two"], - properties: { - prop1: {type: "object"}, - prop2: {type: "object", allowedContexts: ["test_three"]}, - prop3: {type: "number", value: 1}, - prop4: {type: "number", value: 1, allowedContexts: ["numeric_two"]}, - }, - }, - { - namespace: "withAllowedContexts", - allowedContexts: ["test_four"], - properties: { - prop1: {type: "object"}, - prop2: {type: "object", allowedContexts: ["test_five"]}, - prop3: {type: "number", value: 1}, - prop4: {type: "number", value: 1, allowedContexts: ["numeric_three"]}, - }, - }, - { - namespace: "withAllowedContextsAndDefault", - allowedContexts: ["test_six"], - defaultContexts: ["test_seven"], - properties: { - prop1: {type: "object"}, - prop2: {type: "object", allowedContexts: ["test_eight"]}, - prop3: {type: "number", value: 1}, - prop4: {type: "number", value: 1, allowedContexts: ["numeric_four"]}, - }, - }, - { - namespace: "with_submodule", - defaultContexts: ["test_nine"], - types: [{ - id: "subtype", - type: "object", - functions: [{ - name: "noAllowedContexts", - type: "function", - parameters: [], - }, { - name: "allowedContexts", - allowedContexts: ["test_ten"], - type: "function", - parameters: [], - }], - }], - properties: { - prop1: {$ref: "subtype"}, - prop2: {$ref: "subtype", allowedContexts: ["test_eleven"]}, - }, - }, -]; -add_task(function* testRestrictions() { - let url = "data:," + JSON.stringify(schemaJson); - yield Schemas.load(url); - let results = {}; - let localWrapper = { - shouldInject(ns, name, allowedContexts) { - name = name === null ? ns : ns + "." + name; - results[name] = allowedContexts.join(","); - return true; - }, - getImplementation() { - // The actual implementation is not significant for this test. - // Let's take this opportunity to see if schema generation is free of - // exceptions even when somehow getImplementation does not return an - // implementation. - }, - }; - - let root = {}; - Schemas.inject(root, localWrapper); - - function verify(path, expected) { - let obj = root; - for (let thing of path.split(".")) { - try { - obj = obj[thing]; - } catch (e) { - // Blech. - } - } - - let result = results[path]; - equal(result, expected); - } - - verify("noAllowedContexts", ""); - verify("noAllowedContexts.prop1", ""); - verify("noAllowedContexts.prop2", "test_zero,test_one"); - verify("noAllowedContexts.prop3", ""); - verify("noAllowedContexts.prop4", "numeric_one"); - - verify("defaultContexts", ""); - verify("defaultContexts.prop1", "test_two"); - verify("defaultContexts.prop2", "test_three"); - verify("defaultContexts.prop3", "test_two"); - verify("defaultContexts.prop4", "numeric_two"); - - verify("withAllowedContexts", "test_four"); - verify("withAllowedContexts.prop1", ""); - verify("withAllowedContexts.prop2", "test_five"); - verify("withAllowedContexts.prop3", ""); - verify("withAllowedContexts.prop4", "numeric_three"); - - verify("withAllowedContextsAndDefault", "test_six"); - verify("withAllowedContextsAndDefault.prop1", "test_seven"); - verify("withAllowedContextsAndDefault.prop2", "test_eight"); - verify("withAllowedContextsAndDefault.prop3", "test_seven"); - verify("withAllowedContextsAndDefault.prop4", "numeric_four"); - - verify("with_submodule", ""); - verify("with_submodule.prop1", "test_nine"); - verify("with_submodule.prop1.noAllowedContexts", "test_nine"); - verify("with_submodule.prop1.allowedContexts", "test_ten"); - verify("with_submodule.prop2", "test_eleven"); - // Note: test_nine inherits allowed contexts from the namespace, not from - // submodule. There is no "defaultContexts" for submodule types to not - // complicate things. - verify("with_submodule.prop1.noAllowedContexts", "test_nine"); - verify("with_submodule.prop1.allowedContexts", "test_ten"); - - // This is a constant, so it does not matter that getImplementation does not - // return an implementation since the API injector should take care of it. - equal(root.noAllowedContexts.prop3, 1); - - Assert.throws(() => root.noAllowedContexts.prop1, - /undefined/, - "Should throw when the implementation is absent."); -}); - diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_api_injection.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_api_injection.js deleted file mode 100644 index 36d88d722..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_api_injection.js +++ /dev/null @@ -1,102 +0,0 @@ -"use strict"; - -Components.utils.import("resource://gre/modules/ExtensionCommon.jsm"); -Components.utils.import("resource://gre/modules/Schemas.jsm"); - -let { - BaseContext, - SchemaAPIManager, -} = ExtensionCommon; - -let nestedNamespaceJson = [ - { - "namespace": "backgroundAPI.testnamespace", - "functions": [ - { - "name": "create", - "type": "function", - "parameters": [ - { - "name": "title", - "type": "string", - }, - ], - "returns": { - "type": "string", - }, - }, - ], - }, - { - "namespace": "noBackgroundAPI.testnamespace", - "functions": [ - { - "name": "create", - "type": "function", - "parameters": [ - { - "name": "title", - "type": "string", - }, - ], - }, - ], - }, -]; - -let global = this; -class StubContext extends BaseContext { - constructor() { - let fakeExtension = {id: "test@web.extension"}; - super("addon_child", fakeExtension); - this.sandbox = Cu.Sandbox(global); - this.viewType = "background"; - } - - get cloneScope() { - return this.sandbox; - } -} - -add_task(function* testSchemaAPIInjection() { - let url = "data:," + JSON.stringify(nestedNamespaceJson); - - // Load the schema of the fake APIs. - yield Schemas.load(url); - - let apiManager = new SchemaAPIManager("addon"); - - // Register an API that will skip the background page. - apiManager.registerSchemaAPI("noBackgroundAPI.testnamespace", "addon_child", context => { - // This API should not be available in this context, return null so that - // the schema wrapper is removed as well. - return null; - }); - - // Register an API that will skip any but the background page. - apiManager.registerSchemaAPI("backgroundAPI.testnamespace", "addon_child", context => { - if (context.viewType === "background") { - return { - backgroundAPI: { - testnamespace: { - create(title) { - return title; - }, - }, - }, - }; - } - - // This API should not be available in this context, return null so that - // the schema wrapper is removed as well. - return null; - }); - - let context = new StubContext(); - let browserObj = {}; - apiManager.generateAPIs(context, browserObj); - - do_check_eq(browserObj.noBackgroundAPI, undefined); - const res = browserObj.backgroundAPI.testnamespace.create("param-value"); - do_check_eq(res, "param-value"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_async.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_async.js deleted file mode 100644 index 6397d1f96..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_async.js +++ /dev/null @@ -1,232 +0,0 @@ -"use strict"; - -Components.utils.import("resource://gre/modules/ExtensionCommon.jsm"); -Components.utils.import("resource://gre/modules/Schemas.jsm"); - -let {BaseContext, LocalAPIImplementation} = ExtensionCommon; - -let schemaJson = [ - { - namespace: "testnamespace", - functions: [{ - name: "one_required", - type: "function", - parameters: [{ - name: "first", - type: "function", - parameters: [], - }], - }, { - name: "one_optional", - type: "function", - parameters: [{ - name: "first", - type: "function", - parameters: [], - optional: true, - }], - }, { - name: "async_required", - type: "function", - async: "first", - parameters: [{ - name: "first", - type: "function", - parameters: [], - }], - }, { - name: "async_optional", - type: "function", - async: "first", - parameters: [{ - name: "first", - type: "function", - parameters: [], - optional: true, - }], - }], - }, -]; - -const global = this; -class StubContext extends BaseContext { - constructor() { - let fakeExtension = {id: "test@web.extension"}; - super("testEnv", fakeExtension); - this.sandbox = Cu.Sandbox(global); - } - - get cloneScope() { - return this.sandbox; - } - - get principal() { - return Cu.getObjectPrincipal(this.sandbox); - } -} - -let context; - -function generateAPIs(extraWrapper, apiObj) { - context = new StubContext(); - let localWrapper = { - shouldInject() { - return true; - }, - getImplementation(namespace, name) { - return new LocalAPIImplementation(apiObj, name, context); - }, - }; - Object.assign(localWrapper, extraWrapper); - - let root = {}; - Schemas.inject(root, localWrapper); - return root.testnamespace; -} - -add_task(function* testParameterValidation() { - yield Schemas.load("data:," + JSON.stringify(schemaJson)); - - let testnamespace; - function assertThrows(name, ...args) { - Assert.throws(() => testnamespace[name](...args), - /Incorrect argument types/, - `Expected testnamespace.${name}(${args.map(String).join(", ")}) to throw.`); - } - function assertNoThrows(name, ...args) { - try { - testnamespace[name](...args); - } catch (e) { - do_print(`testnamespace.${name}(${args.map(String).join(", ")}) unexpectedly threw.`); - throw new Error(e); - } - } - let cb = () => {}; - - for (let isChromeCompat of [true, false]) { - do_print(`Testing API validation with isChromeCompat=${isChromeCompat}`); - testnamespace = generateAPIs({ - isChromeCompat, - }, { - one_required() {}, - one_optional() {}, - async_required() {}, - async_optional() {}, - }); - - assertThrows("one_required"); - assertThrows("one_required", null); - assertNoThrows("one_required", cb); - assertThrows("one_required", cb, null); - assertThrows("one_required", cb, cb); - - assertNoThrows("one_optional"); - assertNoThrows("one_optional", null); - assertNoThrows("one_optional", cb); - assertThrows("one_optional", cb, null); - assertThrows("one_optional", cb, cb); - - // Schema-based validation happens before an async method is called, so - // errors should be thrown synchronously. - - // The parameter was declared as required, but there was also an "async" - // attribute with the same value as the parameter name, so the callback - // parameter is actually optional. - assertNoThrows("async_required"); - assertNoThrows("async_required", null); - assertNoThrows("async_required", cb); - assertThrows("async_required", cb, null); - assertThrows("async_required", cb, cb); - - assertNoThrows("async_optional"); - assertNoThrows("async_optional", null); - assertNoThrows("async_optional", cb); - assertThrows("async_optional", cb, null); - assertThrows("async_optional", cb, cb); - } -}); - -add_task(function* testAsyncResults() { - yield Schemas.load("data:," + JSON.stringify(schemaJson)); - function* runWithCallback(func) { - do_print(`Calling testnamespace.${func.name}, expecting callback with result`); - return yield new Promise(resolve => { - let result = "uninitialized value"; - let returnValue = func(reply => { - result = reply; - resolve(result); - }); - // When a callback is given, the return value must be missing. - do_check_eq(returnValue, undefined); - // Callback must be called asynchronously. - do_check_eq(result, "uninitialized value"); - }); - } - - function* runFailCallback(func) { - do_print(`Calling testnamespace.${func.name}, expecting callback with error`); - return yield new Promise(resolve => { - func(reply => { - do_check_eq(reply, undefined); - resolve(context.lastError.message); // eslint-disable-line no-undef - }); - }); - } - - for (let isChromeCompat of [true, false]) { - do_print(`Testing API invocation with isChromeCompat=${isChromeCompat}`); - let testnamespace = generateAPIs({ - isChromeCompat, - }, { - async_required(cb) { - do_check_eq(cb, undefined); - return Promise.resolve(1); - }, - async_optional(cb) { - do_check_eq(cb, undefined); - return Promise.resolve(2); - }, - }); - if (!isChromeCompat) { // No promises for chrome. - do_print("testnamespace.async_required should be a Promise"); - let promise = testnamespace.async_required(); - do_check_true(promise instanceof context.cloneScope.Promise); - do_check_eq(yield promise, 1); - - do_print("testnamespace.async_optional should be a Promise"); - promise = testnamespace.async_optional(); - do_check_true(promise instanceof context.cloneScope.Promise); - do_check_eq(yield promise, 2); - } - - do_check_eq(yield* runWithCallback(testnamespace.async_required), 1); - do_check_eq(yield* runWithCallback(testnamespace.async_optional), 2); - - let otherSandbox = Cu.Sandbox(null, {}); - let errorFactories = [ - msg => { throw new context.cloneScope.Error(msg); }, - msg => context.cloneScope.Promise.reject({message: msg}), - msg => Cu.evalInSandbox(`throw new Error("${msg}")`, otherSandbox), - msg => Cu.evalInSandbox(`Promise.reject({message: "${msg}"})`, otherSandbox), - ]; - for (let makeError of errorFactories) { - do_print(`Testing callback/promise with error caused by: ${makeError}`); - testnamespace = generateAPIs({ - isChromeCompat, - }, { - async_required() { return makeError("ONE"); }, - async_optional() { return makeError("TWO"); }, - }); - - if (!isChromeCompat) { // No promises for chrome. - yield Assert.rejects(testnamespace.async_required(), /ONE/, - "should reject testnamespace.async_required()").catch(() => {}); - yield Assert.rejects(testnamespace.async_optional(), /TWO/, - "should reject testnamespace.async_optional()").catch(() => {}); - } - - do_check_eq(yield* runFailCallback(testnamespace.async_required), "ONE"); - do_check_eq(yield* runFailCallback(testnamespace.async_optional), "TWO"); - } - } -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_simple.js b/toolkit/components/extensions/test/xpcshell/test_ext_simple.js deleted file mode 100644 index 91b10354c..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_simple.js +++ /dev/null @@ -1,69 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -add_task(function* test_simple() { - let extensionData = { - manifest: { - "name": "Simple extension test", - "version": "1.0", - "manifest_version": 2, - "description": "", - }, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - yield extension.unload(); -}); - -add_task(function* test_background() { - function background() { - browser.test.log("running background script"); - - browser.test.onMessage.addListener((x, y) => { - browser.test.assertEq(x, 10, "x is 10"); - browser.test.assertEq(y, 20, "y is 20"); - - browser.test.notifyPass("background test passed"); - }); - - browser.test.sendMessage("running", 1); - } - - let extensionData = { - background, - manifest: { - "name": "Simple extension test", - "version": "1.0", - "manifest_version": 2, - "description": "", - }, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - - let [, x] = yield Promise.all([extension.startup(), extension.awaitMessage("running")]); - equal(x, 1, "got correct value from extension"); - - extension.sendMessage(10, 20); - yield extension.awaitFinish(); - yield extension.unload(); -}); - -add_task(function* test_extensionTypes() { - let extensionData = { - background: function() { - browser.test.assertEq(typeof browser.extensionTypes, "object", "browser.extensionTypes exists"); - browser.test.assertEq(typeof browser.extensionTypes.RunAt, "object", "browser.extensionTypes.RunAt exists"); - browser.test.notifyPass("extentionTypes test passed"); - }, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - - yield extension.startup(); - yield extension.awaitFinish(); - yield extension.unload(); -}); - diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_storage.js b/toolkit/components/extensions/test/xpcshell/test_ext_storage.js deleted file mode 100644 index df46dfb63..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_storage.js +++ /dev/null @@ -1,334 +0,0 @@ -/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set sts=2 sw=2 et tw=80: */ -"use strict"; - -const STORAGE_SYNC_PREF = "webextensions.storage.sync.enabled"; -Cu.import("resource://gre/modules/Preferences.jsm"); - -/** - * Utility function to ensure that all supported APIs for getting are - * tested. - * - * @param {string} areaName - * either "local" or "sync" according to what we want to test - * @param {string} prop - * "key" to look up using the storage API - * @param {Object} value - * "value" to compare against - */ -async function checkGetImpl(areaName, prop, value) { - let storage = browser.storage[areaName]; - - let data = await storage.get(null); - browser.test.assertEq(value, data[prop], `null getter worked for ${prop} in ${areaName}`); - - data = await storage.get(prop); - browser.test.assertEq(value, data[prop], `string getter worked for ${prop} in ${areaName}`); - - data = await storage.get([prop]); - browser.test.assertEq(value, data[prop], `array getter worked for ${prop} in ${areaName}`); - - data = await storage.get({[prop]: undefined}); - browser.test.assertEq(value, data[prop], `object getter worked for ${prop} in ${areaName}`); -} - -add_task(function* test_local_cache_invalidation() { - function background(checkGet) { - browser.test.onMessage.addListener(async msg => { - if (msg === "set-initial") { - await browser.storage.local.set({"test-prop1": "value1", "test-prop2": "value2"}); - browser.test.sendMessage("set-initial-done"); - } else if (msg === "check") { - await checkGet("local", "test-prop1", "value1"); - await checkGet("local", "test-prop2", "value2"); - browser.test.sendMessage("check-done"); - } - }); - - browser.test.sendMessage("ready"); - } - - let extension = ExtensionTestUtils.loadExtension({ - manifest: { - permissions: ["storage"], - }, - background: `(${background})(${checkGetImpl})`, - }); - - yield extension.startup(); - yield extension.awaitMessage("ready"); - - extension.sendMessage("set-initial"); - yield extension.awaitMessage("set-initial-done"); - - Services.obs.notifyObservers(null, "extension-invalidate-storage-cache", ""); - - extension.sendMessage("check"); - yield extension.awaitMessage("check-done"); - - yield extension.unload(); -}); - -add_task(function* test_config_flag_needed() { - function background() { - let promises = []; - let apiTests = [ - {method: "get", args: ["foo"]}, - {method: "set", args: [{foo: "bar"}]}, - {method: "remove", args: ["foo"]}, - {method: "clear", args: []}, - ]; - apiTests.forEach(testDef => { - promises.push(browser.test.assertRejects( - browser.storage.sync[testDef.method](...testDef.args), - "Please set webextensions.storage.sync.enabled to true in about:config", - `storage.sync.${testDef.method} is behind a flag`)); - }); - - Promise.all(promises).then(() => browser.test.notifyPass("flag needed")); - } - - ok(!Preferences.get(STORAGE_SYNC_PREF)); - let extension = ExtensionTestUtils.loadExtension({ - manifest: { - permissions: ["storage"], - }, - background: `(${background})(${checkGetImpl})`, - }); - - yield extension.startup(); - yield extension.awaitFinish("flag needed"); - yield extension.unload(); -}); - -add_task(function* test_reloading_extensions_works() { - // Just some random extension ID that we can re-use - const extensionId = "my-extension-id@1"; - - function loadExtension() { - function background() { - browser.storage.sync.set({"a": "b"}).then(() => { - browser.test.notifyPass("set-works"); - }); - } - - return ExtensionTestUtils.loadExtension({ - manifest: { - permissions: ["storage"], - }, - background: `(${background})()`, - }, extensionId); - } - - Preferences.set(STORAGE_SYNC_PREF, true); - - let extension1 = loadExtension(); - - yield extension1.startup(); - yield extension1.awaitFinish("set-works"); - yield extension1.unload(); - - let extension2 = loadExtension(); - - yield extension2.startup(); - yield extension2.awaitFinish("set-works"); - yield extension2.unload(); - - Preferences.reset(STORAGE_SYNC_PREF); -}); - -do_register_cleanup(() => { - Preferences.reset(STORAGE_SYNC_PREF); -}); - -add_task(function* test_backgroundScript() { - async function backgroundScript(checkGet) { - let globalChanges, gResolve; - function clearGlobalChanges() { - globalChanges = new Promise(resolve => { gResolve = resolve; }); - } - clearGlobalChanges(); - let expectedAreaName; - - browser.storage.onChanged.addListener((changes, areaName) => { - browser.test.assertEq(expectedAreaName, areaName, - "Expected area name received by listener"); - gResolve(changes); - }); - - async function checkChanges(areaName, changes, message) { - function checkSub(obj1, obj2) { - for (let prop in obj1) { - browser.test.assertTrue(obj1[prop] !== undefined, - `checkChanges ${areaName} ${prop} is missing (${message})`); - browser.test.assertTrue(obj2[prop] !== undefined, - `checkChanges ${areaName} ${prop} is missing (${message})`); - browser.test.assertEq(obj1[prop].oldValue, obj2[prop].oldValue, - `checkChanges ${areaName} ${prop} old (${message})`); - browser.test.assertEq(obj1[prop].newValue, obj2[prop].newValue, - `checkChanges ${areaName} ${prop} new (${message})`); - } - } - - const recentChanges = await globalChanges; - checkSub(changes, recentChanges); - checkSub(recentChanges, changes); - clearGlobalChanges(); - } - - /* eslint-disable dot-notation */ - async function runTests(areaName) { - expectedAreaName = areaName; - let storage = browser.storage[areaName]; - // Set some data and then test getters. - try { - await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); - await checkChanges(areaName, - {"test-prop1": {newValue: "value1"}, "test-prop2": {newValue: "value2"}}, - "set (a)"); - - await checkGet(areaName, "test-prop1", "value1"); - await checkGet(areaName, "test-prop2", "value2"); - - let data = await storage.get({"test-prop1": undefined, "test-prop2": undefined, "other": "default"}); - browser.test.assertEq("value1", data["test-prop1"], "prop1 correct (a)"); - browser.test.assertEq("value2", data["test-prop2"], "prop2 correct (a)"); - browser.test.assertEq("default", data["other"], "other correct"); - - data = await storage.get(["test-prop1", "test-prop2", "other"]); - browser.test.assertEq("value1", data["test-prop1"], "prop1 correct (b)"); - browser.test.assertEq("value2", data["test-prop2"], "prop2 correct (b)"); - browser.test.assertFalse("other" in data, "other correct"); - - // Remove data in various ways. - await storage.remove("test-prop1"); - await checkChanges(areaName, {"test-prop1": {oldValue: "value1"}}, "remove string"); - - data = await storage.get(["test-prop1", "test-prop2"]); - browser.test.assertFalse("test-prop1" in data, "prop1 absent (remove string)"); - browser.test.assertTrue("test-prop2" in data, "prop2 present (remove string)"); - - await storage.set({"test-prop1": "value1"}); - await checkChanges(areaName, {"test-prop1": {newValue: "value1"}}, "set (c)"); - - data = await storage.get(["test-prop1", "test-prop2"]); - browser.test.assertEq(data["test-prop1"], "value1", "prop1 correct (c)"); - browser.test.assertEq(data["test-prop2"], "value2", "prop2 correct (c)"); - - await storage.remove(["test-prop1", "test-prop2"]); - await checkChanges(areaName, - {"test-prop1": {oldValue: "value1"}, "test-prop2": {oldValue: "value2"}}, - "remove array"); - - data = await storage.get(["test-prop1", "test-prop2"]); - browser.test.assertFalse("test-prop1" in data, "prop1 absent (remove array)"); - browser.test.assertFalse("test-prop2" in data, "prop2 absent (remove array)"); - - // test storage.clear - await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); - // Make sure that set() handler happened before we clear the - // promise again. - await globalChanges; - - clearGlobalChanges(); - await storage.clear(); - - await checkChanges(areaName, - {"test-prop1": {oldValue: "value1"}, "test-prop2": {oldValue: "value2"}}, - "clear"); - data = await storage.get(["test-prop1", "test-prop2"]); - browser.test.assertFalse("test-prop1" in data, "prop1 absent (clear)"); - browser.test.assertFalse("test-prop2" in data, "prop2 absent (clear)"); - - // Make sure we can store complex JSON data. - // known previous values - await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); - - // Make sure the set() handler landed. - await globalChanges; - - clearGlobalChanges(); - await storage.set({ - "test-prop1": { - str: "hello", - bool: true, - null: null, - undef: undefined, - obj: {}, - arr: [1, 2], - date: new Date(0), - regexp: /regexp/, - func: function func() {}, - window, - }, - }); - - await storage.set({"test-prop2": function func() {}}); - const recentChanges = await globalChanges; - - browser.test.assertEq("value1", recentChanges["test-prop1"].oldValue, "oldValue correct"); - browser.test.assertEq("object", typeof(recentChanges["test-prop1"].newValue), "newValue is obj"); - clearGlobalChanges(); - - data = await storage.get({"test-prop1": undefined, "test-prop2": undefined}); - let obj = data["test-prop1"]; - - browser.test.assertEq("hello", obj.str, "string part correct"); - browser.test.assertEq(true, obj.bool, "bool part correct"); - browser.test.assertEq(null, obj.null, "null part correct"); - browser.test.assertEq(undefined, obj.undef, "undefined part correct"); - browser.test.assertEq(undefined, obj.func, "function part correct"); - browser.test.assertEq(undefined, obj.window, "window part correct"); - browser.test.assertEq("1970-01-01T00:00:00.000Z", obj.date, "date part correct"); - browser.test.assertEq("/regexp/", obj.regexp, "regexp part correct"); - browser.test.assertEq("object", typeof(obj.obj), "object part correct"); - browser.test.assertTrue(Array.isArray(obj.arr), "array part present"); - browser.test.assertEq(1, obj.arr[0], "arr[0] part correct"); - browser.test.assertEq(2, obj.arr[1], "arr[1] part correct"); - browser.test.assertEq(2, obj.arr.length, "arr.length part correct"); - - obj = data["test-prop2"]; - - browser.test.assertEq("[object Object]", {}.toString.call(obj), "function serialized as a plain object"); - browser.test.assertEq(0, Object.keys(obj).length, "function serialized as an empty object"); - } catch (e) { - browser.test.fail(`Error: ${e} :: ${e.stack}`); - browser.test.notifyFail("storage"); - } - } - - browser.test.onMessage.addListener(msg => { - let promise; - if (msg === "test-local") { - promise = runTests("local"); - } else if (msg === "test-sync") { - promise = runTests("sync"); - } - promise.then(() => browser.test.sendMessage("test-finished")); - }); - - browser.test.sendMessage("ready"); - } - - let extensionData = { - background: `(${backgroundScript})(${checkGetImpl})`, - manifest: { - permissions: ["storage"], - }, - }; - - Preferences.set(STORAGE_SYNC_PREF, true); - - let extension = ExtensionTestUtils.loadExtension(extensionData); - yield extension.startup(); - yield extension.awaitMessage("ready"); - - extension.sendMessage("test-local"); - yield extension.awaitMessage("test-finished"); - - extension.sendMessage("test-sync"); - yield extension.awaitMessage("test-finished"); - - Preferences.reset(STORAGE_SYNC_PREF); - yield extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_topSites.js b/toolkit/components/extensions/test/xpcshell/test_ext_topSites.js deleted file mode 100644 index eb3f552ed..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_topSites.js +++ /dev/null @@ -1,85 +0,0 @@ -"use strict"; - -Cu.import("resource://gre/modules/NewTabUtils.jsm"); - - -function TestProvider(getLinksFn) { - this.getLinks = getLinksFn; - this._observers = new Set(); -} - -TestProvider.prototype = { - addObserver: function(observer) { - this._observers.add(observer); - }, - notifyLinkChanged: function(link, index = -1, deleted = false) { - this._notifyObservers("onLinkChanged", link, index, deleted); - }, - notifyManyLinksChanged: function() { - this._notifyObservers("onManyLinksChanged"); - }, - _notifyObservers: function(observerMethodName, ...args) { - args.unshift(this); - for (let obs of this._observers) { - if (obs[observerMethodName]) { - obs[observerMethodName].apply(NewTabUtils.links, args); - } - } - }, -}; - -function makeLinks(links) { - // Important: To avoid test failures due to clock jitter on Windows XP, call - // Date.now() once here, not each time through the loop. - let frecency = 0; - let now = Date.now() * 1000; - let places = []; - links.map((link, i) => { - places.push({ - url: link.url, - title: link.title, - lastVisitDate: now - i, - frecency: frecency++, - }); - }); - return places; -} - -add_task(function* test_topSites() { - let expect = [{url: "http://example.com/", title: "site#-1"}, - {url: "http://example0.com/", title: "site#0"}, - {url: "http://example1.com/", title: "site#1"}, - {url: "http://example2.com/", title: "site#2"}, - {url: "http://example3.com/", title: "site#3"}]; - - let extension = ExtensionTestUtils.loadExtension({ - manifest: { - "permissions": [ - "topSites", - ], - }, - background() { - browser.topSites.get(result => { - browser.test.sendMessage("done", result); - }); - }, - }); - - - let expectedLinks = makeLinks(expect); - let provider = new TestProvider(done => done(expectedLinks)); - - NewTabUtils.initWithoutProviders(); - NewTabUtils.links.addProvider(provider); - - yield NewTabUtils.links.populateCache(); - - yield extension.startup(); - - let result = yield extension.awaitMessage("done"); - Assert.deepEqual(expect, result, "got topSites"); - - yield extension.unload(); - - NewTabUtils.links.removeProvider(provider); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_getAPILevelForWindow.js b/toolkit/components/extensions/test/xpcshell/test_getAPILevelForWindow.js deleted file mode 100644 index 68741a6cc..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_getAPILevelForWindow.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; - -Cu.import("resource://gre/modules/ExtensionManagement.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -function createWindowWithAddonId(addonId) { - let baseURI = Services.io.newURI("about:blank", null, null); - let originAttributes = {addonId}; - let principal = Services.scriptSecurityManager - .createCodebasePrincipal(baseURI, originAttributes); - let chromeNav = Services.appShell.createWindowlessBrowser(true); - let interfaceRequestor = chromeNav.QueryInterface(Ci.nsIInterfaceRequestor); - let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell); - docShell.createAboutBlankContentViewer(principal); - - return {chromeNav, window: docShell.contentViewer.DOMDocument.defaultView}; -} - -add_task(function* test_eventpages() { - const {getAPILevelForWindow, getAddonIdForWindow} = ExtensionManagement; - const {NO_PRIVILEGES, FULL_PRIVILEGES} = ExtensionManagement.API_LEVELS; - const FAKE_ADDON_ID = "fakeAddonId"; - const OTHER_ADDON_ID = "otherFakeAddonId"; - const EMPTY_ADDON_ID = ""; - - let fakeAddonId = createWindowWithAddonId(FAKE_ADDON_ID); - equal(getAddonIdForWindow(fakeAddonId.window), FAKE_ADDON_ID, - "the window has the expected addonId"); - - let apiLevel = getAPILevelForWindow(fakeAddonId.window, FAKE_ADDON_ID); - equal(apiLevel, FULL_PRIVILEGES, - "apiLevel for the window with the right addonId should be FULL_PRIVILEGES"); - - apiLevel = getAPILevelForWindow(fakeAddonId.window, OTHER_ADDON_ID); - equal(apiLevel, NO_PRIVILEGES, - "apiLevel for the window with a different addonId should be NO_PRIVILEGES"); - - fakeAddonId.chromeNav.close(); - - // NOTE: check that window with an empty addon Id (which are window that are - // not Extensions pages) always get no WebExtensions APIs. - let emptyAddonId = createWindowWithAddonId(EMPTY_ADDON_ID); - equal(getAddonIdForWindow(emptyAddonId.window), EMPTY_ADDON_ID, - "the window has the expected addonId"); - - apiLevel = getAPILevelForWindow(emptyAddonId.window, EMPTY_ADDON_ID); - equal(apiLevel, NO_PRIVILEGES, - "apiLevel for empty addonId should be NO_PRIVILEGES"); - - apiLevel = getAPILevelForWindow(emptyAddonId.window, OTHER_ADDON_ID); - equal(apiLevel, NO_PRIVILEGES, - "apiLevel for an 'empty addonId' window should be always NO_PRIVILEGES"); - - emptyAddonId.chromeNav.close(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_locale_converter.js b/toolkit/components/extensions/test/xpcshell/test_locale_converter.js deleted file mode 100644 index c8b1ee92b..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_locale_converter.js +++ /dev/null @@ -1,133 +0,0 @@ -"use strict"; - -const convService = Cc["@mozilla.org/streamConverters;1"] - .getService(Ci.nsIStreamConverterService); - -const UUID = "72b61ee3-aceb-476c-be1b-0822b036c9f1"; -const ADDON_ID = "test@web.extension"; -const URI = NetUtil.newURI(`moz-extension://${UUID}/file.css`); - -const FROM_TYPE = "application/vnd.mozilla.webext.unlocalized"; -const TO_TYPE = "text/css"; - - -function StringStream(string) { - let stream = Cc["@mozilla.org/io/string-input-stream;1"] - .createInstance(Ci.nsIStringInputStream); - - stream.data = string; - return stream; -} - - -// Initialize the policy service with a stub localizer for our -// add-on ID. -add_task(function* init() { - const aps = Cc["@mozilla.org/addons/policy-service;1"] - .getService(Ci.nsIAddonPolicyService).wrappedJSObject; - - let oldCallback = aps.setExtensionURIToAddonIdCallback(uri => { - if (uri.host == UUID) { - return ADDON_ID; - } - }); - - aps.setAddonLocalizeCallback(ADDON_ID, string => { - return string.replace(/__MSG_(.*?)__/g, ""); - }); - - do_register_cleanup(() => { - aps.setExtensionURIToAddonIdCallback(oldCallback); - aps.setAddonLocalizeCallback(ADDON_ID, null); - }); -}); - - -// Test that the synchronous converter works as expected with a -// simple string. -add_task(function* testSynchronousConvert() { - let stream = StringStream("Foo __MSG_xxx__ bar __MSG_yyy__ baz"); - - let resultStream = convService.convert(stream, FROM_TYPE, TO_TYPE, URI); - - let result = NetUtil.readInputStreamToString(resultStream, resultStream.available()); - - equal(result, "Foo bar baz"); -}); - - -// Test that the asynchronous converter works as expected with input -// split into multiple chunks, and a boundary in the middle of a -// replacement token. -add_task(function* testAsyncConvert() { - let listener; - let awaitResult = new Promise((resolve, reject) => { - listener = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]), - - onDataAvailable(request, context, inputStream, offset, count) { - this.resultParts.push(NetUtil.readInputStreamToString(inputStream, count)); - }, - - onStartRequest() { - ok(!("resultParts" in this)); - this.resultParts = []; - }, - - onStopRequest(request, context, statusCode) { - if (!Components.isSuccessCode(statusCode)) { - reject(new Error(statusCode)); - } - - resolve(this.resultParts.join("\n")); - }, - }; - }); - - let parts = ["Foo __MSG_x", "xx__ bar __MSG_yyy__ baz"]; - - let converter = convService.asyncConvertData(FROM_TYPE, TO_TYPE, listener, URI); - converter.onStartRequest(null, null); - - for (let part of parts) { - converter.onDataAvailable(null, null, StringStream(part), 0, part.length); - } - - converter.onStopRequest(null, null, Cr.NS_OK); - - - let result = yield awaitResult; - equal(result, "Foo bar baz"); -}); - - -// Test that attempting to initialize a converter with the URI of a -// nonexistent WebExtension fails. -add_task(function* testInvalidUUID() { - let uri = NetUtil.newURI("moz-extension://eb4f3be8-41c9-4970-aa6d-b84d1ecc02b2/file.css"); - let stream = StringStream("Foo __MSG_xxx__ bar __MSG_yyy__ baz"); - - // Assert.throws raise a TypeError exception when the expected param - // is an arrow function. (See Bug 1237961 for rationale) - let expectInvalidContextException = function(e) { - return e.result === Cr.NS_ERROR_INVALID_ARG && /Invalid context/.test(e); - }; - - Assert.throws(() => { - convService.convert(stream, FROM_TYPE, TO_TYPE, uri); - }, expectInvalidContextException); - - Assert.throws(() => { - let listener = {QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener])}; - - convService.asyncConvertData(FROM_TYPE, TO_TYPE, listener, uri); - }, expectInvalidContextException); -}); - - -// Test that an empty stream does not throw an NS_ERROR_ILLEGAL_VALUE. -add_task(function* testEmptyStream() { - let stream = StringStream(""); - let resultStream = convService.convert(stream, FROM_TYPE, TO_TYPE, URI); - equal(resultStream.data, ""); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_locale_data.js b/toolkit/components/extensions/test/xpcshell/test_locale_data.js deleted file mode 100644 index c3cd44e57..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_locale_data.js +++ /dev/null @@ -1,130 +0,0 @@ -"use strict"; - -Cu.import("resource://gre/modules/Extension.jsm"); - -/* globals ExtensionData */ - -const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); - -function* generateAddon(data) { - let id = uuidGenerator.generateUUID().number; - - data = Object.assign({embedded: true}, data); - data.manifest = Object.assign({applications: {gecko: {id}}}, data.manifest); - - let xpi = Extension.generateXPI(data); - do_register_cleanup(() => { - Services.obs.notifyObservers(xpi, "flush-cache-entry", null); - xpi.remove(false); - }); - - let fileURI = Services.io.newFileURI(xpi); - let jarURI = NetUtil.newURI(`jar:${fileURI.spec}!/webextension/`); - - let extension = new ExtensionData(jarURI); - yield extension.readManifest(); - - return extension; -} - -add_task(function* testMissingDefaultLocale() { - let extension = yield generateAddon({ - "files": { - "_locales/en_US/messages.json": {}, - }, - }); - - equal(extension.errors.length, 0, "No errors reported"); - - yield extension.initAllLocales(); - - equal(extension.errors.length, 1, "One error reported"); - - do_print(`Got error: ${extension.errors[0]}`); - - ok(extension.errors[0].includes('"default_locale" property is required'), - "Got missing default_locale error"); -}); - - -add_task(function* testInvalidDefaultLocale() { - let extension = yield generateAddon({ - "manifest": { - "default_locale": "en", - }, - - "files": { - "_locales/en_US/messages.json": {}, - }, - }); - - equal(extension.errors.length, 1, "One error reported"); - - do_print(`Got error: ${extension.errors[0]}`); - - ok(extension.errors[0].includes("Loading locale file _locales/en/messages.json"), - "Got invalid default_locale error"); - - yield extension.initAllLocales(); - - equal(extension.errors.length, 2, "Two errors reported"); - - do_print(`Got error: ${extension.errors[1]}`); - - ok(extension.errors[1].includes('"default_locale" property must correspond'), - "Got invalid default_locale error"); -}); - - -add_task(function* testUnexpectedDefaultLocale() { - let extension = yield generateAddon({ - "manifest": { - "default_locale": "en_US", - }, - }); - - equal(extension.errors.length, 1, "One error reported"); - - do_print(`Got error: ${extension.errors[0]}`); - - ok(extension.errors[0].includes("Loading locale file _locales/en-US/messages.json"), - "Got invalid default_locale error"); - - yield extension.initAllLocales(); - - equal(extension.errors.length, 2, "One error reported"); - - do_print(`Got error: ${extension.errors[1]}`); - - ok(extension.errors[1].includes('"default_locale" property must correspond'), - "Got unexpected default_locale error"); -}); - - -add_task(function* testInvalidSyntax() { - let extension = yield generateAddon({ - "manifest": { - "default_locale": "en_US", - }, - - "files": { - "_locales/en_US/messages.json": '{foo: {message: "bar", description: "baz"}}', - }, - }); - - equal(extension.errors.length, 1, "No errors reported"); - - do_print(`Got error: ${extension.errors[0]}`); - - ok(extension.errors[0].includes("Loading locale file _locales\/en_US\/messages\.json: SyntaxError"), - "Got syntax error"); - - yield extension.initAllLocales(); - - equal(extension.errors.length, 2, "One error reported"); - - do_print(`Got error: ${extension.errors[1]}`); - - ok(extension.errors[1].includes("Loading locale file _locales\/en_US\/messages\.json: SyntaxError"), - "Got syntax error"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_native_messaging.js b/toolkit/components/extensions/test/xpcshell/test_native_messaging.js deleted file mode 100644 index 1fcb7799e..000000000 --- a/toolkit/components/extensions/test/xpcshell/test_native_messaging.js +++ /dev/null @@ -1,302 +0,0 @@ -"use strict"; - -/* global OS, HostManifestManager, NativeApp */ -Cu.import("resource://gre/modules/AppConstants.jsm"); -Cu.import("resource://gre/modules/AsyncShutdown.jsm"); -Cu.import("resource://gre/modules/ExtensionCommon.jsm"); -Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource://gre/modules/Schemas.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -const {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm"); -Cu.import("resource://gre/modules/NativeMessaging.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); - -let registry = null; -if (AppConstants.platform == "win") { - Cu.import("resource://testing-common/MockRegistry.jsm"); - registry = new MockRegistry(); - do_register_cleanup(() => { - registry.shutdown(); - }); -} - -const REGPATH = "Software\\Mozilla\\NativeMessagingHosts"; - -const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json"; - -let dir = FileUtils.getDir("TmpD", ["NativeMessaging"]); -dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - -let userDir = dir.clone(); -userDir.append("user"); -userDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - -let globalDir = dir.clone(); -globalDir.append("global"); -globalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - -let dirProvider = { - getFile(property) { - if (property == "XREUserNativeMessaging") { - return userDir.clone(); - } else if (property == "XRESysNativeMessaging") { - return globalDir.clone(); - } - return null; - }, -}; - -Services.dirsvc.registerProvider(dirProvider); - -do_register_cleanup(() => { - Services.dirsvc.unregisterProvider(dirProvider); - dir.remove(true); -}); - -function writeManifest(path, manifest) { - if (typeof manifest != "string") { - manifest = JSON.stringify(manifest); - } - return OS.File.writeAtomic(path, manifest); -} - -let PYTHON; -add_task(function* setup() { - yield Schemas.load(BASE_SCHEMA); - - PYTHON = yield Subprocess.pathSearch("python2.7"); - if (PYTHON == null) { - PYTHON = yield Subprocess.pathSearch("python"); - } - notEqual(PYTHON, null, "Found a suitable python interpreter"); -}); - -let global = this; - -// Test of HostManifestManager.lookupApplication() begin here... -let context = { - url: null, - jsonStringify(...args) { return JSON.stringify(...args); }, - cloneScope: global, - logError() {}, - preprocessors: {}, - callOnClose: () => {}, - forgetOnClose: () => {}, -}; - -class MockContext extends ExtensionCommon.BaseContext { - constructor(extensionId) { - let fakeExtension = {id: extensionId}; - super("testEnv", fakeExtension); - this.sandbox = Cu.Sandbox(global); - } - - get cloneScope() { - return global; - } - - get principal() { - return Cu.getObjectPrincipal(this.sandbox); - } -} - -let templateManifest = { - name: "test", - description: "this is only a test", - path: "/bin/cat", - type: "stdio", - allowed_extensions: ["extension@tests.mozilla.org"], -}; - -add_task(function* test_nonexistent_manifest() { - let result = yield HostManifestManager.lookupApplication("test", context); - equal(result, null, "lookupApplication returns null for non-existent application"); -}); - -const USER_TEST_JSON = OS.Path.join(userDir.path, "test.json"); - -add_task(function* test_good_manifest() { - yield writeManifest(USER_TEST_JSON, templateManifest); - if (registry) { - registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, - `${REGPATH}\\test`, "", USER_TEST_JSON); - } - - let result = yield HostManifestManager.lookupApplication("test", context); - notEqual(result, null, "lookupApplication finds a good manifest"); - equal(result.path, USER_TEST_JSON, "lookupApplication returns the correct path"); - deepEqual(result.manifest, templateManifest, "lookupApplication returns the manifest contents"); -}); - -add_task(function* test_invalid_json() { - yield writeManifest(USER_TEST_JSON, "this is not valid json"); - let result = yield HostManifestManager.lookupApplication("test", context); - equal(result, null, "lookupApplication ignores bad json"); -}); - -add_task(function* test_invalid_name() { - let manifest = Object.assign({}, templateManifest); - manifest.name = "../test"; - yield writeManifest(USER_TEST_JSON, manifest); - let result = yield HostManifestManager.lookupApplication("test", context); - equal(result, null, "lookupApplication ignores an invalid name"); -}); - -add_task(function* test_name_mismatch() { - let manifest = Object.assign({}, templateManifest); - manifest.name = "not test"; - yield writeManifest(USER_TEST_JSON, manifest); - let result = yield HostManifestManager.lookupApplication("test", context); - let what = (AppConstants.platform == "win") ? "registry key" : "json filename"; - equal(result, null, `lookupApplication ignores mistmatch between ${what} and name property`); -}); - -add_task(function* test_missing_props() { - const PROPS = [ - "name", - "description", - "path", - "type", - "allowed_extensions", - ]; - for (let prop of PROPS) { - let manifest = Object.assign({}, templateManifest); - delete manifest[prop]; - - yield writeManifest(USER_TEST_JSON, manifest); - let result = yield HostManifestManager.lookupApplication("test", context); - equal(result, null, `lookupApplication ignores missing ${prop}`); - } -}); - -add_task(function* test_invalid_type() { - let manifest = Object.assign({}, templateManifest); - manifest.type = "bogus"; - yield writeManifest(USER_TEST_JSON, manifest); - let result = yield HostManifestManager.lookupApplication("test", context); - equal(result, null, "lookupApplication ignores invalid type"); -}); - -add_task(function* test_no_allowed_extensions() { - let manifest = Object.assign({}, templateManifest); - manifest.allowed_extensions = []; - yield writeManifest(USER_TEST_JSON, manifest); - let result = yield HostManifestManager.lookupApplication("test", context); - equal(result, null, "lookupApplication ignores manifest with no allowed_extensions"); -}); - -const GLOBAL_TEST_JSON = OS.Path.join(globalDir.path, "test.json"); -let globalManifest = Object.assign({}, templateManifest); -globalManifest.description = "This manifest is from the systemwide directory"; - -add_task(function* good_manifest_system_dir() { - yield OS.File.remove(USER_TEST_JSON); - yield writeManifest(GLOBAL_TEST_JSON, globalManifest); - if (registry) { - registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, - `${REGPATH}\\test`, "", null); - registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, - `${REGPATH}\\test`, "", GLOBAL_TEST_JSON); - } - - let where = (AppConstants.platform == "win") ? "registry location" : "directory"; - let result = yield HostManifestManager.lookupApplication("test", context); - notEqual(result, null, `lookupApplication finds a manifest in the system-wide ${where}`); - equal(result.path, GLOBAL_TEST_JSON, `lookupApplication returns path in the system-wide ${where}`); - deepEqual(result.manifest, globalManifest, `lookupApplication returns manifest contents from the system-wide ${where}`); -}); - -add_task(function* test_user_dir_precedence() { - yield writeManifest(USER_TEST_JSON, templateManifest); - if (registry) { - registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, - `${REGPATH}\\test`, "", USER_TEST_JSON); - } - // global test.json and LOCAL_MACHINE registry key on windows are - // still present from the previous test - - let result = yield HostManifestManager.lookupApplication("test", context); - notEqual(result, null, "lookupApplication finds a manifest when entries exist in both user-specific and system-wide locations"); - equal(result.path, USER_TEST_JSON, "lookupApplication returns the user-specific path when user-specific and system-wide entries both exist"); - deepEqual(result.manifest, templateManifest, "lookupApplication returns user-specific manifest contents with user-specific and system-wide entries both exist"); -}); - -// Test shutdown handling in NativeApp -add_task(function* test_native_app_shutdown() { - const SCRIPT = String.raw` -import signal -import struct -import sys - -signal.signal(signal.SIGTERM, signal.SIG_IGN) - -while True: - rawlen = sys.stdin.read(4) - if len(rawlen) == 0: - signal.pause() - msglen = struct.unpack('@I', rawlen)[0] - msg = sys.stdin.read(msglen) - - sys.stdout.write(struct.pack('@I', msglen)) - sys.stdout.write(msg) - `; - - let scriptPath = OS.Path.join(userDir.path, "wontdie.py"); - let manifestPath = OS.Path.join(userDir.path, "wontdie.json"); - - const ID = "native@tests.mozilla.org"; - let manifest = { - name: "wontdie", - description: "test async shutdown of native apps", - type: "stdio", - allowed_extensions: [ID], - }; - - if (AppConstants.platform == "win") { - yield OS.File.writeAtomic(scriptPath, SCRIPT); - - let batPath = OS.Path.join(userDir.path, "wontdie.bat"); - let batBody = `@ECHO OFF\n${PYTHON} -u "${scriptPath}" %*\n`; - yield OS.File.writeAtomic(batPath, batBody); - yield OS.File.setPermissions(batPath, {unixMode: 0o755}); - - manifest.path = batPath; - yield writeManifest(manifestPath, manifest); - - registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, - `${REGPATH}\\wontdie`, "", manifestPath); - } else { - yield OS.File.writeAtomic(scriptPath, `#!${PYTHON} -u\n${SCRIPT}`); - yield OS.File.setPermissions(scriptPath, {unixMode: 0o755}); - manifest.path = scriptPath; - yield writeManifest(manifestPath, manifest); - } - - let mockContext = new MockContext(ID); - let app = new NativeApp(mockContext, "wontdie"); - - // send a message and wait for the reply to make sure the app is running - let MSG = "test"; - let recvPromise = new Promise(resolve => { - let listener = (what, msg) => { - equal(msg, MSG, "Received test message"); - app.off("message", listener); - resolve(); - }; - app.on("message", listener); - }); - - let buffer = NativeApp.encodeMessage(mockContext, MSG); - app.send(buffer); - yield recvPromise; - - app._cleanup(); - - do_print("waiting for async shutdown"); - Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); - AsyncShutdown.profileBeforeChange._trigger(); - Services.prefs.clearUserPref("toolkit.asyncshutdown.testing"); - - let procs = yield SubprocessImpl.Process.getWorker().call("getProcesses", []); - equal(procs.size, 0, "native process exited"); -}); diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell.ini b/toolkit/components/extensions/test/xpcshell/xpcshell.ini deleted file mode 100644 index d2c6fd5d0..000000000 --- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini +++ /dev/null @@ -1,69 +0,0 @@ -[DEFAULT] -head = head.js -tail = -firefox-appdir = browser -skip-if = appname == "thunderbird" -support-files = - data/** head_sync.js -tags = webextensions - -[test_csp_custom_policies.js] -[test_csp_validator.js] -[test_ext_alarms.js] -[test_ext_alarms_does_not_fire.js] -[test_ext_alarms_periodic.js] -[test_ext_alarms_replaces.js] -[test_ext_apimanager.js] -[test_ext_api_permissions.js] -[test_ext_background_generated_load_events.js] -[test_ext_background_generated_reload.js] -[test_ext_background_global_history.js] -skip-if = os == "android" # Android does not use Places for history. -[test_ext_background_private_browsing.js] -[test_ext_background_runtime_connect_params.js] -[test_ext_background_sub_windows.js] -[test_ext_background_window_properties.js] -skip-if = os == "android" -[test_ext_contexts.js] -[test_ext_downloads.js] -[test_ext_downloads_download.js] -skip-if = os == "android" -[test_ext_downloads_misc.js] -skip-if = os == "android" -[test_ext_downloads_search.js] -skip-if = os == "android" -[test_ext_experiments.js] -skip-if = release_or_beta -[test_ext_extension.js] -[test_ext_idle.js] -[test_ext_json_parser.js] -[test_ext_localStorage.js] -[test_ext_management.js] -[test_ext_management_uninstall_self.js] -[test_ext_manifest_content_security_policy.js] -[test_ext_manifest_incognito.js] -[test_ext_manifest_minimum_chrome_version.js] -[test_ext_onmessage_removelistener.js] -[test_ext_runtime_connect_no_receiver.js] -[test_ext_runtime_getBrowserInfo.js] -[test_ext_runtime_getPlatformInfo.js] -[test_ext_runtime_onInstalled_and_onStartup.js] -[test_ext_runtime_sendMessage.js] -[test_ext_runtime_sendMessage_errors.js] -[test_ext_runtime_sendMessage_no_receiver.js] -[test_ext_runtime_sendMessage_self.js] -[test_ext_schemas.js] -[test_ext_schemas_api_injection.js] -[test_ext_schemas_async.js] -[test_ext_schemas_allowed_contexts.js] -[test_ext_simple.js] -[test_ext_storage.js] -[test_ext_topSites.js] -skip-if = os == "android" -[test_getAPILevelForWindow.js] -[test_ext_legacy_extension_context.js] -[test_ext_legacy_extension_embedding.js] -[test_locale_converter.js] -[test_locale_data.js] -[test_native_messaging.js] -skip-if = os == "android" diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index 509332800..ce8722910 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -25,7 +25,6 @@ DIRS += [ 'crashmonitor', 'diskspacewatcher', 'downloads', - 'extensions', 'exthelper', 'filewatcher', 'finalizationwitness', @@ -68,6 +67,9 @@ DIRS += [ 'xulstore' ] +if CONFIG['MOZ_WEBEXTENSIONS']: + DIRS += ['webextensions'] + if CONFIG['ENABLE_INTL_API']: DIRS += ['mozintl'] diff --git a/toolkit/components/webextensions/.eslintrc.js b/toolkit/components/webextensions/.eslintrc.js new file mode 100644 index 000000000..70196fc6a --- /dev/null +++ b/toolkit/components/webextensions/.eslintrc.js @@ -0,0 +1,497 @@ +"use strict"; + +module.exports = { // eslint-disable-line no-undef + "extends": "../../.eslintrc.js", + + "parserOptions": { + "ecmaVersion": 8, + }, + + "globals": { + "Cc": true, + "Ci": true, + "Components": true, + "Cr": true, + "Cu": true, + "dump": true, + "TextDecoder": false, + "TextEncoder": false, + // Specific to WebExtensions: + "Extension": true, + "ExtensionManagement": true, + "extensions": true, + "global": true, + "NetUtil": true, + "openOptionsPage": true, + "require": false, + "runSafe": true, + "runSafeSync": true, + "runSafeSyncWithoutClone": true, + "Services": true, + "TabManager": true, + "WindowListManager": true, + "XPCOMUtils": true, + }, + + "rules": { + // Rules from the mozilla plugin + "mozilla/balanced-listeners": "error", + "mozilla/no-aArgs": "error", + "mozilla/no-cpows-in-tests": "warn", + "mozilla/var-only-at-top-level": "warn", + + "valid-jsdoc": ["error", { + "prefer": { + "return": "returns", + }, + "preferType": { + "Boolean": "boolean", + "Number": "number", + "String": "string", + "bool": "boolean", + }, + "requireParamDescription": false, + "requireReturn": false, + "requireReturnDescription": false, + }], + + // Braces only needed for multi-line arrow function blocks + // "arrow-body-style": ["error", "as-needed"], + + // Require spacing around => + "arrow-spacing": "error", + + // Always require spacing around a single line block + "block-spacing": "warn", + + // Forbid spaces inside the square brackets of array literals. + "array-bracket-spacing": ["error", "never"], + + // Forbid spaces inside the curly brackets of object literals. + "object-curly-spacing": ["error", "never"], + + // No space padding in parentheses + "space-in-parens": ["error", "never"], + + // Enforce one true brace style (opening brace on the same line) and avoid + // start and end braces on the same line. + "brace-style": ["error", "1tbs", {"allowSingleLine": true}], + + // No space before always a space after a comma + "comma-spacing": ["error", {"before": false, "after": true}], + + // Commas at the end of the line not the start + "comma-style": "error", + + // Don't require spaces around computed properties + "computed-property-spacing": ["error", "never"], + + // Functions are not required to consistently return something or nothing + "consistent-return": "off", + + // Require braces around blocks that start a new line + "curly": ["error", "all"], + + // Always require a trailing EOL + "eol-last": "error", + + // Require function* name() + "generator-star-spacing": ["error", {"before": false, "after": true}], + + // Two space indent + "indent": ["error", 2, {"SwitchCase": 1}], + + // Space after colon not before in property declarations + "key-spacing": ["error", {"beforeColon": false, "afterColon": true, "mode": "minimum"}], + + // Require spaces before and after finally, catch, etc. + "keyword-spacing": "error", + + // Unix linebreaks + "linebreak-style": ["error", "unix"], + + // Always require parenthesis for new calls + "new-parens": "error", + + // Use [] instead of Array() + "no-array-constructor": "error", + + // No duplicate arguments in function declarations + "no-dupe-args": "error", + + // No duplicate keys in object declarations + "no-dupe-keys": "error", + + // No duplicate cases in switch statements + "no-duplicate-case": "error", + + // If an if block ends with a return no need for an else block + // "no-else-return": "error", + + // Disallow empty statements. This will report an error for: + // try { something(); } catch (e) {} + // but will not report it for: + // try { something(); } catch (e) { /* Silencing the error because ...*/ } + // which is a valid use case. + "no-empty": "error", + + // No empty character classes in regex + "no-empty-character-class": "error", + + // Disallow empty destructuring + "no-empty-pattern": "error", + + // No assiging to exception variable + "no-ex-assign": "error", + + // No using !! where casting to boolean is already happening + "no-extra-boolean-cast": "warn", + + // No double semicolon + "no-extra-semi": "error", + + // No overwriting defined functions + "no-func-assign": "error", + + // No invalid regular expresions + "no-invalid-regexp": "error", + + // No odd whitespace characters + "no-irregular-whitespace": "error", + + // No single if block inside an else block + "no-lonely-if": "warn", + + // No mixing spaces and tabs in indent + "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], + + // Disallow use of multiple spaces (sometimes used to align const values, + // array or object items, etc.). It's hard to maintain and doesn't add that + // much benefit. + "no-multi-spaces": "warn", + + // No reassigning native JS objects + "no-native-reassign": "error", + + // No (!foo in bar) + "no-negated-in-lhs": "error", + + // Nested ternary statements are confusing + "no-nested-ternary": "error", + + // Use {} instead of new Object() + "no-new-object": "error", + + // No Math() or JSON() + "no-obj-calls": "error", + + // No octal literals + "no-octal": "error", + + // No redeclaring variables + "no-redeclare": "error", + + // No unnecessary comparisons + "no-self-compare": "error", + + // No spaces between function name and parentheses + "no-spaced-func": "warn", + + // No trailing whitespace + "no-trailing-spaces": "error", + + // Error on newline where a semicolon is needed + "no-unexpected-multiline": "error", + + // No unreachable statements + "no-unreachable": "error", + + // No expressions where a statement is expected + "no-unused-expressions": "error", + + // No declaring variables that are never used + "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$"}], + + // No using variables before defined + "no-use-before-define": "error", + + // No using with + "no-with": "error", + + // Always require semicolon at end of statement + "semi": ["error", "always"], + + // Require space before blocks + "space-before-blocks": "error", + + // Never use spaces before function parentheses + "space-before-function-paren": ["error", {"anonymous": "never", "named": "never"}], + + // Require spaces around operators, except for a|0. + "space-infix-ops": ["error", {"int32Hint": true}], + + // ++ and -- should not need spacing + "space-unary-ops": ["warn", {"nonwords": false, "words": true, "overrides": {"typeof": false}}], + + // No comparisons to NaN + "use-isnan": "error", + + // Only check typeof against valid results + "valid-typeof": "error", + + // Disallow using variables outside the blocks they are defined (especially + // since only let and const are used, see "no-var"). + "block-scoped-var": "error", + + // Allow trailing commas for easy list extension. Having them does not + // impair readability, but also not required either. + "comma-dangle": ["error", "always-multiline"], + + // Warn about cyclomatic complexity in functions. + "complexity": "warn", + + // Don't warn for inconsistent naming when capturing this (not so important + // with auto-binding fat arrow functions). + // "consistent-this": ["error", "self"], + + // Don't require a default case in switch statements. Avoid being forced to + // add a bogus default when you know all possible cases are handled. + "default-case": "off", + + // Enforce dots on the next line with property name. + "dot-location": ["error", "property"], + + // Encourage the use of dot notation whenever possible. + "dot-notation": "error", + + // Allow using == instead of ===, in the interest of landing something since + // the devtools codebase is split on convention here. + "eqeqeq": "off", + + // Don't require function expressions to have a name. + // This makes the code more verbose and hard to read. Our engine already + // does a fantastic job assigning a name to the function, which includes + // the enclosing function name, and worst case you have a line number that + // you can just look up. + "func-names": "off", + + // Allow use of function declarations and expressions. + "func-style": "off", + + // Don't enforce the maximum depth that blocks can be nested. The complexity + // rule is a better rule to check this. + "max-depth": "off", + + // Maximum length of a line. + // Disabled because we exceed this in too many places. + "max-len": [0, 80], + + // Maximum depth callbacks can be nested. + "max-nested-callbacks": ["error", 4], + + // Don't limit the number of parameters that can be used in a function. + "max-params": "off", + + // Don't limit the maximum number of statement allowed in a function. We + // already have the complexity rule that's a better measurement. + "max-statements": "off", + + // Don't require a capital letter for constructors, only check if all new + // operators are followed by a capital letter. Don't warn when capitalized + // functions are used without the new operator. + "new-cap": ["off", {"capIsNew": false}], + + // Allow use of bitwise operators. + "no-bitwise": "off", + + // Disallow use of arguments.caller or arguments.callee. + "no-caller": "error", + + // Disallow the catch clause parameter name being the same as a variable in + // the outer scope, to avoid confusion. + "no-catch-shadow": "off", + + // Disallow assignment in conditional expressions. + "no-cond-assign": "error", + + // Disallow using the console API. + "no-console": "error", + + // Allow using constant expressions in conditions like while (true) + "no-constant-condition": "off", + + // Allow use of the continue statement. + "no-continue": "off", + + // Disallow control characters in regular expressions. + "no-control-regex": "error", + + // Disallow use of debugger. + "no-debugger": "error", + + // Disallow deletion of variables (deleting properties is fine). + "no-delete-var": "error", + + // Allow division operators explicitly at beginning of regular expression. + "no-div-regex": "off", + + // Disallow use of eval(). We have other APIs to evaluate code in content. + "no-eval": "error", + + // Disallow adding to native types + "no-extend-native": "error", + + // Disallow unnecessary function binding. + "no-extra-bind": "error", + + // Allow unnecessary parentheses, as they may make the code more readable. + "no-extra-parens": "off", + + // Disallow fallthrough of case statements, except if there is a comment. + "no-fallthrough": "error", + + // Allow the use of leading or trailing decimal points in numeric literals. + "no-floating-decimal": "off", + + // Allow comments inline after code. + "no-inline-comments": "off", + + // Disallow use of labels for anything other then loops and switches. + "no-labels": ["error", {"allowLoop": true}], + + // Disallow use of multiline strings (use template strings instead). + "no-multi-str": "warn", + + // Disallow multiple empty lines. + "no-multiple-empty-lines": [1, {"max": 2}], + + // Allow reassignment of function parameters. + "no-param-reassign": "off", + + // Allow string concatenation with __dirname and __filename (not a node env). + "no-path-concat": "off", + + // Allow use of unary operators, ++ and --. + "no-plusplus": "off", + + // Allow using process.env (not a node environment). + "no-process-env": "off", + + // Allow using process.exit (not a node environment). + "no-process-exit": "off", + + // Disallow usage of __proto__ property. + "no-proto": "error", + + // Disallow multiple spaces in a regular expression literal. + "no-regex-spaces": "error", + + // Allow reserved words being used as object literal keys. + "no-reserved-keys": "off", + + // Don't restrict usage of specified node modules (not a node environment). + "no-restricted-modules": "off", + + // Disallow use of assignment in return statement. It is preferable for a + // single line of code to have only one easily predictable effect. + "no-return-assign": "error", + + // Don't warn about declaration of variables already declared in the outer scope. + "no-shadow": "off", + + // Disallow shadowing of names such as arguments. + "no-shadow-restricted-names": "error", + + // Allow use of synchronous methods (not a node environment). + "no-sync": "off", + + // Allow the use of ternary operators. + "no-ternary": "off", + + // Disallow throwing literals (eg. throw "error" instead of + // throw new Error("error")). + "no-throw-literal": "error", + + // Disallow use of undeclared variables unless mentioned in a /* global */ + // block. Note that globals from head.js are automatically imported in tests + // by the import-headjs-globals rule form the mozilla eslint plugin. + "no-undef": "error", + + // Allow dangling underscores in identifiers (for privates). + "no-underscore-dangle": "off", + + // Allow use of undefined variable. + "no-undefined": "off", + + // Disallow the use of Boolean literals in conditional expressions. + "no-unneeded-ternary": "error", + + // We use var-only-at-top-level instead of no-var as we allow top level + // vars. + "no-var": "off", + + // Allow using TODO/FIXME comments. + "no-warning-comments": "off", + + // Don't require method and property shorthand syntax for object literals. + // We use this in the code a lot, but not consistently, and this seems more + // like something to check at code review time. + "object-shorthand": "off", + + // Allow more than one variable declaration per function. + "one-var": "off", + + // Disallow padding within blocks. + "padded-blocks": ["warn", "never"], + + // Don't require quotes around object literal property names. + "quote-props": "off", + + // Double quotes should be used. + "quotes": ["warn", "double", {"avoidEscape": true, "allowTemplateLiterals": true}], + + // Require use of the second argument for parseInt(). + "radix": "error", + + // Enforce spacing after semicolons. + "semi-spacing": ["error", {"before": false, "after": true}], + + // Don't require to sort variables within the same declaration block. + // Anyway, one-var is disabled. + "sort-vars": "off", + + // Require a space immediately following the // in a line comment. + "spaced-comment": ["error", "always"], + + // Require "use strict" to be defined globally in the script. + "strict": ["error", "global"], + + // Allow vars to be declared anywhere in the scope. + "vars-on-top": "off", + + // Don't require immediate function invocation to be wrapped in parentheses. + "wrap-iife": "off", + + // Don't require regex literals to be wrapped in parentheses (which + // supposedly prevent them from being mistaken for division operators). + "wrap-regex": "off", + + // Disallow Yoda conditions (where literal value comes first). + "yoda": "error", + + // disallow use of eval()-like methods + "no-implied-eval": "error", + + // Disallow function or variable declarations in nested blocks + "no-inner-declarations": "error", + + // Disallow usage of __iterator__ property + "no-iterator": "error", + + // Disallow labels that share a name with a variable + "no-label-var": "error", + + // Disallow creating new instances of String, Number, and Boolean + "no-new-wrappers": "error", + }, +}; diff --git a/toolkit/components/webextensions/Extension.jsm b/toolkit/components/webextensions/Extension.jsm new file mode 100644 index 000000000..3468f2594 --- /dev/null +++ b/toolkit/components/webextensions/Extension.jsm @@ -0,0 +1,902 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["Extension", "ExtensionData"]; + +/* globals Extension ExtensionData */ + +/* + * This file is the main entry point for extensions. When an extension + * loads, its bootstrap.js file creates a Extension instance + * and calls .startup() on it. It calls .shutdown() when the extension + * unloads. Extension manages any extension-specific state in + * the chrome process. + */ + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.importGlobalProperties(["TextEncoder"]); + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", + "resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionAPIs", + "resource://gre/modules/ExtensionAPI.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage", + "resource://gre/modules/ExtensionStorage.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestCommon", + "resource://testing-common/ExtensionTestCommon.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Locale", + "resource://gre/modules/Locale.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Log", + "resource://gre/modules/Log.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MatchGlobs", + "resource://gre/modules/MatchPattern.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern", + "resource://gre/modules/MatchPattern.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", + "resource://gre/modules/MessageChannel.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Preferences", + "resource://gre/modules/Preferences.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "require", + "resource://devtools/shared/Loader.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + +Cu.import("resource://gre/modules/ExtensionContent.jsm"); +Cu.import("resource://gre/modules/ExtensionManagement.jsm"); +Cu.import("resource://gre/modules/ExtensionParent.jsm"); +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "uuidGen", + "@mozilla.org/uuid-generator;1", + "nsIUUIDGenerator"); + +var { + GlobalManager, + ParentAPIManager, + apiManager: Management, +} = ExtensionParent; + +const { + EventEmitter, + LocaleData, + getUniqueId, +} = ExtensionUtils; + +XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole); + +const LOGGER_ID_BASE = "addons.webextension."; +const UUID_MAP_PREF = "extensions.webextensions.uuids"; +const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall"; +const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall"; + +const COMMENT_REGEXP = new RegExp(String.raw` + ^ + ( + (?: + [^"\n] | + " (?:[^"\\\n] | \\.)* " + )*? + ) + + //.* + `.replace(/\s+/g, ""), "gm"); + +// All moz-extension URIs use a machine-specific UUID rather than the +// extension's own ID in the host component. This makes it more +// difficult for web pages to detect whether a user has a given add-on +// installed (by trying to load a moz-extension URI referring to a +// web_accessible_resource from the extension). UUIDMap.get() +// returns the UUID for a given add-on ID. +var UUIDMap = { + _read() { + let pref = Preferences.get(UUID_MAP_PREF, "{}"); + try { + return JSON.parse(pref); + } catch (e) { + Cu.reportError(`Error parsing ${UUID_MAP_PREF}.`); + return {}; + } + }, + + _write(map) { + Preferences.set(UUID_MAP_PREF, JSON.stringify(map)); + }, + + get(id, create = true) { + let map = this._read(); + + if (id in map) { + return map[id]; + } + + let uuid = null; + if (create) { + uuid = uuidGen.generateUUID().number; + uuid = uuid.slice(1, -1); // Strip { and } off the UUID. + + map[id] = uuid; + this._write(map); + } + return uuid; + }, + + remove(id) { + let map = this._read(); + delete map[id]; + this._write(map); + }, +}; + +// This is the old interface that UUIDMap replaced, to be removed when +// the references listed in bug 1291399 are updated. +/* exported getExtensionUUID */ +function getExtensionUUID(id) { + return UUIDMap.get(id, true); +} + +// For extensions that have called setUninstallURL(), send an event +// so the browser can display the URL. +var UninstallObserver = { + initialized: false, + + init() { + if (!this.initialized) { + AddonManager.addAddonListener(this); + XPCOMUtils.defineLazyPreferenceGetter(this, "leaveStorage", LEAVE_STORAGE_PREF, false); + XPCOMUtils.defineLazyPreferenceGetter(this, "leaveUuid", LEAVE_UUID_PREF, false); + this.initialized = true; + } + }, + + onUninstalling(addon) { + let extension = GlobalManager.extensionMap.get(addon.id); + if (extension) { + // Let any other interested listeners respond + // (e.g., display the uninstall URL) + Management.emit("uninstall", extension); + } + }, + + onUninstalled(addon) { + let uuid = UUIDMap.get(addon.id, false); + if (!uuid) { + return; + } + + if (!this.leaveStorage) { + // Clear browser.local.storage + ExtensionStorage.clear(addon.id); + + // Clear any IndexedDB storage created by the extension + let baseURI = NetUtil.newURI(`moz-extension://${uuid}/`); + let principal = Services.scriptSecurityManager.createCodebasePrincipal( + baseURI, {addonId: addon.id} + ); + Services.qms.clearStoragesForPrincipal(principal); + + // Clear localStorage created by the extension + let attrs = JSON.stringify({addonId: addon.id}); + Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs); + } + + if (!this.leaveUuid) { + // Clear the entry in the UUID map + UUIDMap.remove(addon.id); + } + }, +}; + +UninstallObserver.init(); + +// Represents the data contained in an extension, contained either +// in a directory or a zip file, which may or may not be installed. +// This class implements the functionality of the Extension class, +// primarily related to manifest parsing and localization, which is +// useful prior to extension installation or initialization. +// +// No functionality of this class is guaranteed to work before +// |readManifest| has been called, and completed. +this.ExtensionData = class { + constructor(rootURI) { + this.rootURI = rootURI; + + this.manifest = null; + this.id = null; + this.uuid = null; + this.localeData = null; + this._promiseLocales = null; + + this.apiNames = new Set(); + this.dependencies = new Set(); + this.permissions = new Set(); + + this.errors = []; + } + + get builtinMessages() { + return null; + } + + get logger() { + let id = this.id || ""; + return Log.repository.getLogger(LOGGER_ID_BASE + id); + } + + // Report an error about the extension's manifest file. + manifestError(message) { + this.packagingError(`Reading manifest: ${message}`); + } + + // Report an error about the extension's general packaging. + packagingError(message) { + this.errors.push(message); + this.logger.error(`Loading extension '${this.id}': ${message}`); + } + + /** + * Returns the moz-extension: URL for the given path within this + * extension. + * + * Must not be called unless either the `id` or `uuid` property has + * already been set. + * + * @param {string} path The path portion of the URL. + * @returns {string} + */ + getURL(path = "") { + if (!(this.id || this.uuid)) { + throw new Error("getURL may not be called before an `id` or `uuid` has been set"); + } + if (!this.uuid) { + this.uuid = UUIDMap.get(this.id); + } + return `moz-extension://${this.uuid}/${path}`; + } + + readDirectory(path) { + return Task.spawn(function* () { + if (this.rootURI instanceof Ci.nsIFileURL) { + let uri = NetUtil.newURI(this.rootURI.resolve("./" + path)); + let fullPath = uri.QueryInterface(Ci.nsIFileURL).file.path; + + let iter = new OS.File.DirectoryIterator(fullPath); + let results = []; + + try { + yield iter.forEach(entry => { + results.push(entry); + }); + } catch (e) { + // Always return a list, even if the directory does not exist (or is + // not a directory) for symmetry with the ZipReader behavior. + } + iter.close(); + + return results; + } + + // FIXME: We need a way to do this without main thread IO. + + let uri = this.rootURI.QueryInterface(Ci.nsIJARURI); + + let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file; + let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader); + zipReader.open(file); + try { + let results = []; + + // Normalize the directory path. + path = `${uri.JAREntry}/${path}`; + path = path.replace(/\/\/+/g, "/").replace(/^\/|\/$/g, "") + "/"; + + // Escape pattern metacharacters. + let pattern = path.replace(/[[\]()?*~|$\\]/g, "\\$&"); + + let enumerator = zipReader.findEntries(pattern + "*"); + while (enumerator.hasMore()) { + let name = enumerator.getNext(); + if (!name.startsWith(path)) { + throw new Error("Unexpected ZipReader entry"); + } + + // The enumerator returns the full path of all entries. + // Trim off the leading path, and filter out entries from + // subdirectories. + name = name.slice(path.length); + if (name && !/\/./.test(name)) { + results.push({ + name: name.replace("/", ""), + isDir: name.endsWith("/"), + }); + } + } + + return results; + } finally { + zipReader.close(); + } + }.bind(this)); + } + + readJSON(path) { + return new Promise((resolve, reject) => { + let uri = this.rootURI.resolve(`./${path}`); + + NetUtil.asyncFetch({uri, loadUsingSystemPrincipal: true}, (inputStream, status) => { + if (!Components.isSuccessCode(status)) { + // Convert status code to a string + let e = Components.Exception("", status); + reject(new Error(`Error while loading '${uri}' (${e.name})`)); + return; + } + try { + let text = NetUtil.readInputStreamToString(inputStream, inputStream.available(), + {charset: "utf-8"}); + + text = text.replace(COMMENT_REGEXP, "$1"); + + resolve(JSON.parse(text)); + } catch (e) { + reject(e); + } + }); + }); + } + + // Reads the extension's |manifest.json| file, and stores its + // parsed contents in |this.manifest|. + readManifest() { + return Promise.all([ + this.readJSON("manifest.json"), + Management.lazyInit(), + ]).then(([manifest]) => { + this.manifest = manifest; + this.rawManifest = manifest; + + if (manifest && manifest.default_locale) { + return this.initLocale(); + } + }).then(() => { + let context = { + url: this.baseURI && this.baseURI.spec, + + principal: this.principal, + + logError: error => { + this.logger.warn(`Loading extension '${this.id}': Reading manifest: ${error}`); + }, + + preprocessors: {}, + }; + + if (this.localeData) { + context.preprocessors.localize = (value, context) => this.localize(value); + } + + let normalized = Schemas.normalize(this.manifest, "manifest.WebExtensionManifest", context); + if (normalized.error) { + this.manifestError(normalized.error); + } else { + this.manifest = normalized.value; + } + + try { + // Do not override the add-on id that has been already assigned. + if (!this.id && this.manifest.applications.gecko.id) { + this.id = this.manifest.applications.gecko.id; + } + } catch (e) { + // Errors are handled by the type checks above. + } + + let permissions = this.manifest.permissions || []; + + let whitelist = []; + for (let perm of permissions) { + this.permissions.add(perm); + + let match = /^(\w+)(?:\.(\w+)(?:\.\w+)*)?$/.exec(perm); + if (!match) { + whitelist.push(perm); + } else if (match[1] == "experiments" && match[2]) { + this.apiNames.add(match[2]); + } + } + this.whiteListedHosts = new MatchPattern(whitelist); + + for (let api of this.apiNames) { + this.dependencies.add(`${api}@experiments.addons.mozilla.org`); + } + + return this.manifest; + }); + } + + localizeMessage(...args) { + return this.localeData.localizeMessage(...args); + } + + localize(...args) { + return this.localeData.localize(...args); + } + + // If a "default_locale" is specified in that manifest, returns it + // as a Gecko-compatible locale string. Otherwise, returns null. + get defaultLocale() { + if (this.manifest.default_locale != null) { + return this.normalizeLocaleCode(this.manifest.default_locale); + } + + return null; + } + + // Normalizes a Chrome-compatible locale code to the appropriate + // Gecko-compatible variant. Currently, this means simply + // replacing underscores with hyphens. + normalizeLocaleCode(locale) { + return String.replace(locale, /_/g, "-"); + } + + // Reads the locale file for the given Gecko-compatible locale code, and + // stores its parsed contents in |this.localeMessages.get(locale)|. + readLocaleFile(locale) { + return Task.spawn(function* () { + let locales = yield this.promiseLocales(); + let dir = locales.get(locale) || locale; + let file = `_locales/${dir}/messages.json`; + + try { + let messages = yield this.readJSON(file); + return this.localeData.addLocale(locale, messages, this); + } catch (e) { + this.packagingError(`Loading locale file ${file}: ${e}`); + return new Map(); + } + }.bind(this)); + } + + // Reads the list of locales available in the extension, and returns a + // Promise which resolves to a Map upon completion. + // Each map key is a Gecko-compatible locale code, and each value is the + // "_locales" subdirectory containing that locale: + // + // Map(gecko-locale-code -> locale-directory-name) + promiseLocales() { + if (!this._promiseLocales) { + this._promiseLocales = Task.spawn(function* () { + let locales = new Map(); + + let entries = yield this.readDirectory("_locales"); + for (let file of entries) { + if (file.isDir) { + let locale = this.normalizeLocaleCode(file.name); + locales.set(locale, file.name); + } + } + + this.localeData = new LocaleData({ + defaultLocale: this.defaultLocale, + locales, + builtinMessages: this.builtinMessages, + }); + + return locales; + }.bind(this)); + } + + return this._promiseLocales; + } + + // Reads the locale messages for all locales, and returns a promise which + // resolves to a Map of locale messages upon completion. Each key in the map + // is a Gecko-compatible locale code, and each value is a locale data object + // as returned by |readLocaleFile|. + initAllLocales() { + return Task.spawn(function* () { + let locales = yield this.promiseLocales(); + + yield Promise.all(Array.from(locales.keys(), + locale => this.readLocaleFile(locale))); + + let defaultLocale = this.defaultLocale; + if (defaultLocale) { + if (!locales.has(defaultLocale)) { + this.manifestError('Value for "default_locale" property must correspond to ' + + 'a directory in "_locales/". Not found: ' + + JSON.stringify(`_locales/${this.manifest.default_locale}/`)); + } + } else if (locales.size) { + this.manifestError('The "default_locale" property is required when a ' + + '"_locales/" directory is present.'); + } + + return this.localeData.messages; + }.bind(this)); + } + + // Reads the locale file for the given Gecko-compatible locale code, or the + // default locale if no locale code is given, and sets it as the currently + // selected locale on success. + // + // Pre-loads the default locale for fallback message processing, regardless + // of the locale specified. + // + // If no locales are unavailable, resolves to |null|. + initLocale(locale = this.defaultLocale) { + return Task.spawn(function* () { + if (locale == null) { + return null; + } + + let promises = [this.readLocaleFile(locale)]; + + let {defaultLocale} = this; + if (locale != defaultLocale && !this.localeData.has(defaultLocale)) { + promises.push(this.readLocaleFile(defaultLocale)); + } + + let results = yield Promise.all(promises); + + this.localeData.selectedLocale = locale; + return results[0]; + }.bind(this)); + } +}; + +let _browserUpdated = false; + +const PROXIED_EVENTS = new Set(["test-harness-message"]); + +// We create one instance of this class per extension. |addonData| +// comes directly from bootstrap.js when initializing. +this.Extension = class extends ExtensionData { + constructor(addonData, startupReason) { + super(addonData.resourceURI); + + this.uuid = UUIDMap.get(addonData.id); + this.instanceId = getUniqueId(); + + this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`; + Services.ppmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this); + + if (addonData.cleanupFile) { + Services.obs.addObserver(this, "xpcom-shutdown", false); + this.cleanupFile = addonData.cleanupFile || null; + delete addonData.cleanupFile; + } + + this.addonData = addonData; + this.startupReason = startupReason; + + this.id = addonData.id; + this.baseURI = NetUtil.newURI(this.getURL("")).QueryInterface(Ci.nsIURL); + this.principal = this.createPrincipal(); + + this.onStartup = null; + + this.hasShutdown = false; + this.onShutdown = new Set(); + + this.uninstallURL = null; + + this.apis = []; + this.whiteListedHosts = null; + this.webAccessibleResources = null; + + this.emitter = new EventEmitter(); + } + + static set browserUpdated(updated) { + _browserUpdated = updated; + } + + static get browserUpdated() { + return _browserUpdated; + } + + static generateXPI(data) { + return ExtensionTestCommon.generateXPI(data); + } + + static generateZipFile(files, baseName = "generated-extension.xpi") { + return ExtensionTestCommon.generateZipFile(files, baseName); + } + + static generate(data) { + return ExtensionTestCommon.generate(data); + } + + on(hook, f) { + return this.emitter.on(hook, f); + } + + off(hook, f) { + return this.emitter.off(hook, f); + } + + emit(event, ...args) { + if (PROXIED_EVENTS.has(event)) { + Services.ppmm.broadcastAsyncMessage(this.MESSAGE_EMIT_EVENT, {event, args}); + } + + return this.emitter.emit(event, ...args); + } + + receiveMessage({name, data}) { + if (name === this.MESSAGE_EMIT_EVENT) { + this.emitter.emit(data.event, ...data.args); + } + } + + testMessage(...args) { + this.emit("test-harness-message", ...args); + } + + createPrincipal(uri = this.baseURI) { + return Services.scriptSecurityManager.createCodebasePrincipal( + uri, {addonId: this.id}); + } + + // Checks that the given URL is a child of our baseURI. + isExtensionURL(url) { + let uri = Services.io.newURI(url, null, null); + + let common = this.baseURI.getCommonBaseSpec(uri); + return common == this.baseURI.spec; + } + + readManifest() { + return super.readManifest().then(manifest => { + if (AppConstants.RELEASE_OR_BETA) { + return manifest; + } + + // Load Experiments APIs that this extension depends on. + return Promise.all( + Array.from(this.apiNames, api => ExtensionAPIs.load(api)) + ).then(apis => { + for (let API of apis) { + this.apis.push(new API(this)); + } + + return manifest; + }); + }); + } + + // Representation of the extension to send to content + // processes. This should include anything the content process might + // need. + serialize() { + return { + id: this.id, + uuid: this.uuid, + instanceId: this.instanceId, + manifest: this.manifest, + resourceURL: this.addonData.resourceURI.spec, + baseURL: this.baseURI.spec, + content_scripts: this.manifest.content_scripts || [], // eslint-disable-line camelcase + webAccessibleResources: this.webAccessibleResources.serialize(), + whiteListedHosts: this.whiteListedHosts.serialize(), + localeData: this.localeData.serialize(), + permissions: this.permissions, + principal: this.principal, + }; + } + + broadcast(msg, data) { + return new Promise(resolve => { + let count = Services.ppmm.childCount; + Services.ppmm.addMessageListener(msg + "Complete", function listener() { + count--; + if (count == 0) { + Services.ppmm.removeMessageListener(msg + "Complete", listener); + resolve(); + } + }); + Services.ppmm.broadcastAsyncMessage(msg, data); + }); + } + + runManifest(manifest) { + // Strip leading slashes from web_accessible_resources. + let strippedWebAccessibleResources = []; + if (manifest.web_accessible_resources) { + strippedWebAccessibleResources = manifest.web_accessible_resources.map(path => path.replace(/^\/+/, "")); + } + + this.webAccessibleResources = new MatchGlobs(strippedWebAccessibleResources); + + let promises = []; + for (let directive in manifest) { + if (manifest[directive] !== null) { + promises.push(Management.emit(`manifest_${directive}`, directive, this, manifest)); + } + } + + let data = Services.ppmm.initialProcessData; + if (!data["Extension:Extensions"]) { + data["Extension:Extensions"] = []; + } + let serial = this.serialize(); + data["Extension:Extensions"].push(serial); + + return this.broadcast("Extension:Startup", serial).then(() => { + return Promise.all(promises); + }); + } + + callOnClose(obj) { + this.onShutdown.add(obj); + } + + forgetOnClose(obj) { + this.onShutdown.delete(obj); + } + + get builtinMessages() { + return new Map([ + ["@@extension_id", this.uuid], + ]); + } + + // Reads the locale file for the given Gecko-compatible locale code, or if + // no locale is given, the available locale closest to the UI locale. + // Sets the currently selected locale on success. + initLocale(locale = undefined) { + // Ugh. + let super_ = super.initLocale.bind(this); + + return Task.spawn(function* () { + if (locale === undefined) { + let locales = yield this.promiseLocales(); + + let localeList = Array.from(locales.keys(), locale => { + return {name: locale, locales: [locale]}; + }); + + let match = Locale.findClosestLocale(localeList); + locale = match ? match.name : this.defaultLocale; + } + + return super_(locale); + }.bind(this)); + } + + startup() { + let started = false; + return this.readManifest().then(() => { + ExtensionManagement.startupExtension(this.uuid, this.addonData.resourceURI, this); + started = true; + + if (!this.hasShutdown) { + return this.initLocale(); + } + }).then(() => { + if (this.errors.length) { + return Promise.reject({errors: this.errors}); + } + + if (this.hasShutdown) { + return; + } + + GlobalManager.init(this); + + // The "startup" Management event sent on the extension instance itself + // is emitted just before the Management "startup" event, + // and it is used to run code that needs to be executed before + // any of the "startup" listeners. + this.emit("startup", this); + Management.emit("startup", this); + + return this.runManifest(this.manifest); + }).then(() => { + Management.emit("ready", this); + }).catch(e => { + dump(`Extension error: ${e.message} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`); + Cu.reportError(e); + + if (started) { + ExtensionManagement.shutdownExtension(this.uuid); + } + + this.cleanupGeneratedFile(); + + throw e; + }); + } + + cleanupGeneratedFile() { + if (!this.cleanupFile) { + return; + } + + let file = this.cleanupFile; + this.cleanupFile = null; + + Services.obs.removeObserver(this, "xpcom-shutdown"); + + this.broadcast("Extension:FlushJarCache", {path: file.path}).then(() => { + // We can't delete this file until everyone using it has + // closed it (because Windows is dumb). So we wait for all the + // child processes (including the parent) to flush their JAR + // caches. These caches may keep the file open. + file.remove(false); + }); + } + + shutdown() { + this.hasShutdown = true; + + Services.ppmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this); + + if (!this.manifest) { + ExtensionManagement.shutdownExtension(this.uuid); + + this.cleanupGeneratedFile(); + return; + } + + GlobalManager.uninit(this); + + for (let obj of this.onShutdown) { + obj.close(); + } + + for (let api of this.apis) { + api.destroy(); + } + + ParentAPIManager.shutdownExtension(this.id); + + Management.emit("shutdown", this); + + Services.ppmm.broadcastAsyncMessage("Extension:Shutdown", {id: this.id}); + + MessageChannel.abortResponses({extensionId: this.id}); + + ExtensionManagement.shutdownExtension(this.uuid); + + this.cleanupGeneratedFile(); + } + + observe(subject, topic, data) { + if (topic == "xpcom-shutdown") { + this.cleanupGeneratedFile(); + } + } + + hasPermission(perm) { + let match = /^manifest:(.*)/.exec(perm); + if (match) { + return this.manifest[match[1]] != null; + } + + return this.permissions.has(perm); + } + + get name() { + return this.manifest.name; + } +}; diff --git a/toolkit/components/webextensions/ExtensionAPI.jsm b/toolkit/components/webextensions/ExtensionAPI.jsm new file mode 100644 index 000000000..54dab8e3b --- /dev/null +++ b/toolkit/components/webextensions/ExtensionAPI.jsm @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["ExtensionAPI", "ExtensionAPIs"]; + +/* exported ExtensionAPIs */ + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/ExtensionManagement.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter", + "resource://devtools/shared/event-emitter.js"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + +const global = this; + +class ExtensionAPI { + constructor(extension) { + this.extension = extension; + } + + destroy() { + } + + getAPI(context) { + throw new Error("Not Implemented"); + } +} + +var ExtensionAPIs = { + apis: ExtensionManagement.APIs.apis, + + load(apiName) { + let api = this.apis.get(apiName); + + if (api.loadPromise) { + return api.loadPromise; + } + + let {script, schema} = api; + + let addonId = `${apiName}@experiments.addons.mozilla.org`; + api.sandbox = Cu.Sandbox(global, { + wantXrays: false, + sandboxName: script, + addonId, + metadata: {addonID: addonId}, + }); + + api.sandbox.ExtensionAPI = ExtensionAPI; + + Services.scriptloader.loadSubScript(script, api.sandbox, "UTF-8"); + + api.loadPromise = Schemas.load(schema).then(() => { + return Cu.evalInSandbox("API", api.sandbox); + }); + + return api.loadPromise; + }, + + unload(apiName) { + let api = this.apis.get(apiName); + + let {schema} = api; + + Schemas.unload(schema); + Cu.nukeSandbox(api.sandbox); + + api.sandbox = null; + api.loadPromise = null; + }, +}; diff --git a/toolkit/components/webextensions/ExtensionChild.jsm b/toolkit/components/webextensions/ExtensionChild.jsm new file mode 100644 index 000000000..c953dd685 --- /dev/null +++ b/toolkit/components/webextensions/ExtensionChild.jsm @@ -0,0 +1,1040 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["ExtensionChild"]; + +/* + * This file handles addon logic that is independent of the chrome process. + * When addons run out-of-process, this is the main entry point. + * Its primary function is managing addon globals. + * + * Don't put contentscript logic here, use ExtensionContent.jsm instead. + */ + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", + "resource://gre/modules/MessageChannel.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NativeApp", + "resource://gre/modules/NativeMessaging.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils", + "resource://gre/modules/PromiseUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); + +const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon"; + +Cu.import("resource://gre/modules/ExtensionCommon.jsm"); +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +const { + DefaultMap, + EventManager, + SingletonEventManager, + SpreadArgs, + defineLazyGetter, + getInnerWindowID, + getMessageManager, + getUniqueId, + injectAPI, +} = ExtensionUtils; + +const { + BaseContext, + LocalAPIImplementation, + SchemaAPIInterface, + SchemaAPIManager, +} = ExtensionCommon; + +var ExtensionChild; + +/** + * Abstraction for a Port object in the extension API. + * + * @param {BaseContext} context The context that owns this port. + * @param {nsIMessageSender} senderMM The message manager to send messages to. + * @param {Array} receiverMMs Message managers to + * listen on. + * @param {string} name Arbitrary port name as defined by the addon. + * @param {string} id An ID that uniquely identifies this port's channel. + * @param {object} sender The `port.sender` property. + * @param {object} recipient The recipient of messages sent from this port. + */ +class Port { + constructor(context, senderMM, receiverMMs, name, id, sender, recipient) { + this.context = context; + this.senderMM = senderMM; + this.receiverMMs = receiverMMs; + this.name = name; + this.id = id; + this.sender = sender; + this.recipient = recipient; + this.disconnected = false; + this.disconnectListeners = new Set(); + this.unregisterMessageFuncs = new Set(); + + // Common options for onMessage and onDisconnect. + this.handlerBase = { + messageFilterStrict: {portId: id}, + + filterMessage: (sender, recipient) => { + return sender.contextId !== this.context.contextId; + }, + }; + + this.disconnectHandler = Object.assign({ + receiveMessage: ({data}) => this.disconnectByOtherEnd(data), + }, this.handlerBase); + + MessageChannel.addListener(this.receiverMMs, "Extension:Port:Disconnect", this.disconnectHandler); + + this.context.callOnClose(this); + } + + api() { + let portObj = Cu.createObjectIn(this.context.cloneScope); + + let portError = null; + let publicAPI = { + name: this.name, + + disconnect: () => { + this.disconnect(); + }, + + postMessage: json => { + this.postMessage(json); + }, + + onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => { + return this.registerOnDisconnect(error => { + portError = error && this.context.normalizeError(error); + fire.withoutClone(portObj); + }); + }).api(), + + onMessage: new EventManager(this.context, "Port.onMessage", fire => { + return this.registerOnMessage(msg => { + msg = Cu.cloneInto(msg, this.context.cloneScope); + fire.withoutClone(msg, portObj); + }); + }).api(), + + get error() { + return portError; + }, + }; + + if (this.sender) { + publicAPI.sender = this.sender; + } + + injectAPI(publicAPI, portObj); + return portObj; + } + + postMessage(json) { + if (this.disconnected) { + throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port"); + } + + this._sendMessage("Extension:Port:PostMessage", json); + } + + /** + * Register a callback that is called when the port is disconnected by the + * *other* end. The callback is automatically unregistered when the port or + * context is closed. + * + * @param {function} callback Called when the other end disconnects the port. + * If the disconnect is caused by an error, the first parameter is an + * object with a "message" string property that describes the cause. + * @returns {function} Function to unregister the listener. + */ + registerOnDisconnect(callback) { + let listener = error => { + if (this.context.active && !this.disconnected) { + callback(error); + } + }; + this.disconnectListeners.add(listener); + return () => { + this.disconnectListeners.delete(listener); + }; + } + + /** + * Register a callback that is called when a message is received. The callback + * is automatically unregistered when the port or context is closed. + * + * @param {function} callback Called when a message is received. + * @returns {function} Function to unregister the listener. + */ + registerOnMessage(callback) { + let handler = Object.assign({ + receiveMessage: ({data}) => { + if (this.context.active && !this.disconnected) { + callback(data); + } + }, + }, this.handlerBase); + + let unregister = () => { + this.unregisterMessageFuncs.delete(unregister); + MessageChannel.removeListener(this.receiverMMs, "Extension:Port:PostMessage", handler); + }; + MessageChannel.addListener(this.receiverMMs, "Extension:Port:PostMessage", handler); + this.unregisterMessageFuncs.add(unregister); + return unregister; + } + + _sendMessage(message, data) { + let options = { + recipient: Object.assign({}, this.recipient, {portId: this.id}), + responseType: MessageChannel.RESPONSE_NONE, + }; + + return this.context.sendMessage(this.senderMM, message, data, options); + } + + handleDisconnection() { + MessageChannel.removeListener(this.receiverMMs, "Extension:Port:Disconnect", this.disconnectHandler); + for (let unregister of this.unregisterMessageFuncs) { + unregister(); + } + this.context.forgetOnClose(this); + this.disconnected = true; + } + + /** + * Disconnect the port from the other end (which may not even exist). + * + * @param {Error|{message: string}} [error] The reason for disconnecting, + * if it is an abnormal disconnect. + */ + disconnectByOtherEnd(error = null) { + if (this.disconnected) { + return; + } + + for (let listener of this.disconnectListeners) { + listener(error); + } + + this.handleDisconnection(); + } + + /** + * Disconnect the port from this end. + * + * @param {Error|{message: string}} [error] The reason for disconnecting, + * if it is an abnormal disconnect. + */ + disconnect(error = null) { + if (this.disconnected) { + // disconnect() may be called without side effects even after the port is + // closed - https://developer.chrome.com/extensions/runtime#type-Port + return; + } + this.handleDisconnection(); + if (error) { + error = {message: this.context.normalizeError(error).message}; + } + this._sendMessage("Extension:Port:Disconnect", error); + } + + close() { + this.disconnect(); + } +} + +class NativePort extends Port { + postMessage(data) { + data = NativeApp.encodeMessage(this.context, data); + + return super.postMessage(data); + } +} + +/** + * Each extension context gets its own Messenger object. It handles the + * basics of sendMessage, onMessage, connect and onConnect. + * + * @param {BaseContext} context The context to which this Messenger is tied. + * @param {Array} messageManagers + * The message managers used to receive messages (e.g. onMessage/onConnect + * requests). + * @param {object} sender Describes this sender to the recipient. This object + * is extended further by BaseContext's sendMessage method and appears as + * the `sender` object to `onConnect` and `onMessage`. + * Do not set the `extensionId`, `contextId` or `tab` properties. The former + * two are added by BaseContext's sendMessage, while `sender.tab` is set by + * the ProxyMessenger in the main process. + * @param {object} filter A recipient filter to apply to incoming messages from + * the broker. Messages are only handled by this Messenger if all key-value + * pairs match the `recipient` as specified by the sender of the message. + * In other words, this filter defines the required fields of `recipient`. + * @param {object} [optionalFilter] An additional filter to apply to incoming + * messages. Unlike `filter`, the keys from `optionalFilter` are allowed to + * be omitted from `recipient`. Only keys that are present in both + * `optionalFilter` and `recipient` are applied to filter incoming messages. + */ +class Messenger { + constructor(context, messageManagers, sender, filter, optionalFilter) { + this.context = context; + this.messageManagers = messageManagers; + this.sender = sender; + this.filter = filter; + this.optionalFilter = optionalFilter; + } + + _sendMessage(messageManager, message, data, recipient) { + let options = { + recipient, + sender: this.sender, + responseType: MessageChannel.RESPONSE_FIRST, + }; + + return this.context.sendMessage(messageManager, message, data, options); + } + + sendMessage(messageManager, msg, recipient, responseCallback) { + let promise = this._sendMessage(messageManager, "Extension:Message", msg, recipient) + .catch(error => { + if (error.result == MessageChannel.RESULT_NO_HANDLER) { + return Promise.reject({message: "Could not establish connection. Receiving end does not exist."}); + } else if (error.result != MessageChannel.RESULT_NO_RESPONSE) { + return Promise.reject({message: error.message}); + } + }); + + return this.context.wrapPromise(promise, responseCallback); + } + + sendNativeMessage(messageManager, msg, recipient, responseCallback) { + msg = NativeApp.encodeMessage(this.context, msg); + return this.sendMessage(messageManager, msg, recipient, responseCallback); + } + + onMessage(name) { + return new SingletonEventManager(this.context, name, callback => { + let listener = { + messageFilterPermissive: this.optionalFilter, + messageFilterStrict: this.filter, + + filterMessage: (sender, recipient) => { + // Ignore the message if it was sent by this Messenger. + return sender.contextId !== this.context.contextId; + }, + + receiveMessage: ({target, data: message, sender, recipient}) => { + if (!this.context.active) { + return; + } + + let sendResponse; + let response = undefined; + let promise = new Promise(resolve => { + sendResponse = value => { + resolve(value); + response = promise; + }; + }); + + message = Cu.cloneInto(message, this.context.cloneScope); + sender = Cu.cloneInto(sender, this.context.cloneScope); + sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope); + + // Note: We intentionally do not use runSafe here so that any + // errors are propagated to the message sender. + let result = callback(message, sender, sendResponse); + if (result instanceof this.context.cloneScope.Promise) { + return result; + } else if (result === true) { + return promise; + } + return response; + }, + }; + + MessageChannel.addListener(this.messageManagers, "Extension:Message", listener); + return () => { + MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener); + }; + }).api(); + } + + _connect(messageManager, port, recipient) { + let msg = { + name: port.name, + portId: port.id, + }; + + this._sendMessage(messageManager, "Extension:Connect", msg, recipient).catch(error => { + if (error.result === MessageChannel.RESULT_NO_HANDLER) { + error = {message: "Could not establish connection. Receiving end does not exist."}; + } else if (error.result === MessageChannel.RESULT_DISCONNECTED) { + error = null; + } + port.disconnectByOtherEnd(error); + }); + + return port.api(); + } + + connect(messageManager, name, recipient) { + let portId = getUniqueId(); + + let port = new Port(this.context, messageManager, this.messageManagers, name, portId, null, recipient); + + return this._connect(messageManager, port, recipient); + } + + connectNative(messageManager, name, recipient) { + let portId = getUniqueId(); + + let port = new NativePort(this.context, messageManager, this.messageManagers, name, portId, null, recipient); + + return this._connect(messageManager, port, recipient); + } + + onConnect(name) { + return new SingletonEventManager(this.context, name, callback => { + let listener = { + messageFilterPermissive: this.optionalFilter, + messageFilterStrict: this.filter, + + filterMessage: (sender, recipient) => { + // Ignore the port if it was created by this Messenger. + return sender.contextId !== this.context.contextId; + }, + + receiveMessage: ({target, data: message, sender}) => { + let {name, portId} = message; + let mm = getMessageManager(target); + let recipient = Object.assign({}, sender); + if (recipient.tab) { + recipient.tabId = recipient.tab.id; + delete recipient.tab; + } + let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient); + this.context.runSafeWithoutClone(callback, port.api()); + return true; + }, + }; + + MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener); + return () => { + MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener); + }; + }).api(); + } +} + +var apiManager = new class extends SchemaAPIManager { + constructor() { + super("addon"); + this.initialized = false; + } + + generateAPIs(...args) { + if (!this.initialized) { + this.initialized = true; + for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS_ADDON)) { + this.loadScript(value); + } + } + return super.generateAPIs(...args); + } + + registerSchemaAPI(namespace, envType, getAPI) { + if (envType == "addon_child") { + super.registerSchemaAPI(namespace, envType, getAPI); + } + } +}(); + +/** + * An object that runs an remote implementation of an API. + */ +class ProxyAPIImplementation extends SchemaAPIInterface { + /** + * @param {string} namespace The full path to the namespace that contains the + * `name` member. This may contain dots, e.g. "storage.local". + * @param {string} name The name of the method or property. + * @param {ChildAPIManager} childApiManager The owner of this implementation. + */ + constructor(namespace, name, childApiManager) { + super(); + this.path = `${namespace}.${name}`; + this.childApiManager = childApiManager; + } + + callFunctionNoReturn(args) { + this.childApiManager.callParentFunctionNoReturn(this.path, args); + } + + callAsyncFunction(args, callback) { + return this.childApiManager.callParentAsyncFunction(this.path, args, callback); + } + + addListener(listener, args) { + let map = this.childApiManager.listeners.get(this.path); + + if (map.listeners.has(listener)) { + // TODO: Called with different args? + return; + } + + let id = getUniqueId(); + + map.ids.set(id, listener); + map.listeners.set(listener, id); + + this.childApiManager.messageManager.sendAsyncMessage("API:AddListener", { + childId: this.childApiManager.id, + listenerId: id, + path: this.path, + args, + }); + } + + removeListener(listener) { + let map = this.childApiManager.listeners.get(this.path); + + if (!map.listeners.has(listener)) { + return; + } + + let id = map.listeners.get(listener); + map.listeners.delete(listener); + map.ids.delete(id); + + this.childApiManager.messageManager.sendAsyncMessage("API:RemoveListener", { + childId: this.childApiManager.id, + listenerId: id, + path: this.path, + }); + } + + hasListener(listener) { + let map = this.childApiManager.listeners.get(this.path); + return map.listeners.has(listener); + } +} + +// We create one instance of this class for every extension context that +// needs to use remote APIs. It uses the message manager to communicate +// with the ParentAPIManager singleton in ExtensionParent.jsm. It +// handles asynchronous function calls as well as event listeners. +class ChildAPIManager { + constructor(context, messageManager, localApis, contextData) { + this.context = context; + this.messageManager = messageManager; + this.url = contextData.url; + + // The root namespace of all locally implemented APIs. If an extension calls + // an API that does not exist in this object, then the implementation is + // delegated to the ParentAPIManager. + this.localApis = localApis; + + this.id = `${context.extension.id}.${context.contextId}`; + + MessageChannel.addListener(messageManager, "API:RunListener", this); + messageManager.addMessageListener("API:CallResult", this); + + this.messageFilterStrict = {childId: this.id}; + + this.listeners = new DefaultMap(() => ({ + ids: new Map(), + listeners: new Map(), + })); + + // Map[callId -> Deferred] + this.callPromises = new Map(); + + let params = { + childId: this.id, + extensionId: context.extension.id, + principal: context.principal, + }; + Object.assign(params, contextData); + + this.messageManager.sendAsyncMessage("API:CreateProxyContext", params); + } + + receiveMessage({name, messageName, data}) { + if (data.childId != this.id) { + return; + } + + switch (name || messageName) { + case "API:RunListener": + let map = this.listeners.get(data.path); + let listener = map.ids.get(data.listenerId); + + if (listener) { + return this.context.runSafe(listener, ...data.args); + } + + Cu.reportError(`Unknown listener at childId=${data.childId} path=${data.path} listenerId=${data.listenerId}\n`); + break; + + case "API:CallResult": + let deferred = this.callPromises.get(data.callId); + if ("error" in data) { + deferred.reject(data.error); + } else { + deferred.resolve(new SpreadArgs(data.result)); + } + this.callPromises.delete(data.callId); + break; + } + } + + /** + * Call a function in the parent process and ignores its return value. + * + * @param {string} path The full name of the method, e.g. "tabs.create". + * @param {Array} args The parameters for the function. + */ + callParentFunctionNoReturn(path, args) { + this.messageManager.sendAsyncMessage("API:Call", { + childId: this.id, + path, + args, + }); + } + + /** + * Calls a function in the parent process and returns its result + * asynchronously. + * + * @param {string} path The full name of the method, e.g. "tabs.create". + * @param {Array} args The parameters for the function. + * @param {function(*)} [callback] The callback to be called when the function + * completes. + * @returns {Promise|undefined} Must be void if `callback` is set, and a + * promise otherwise. The promise is resolved when the function completes. + */ + callParentAsyncFunction(path, args, callback) { + let callId = getUniqueId(); + let deferred = PromiseUtils.defer(); + this.callPromises.set(callId, deferred); + + this.messageManager.sendAsyncMessage("API:Call", { + childId: this.id, + callId, + path, + args, + }); + + return this.context.wrapPromise(deferred.promise, callback); + } + + /** + * Create a proxy for an event in the parent process. The returned event + * object shares its internal state with other instances. For instance, if + * `removeListener` is used on a listener that was added on another object + * through `addListener`, then the event is unregistered. + * + * @param {string} path The full name of the event, e.g. "tabs.onCreated". + * @returns {object} An object with the addListener, removeListener and + * hasListener methods. See SchemaAPIInterface for documentation. + */ + getParentEvent(path) { + path = path.split("."); + + let name = path.pop(); + let namespace = path.join("."); + + let impl = new ProxyAPIImplementation(namespace, name, this); + return { + addListener: (listener, ...args) => impl.addListener(listener, args), + removeListener: (listener) => impl.removeListener(listener), + hasListener: (listener) => impl.hasListener(listener), + }; + } + + close() { + this.messageManager.sendAsyncMessage("API:CloseProxyContext", {childId: this.id}); + } + + get cloneScope() { + return this.context.cloneScope; + } + + get principal() { + return this.context.principal; + } + + shouldInject(namespace, name, allowedContexts) { + // Do not generate content script APIs, unless explicitly allowed. + if (this.context.envType === "content_child" && + !allowedContexts.includes("content")) { + return false; + } + if (allowedContexts.includes("addon_parent_only")) { + return false; + } + return true; + } + + getImplementation(namespace, name) { + let obj = namespace.split(".").reduce( + (object, prop) => object && object[prop], + this.localApis); + + if (obj && name in obj) { + return new LocalAPIImplementation(obj, name, this.context); + } + + return this.getFallbackImplementation(namespace, name); + } + + getFallbackImplementation(namespace, name) { + // No local API found, defer implementation to the parent. + return new ProxyAPIImplementation(namespace, name, this); + } + + hasPermission(permission) { + return this.context.extension.hasPermission(permission); + } +} + +class ExtensionPageContextChild extends BaseContext { + /** + * This ExtensionPageContextChild represents a privileged addon + * execution environment that has full access to the WebExtensions + * APIs (provided that the correct permissions have been requested). + * + * This is the child side of the ExtensionPageContextParent class + * defined in ExtensionParent.jsm. + * + * @param {BrowserExtensionContent} extension This context's owner. + * @param {object} params + * @param {nsIDOMWindow} params.contentWindow The window where the addon runs. + * @param {string} params.viewType One of "background", "popup" or "tab". + * "background" and "tab" are used by `browser.extension.getViews`. + * "popup" is only used internally to identify page action and browser + * action popups and options_ui pages. + * @param {number} [params.tabId] This tab's ID, used if viewType is "tab". + */ + constructor(extension, params) { + super("addon_child", extension); + if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) { + // This check is temporary. It should be removed once the proxy creation + // is asynchronous. + throw new Error("ExtensionPageContextChild cannot be created in child processes"); + } + + let {viewType, uri, contentWindow, tabId} = params; + this.viewType = viewType; + this.uri = uri || extension.baseURI; + + this.setContentWindow(contentWindow); + + // This is the MessageSender property passed to extension. + // It can be augmented by the "page-open" hook. + let sender = {id: extension.uuid}; + if (viewType == "tab") { + sender.tabId = tabId; + this.tabId = tabId; + } + if (uri) { + sender.url = uri.spec; + } + this.sender = sender; + + Schemas.exportLazyGetter(contentWindow, "browser", () => { + let browserObj = Cu.createObjectIn(contentWindow); + Schemas.inject(browserObj, this.childManager); + return browserObj; + }); + + Schemas.exportLazyGetter(contentWindow, "chrome", () => { + let chromeApiWrapper = Object.create(this.childManager); + chromeApiWrapper.isChromeCompat = true; + + let chromeObj = Cu.createObjectIn(contentWindow); + Schemas.inject(chromeObj, chromeApiWrapper); + return chromeObj; + }); + + this.extension.views.add(this); + } + + get cloneScope() { + return this.contentWindow; + } + + get principal() { + return this.contentWindow.document.nodePrincipal; + } + + get windowId() { + if (this.viewType == "tab" || this.viewType == "popup") { + let globalView = ExtensionChild.contentGlobals.get(this.messageManager); + return globalView ? globalView.windowId : -1; + } + } + + // Called when the extension shuts down. + shutdown() { + this.unload(); + } + + // This method is called when an extension page navigates away or + // its tab is closed. + unload() { + // Note that without this guard, we end up running unload code + // multiple times for tab pages closed by the "page-unload" handlers + // triggered below. + if (this.unloaded) { + return; + } + + if (this.contentWindow) { + this.contentWindow.close(); + } + + super.unload(); + this.extension.views.delete(this); + } +} + +defineLazyGetter(ExtensionPageContextChild.prototype, "messenger", function() { + let filter = {extensionId: this.extension.id}; + let optionalFilter = {}; + // Addon-generated messages (not necessarily from the same process as the + // addon itself) are sent to the main process, which forwards them via the + // parent process message manager. Specific replies can be sent to the frame + // message manager. + return new Messenger(this, [Services.cpmm, this.messageManager], this.sender, + filter, optionalFilter); +}); + +defineLazyGetter(ExtensionPageContextChild.prototype, "childManager", function() { + let localApis = {}; + apiManager.generateAPIs(this, localApis); + + if (this.viewType == "background") { + apiManager.global.initializeBackgroundPage(this.contentWindow); + } + + let childManager = new ChildAPIManager(this, this.messageManager, localApis, { + envType: "addon_parent", + viewType: this.viewType, + url: this.uri.spec, + incognito: this.incognito, + }); + + this.callOnClose(childManager); + + return childManager; +}); + +// All subframes in a tab, background page, popup, etc. have the same view type. +// This class keeps track of such global state. +// Note that this is created even for non-extension tabs because at present we +// do not have a way to distinguish regular tabs from extension tabs at the +// initialization of a frame script. +class ContentGlobal { + /** + * @param {nsIContentFrameMessageManager} global The frame script's global. + */ + constructor(global) { + this.global = global; + // Unless specified otherwise assume that the extension page is in a tab, + // because the majority of all class instances are going to be a tab. Any + // special views (background page, extension popup) will immediately send an + // Extension:InitExtensionView message to change the viewType. + this.viewType = "tab"; + this.tabId = -1; + this.windowId = -1; + this.initialized = false; + this.global.addMessageListener("Extension:InitExtensionView", this); + this.global.addMessageListener("Extension:SetTabAndWindowId", this); + + this.initialDocuments = new WeakSet(); + } + + uninit() { + this.global.removeMessageListener("Extension:InitExtensionView", this); + this.global.removeMessageListener("Extension:SetTabAndWindowId", this); + this.global.removeEventListener("DOMContentLoaded", this); + } + + ensureInitialized() { + if (!this.initialized) { + // Request tab and window ID in case "Extension:InitExtensionView" is not + // sent (e.g. when `viewType` is "tab"). + let reply = this.global.sendSyncMessage("Extension:GetTabAndWindowId"); + this.handleSetTabAndWindowId(reply[0] || {}); + } + return this; + } + + receiveMessage({name, data}) { + switch (name) { + case "Extension:InitExtensionView": + // The view type is initialized once and then fixed. + this.global.removeMessageListener("Extension:InitExtensionView", this); + let {viewType, url} = data; + this.viewType = viewType; + this.global.addEventListener("DOMContentLoaded", this); + if (url) { + // TODO(robwu): Remove this check. It is only here because the popup + // implementation does not always load a URL at the initialization, + // and the logic is too complex to fix at once. + let {document} = this.global.content; + this.initialDocuments.add(document); + document.location.replace(url); + } + /* Falls through to allow these properties to be initialized at once */ + case "Extension:SetTabAndWindowId": + this.handleSetTabAndWindowId(data); + break; + } + } + + handleSetTabAndWindowId(data) { + let {tabId, windowId} = data; + if (tabId) { + // Tab IDs are not expected to change. + if (this.tabId !== -1 && tabId !== this.tabId) { + throw new Error("Attempted to change a tabId after it was set"); + } + this.tabId = tabId; + } + if (windowId !== undefined) { + // Window IDs may change if a tab is moved to a different location. + // Note: This is the ID of the browser window for the extension API. + // Do not confuse it with the innerWindowID of DOMWindows! + this.windowId = windowId; + } + this.initialized = true; + } + + // "DOMContentLoaded" event. + handleEvent(event) { + let {document} = this.global.content; + if (event.target === document) { + // If the document was still being loaded at the time of navigation, then + // the DOMContentLoaded event is fired for the old document. Ignore it. + if (this.initialDocuments.has(document)) { + this.initialDocuments.delete(document); + return; + } + this.global.removeEventListener("DOMContentLoaded", this); + this.global.sendAsyncMessage("Extension:ExtensionViewLoaded"); + } + } +} + +ExtensionChild = { + // Map + contentGlobals: new Map(), + + // Map + extensionContexts: new Map(), + + initOnce() { + // This initializes the default message handler for messages targeted at + // an addon process, in case the addon process receives a message before + // its Messenger has been instantiated. For example, if a content script + // sends a message while there is no background page. + MessageChannel.setupMessageManagers([Services.cpmm]); + }, + + init(global) { + this.contentGlobals.set(global, new ContentGlobal(global)); + }, + + uninit(global) { + this.contentGlobals.get(global).uninit(); + this.contentGlobals.delete(global); + }, + + /** + * Create a privileged context at document-element-inserted. + * + * @param {BrowserExtensionContent} extension + * The extension for which the context should be created. + * @param {nsIDOMWindow} contentWindow The global of the page. + */ + createExtensionContext(extension, contentWindow) { + let windowId = getInnerWindowID(contentWindow); + let context = this.extensionContexts.get(windowId); + if (context) { + if (context.extension !== extension) { + // Oops. This should never happen. + Cu.reportError("A different extension context already exists in this frame!"); + } else { + // This should not happen either. + Cu.reportError("The extension context was already initialized in this frame."); + } + return; + } + + let mm = contentWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + let {viewType, tabId} = this.contentGlobals.get(mm).ensureInitialized(); + + let uri = contentWindow.document.documentURIObject; + + context = new ExtensionPageContextChild(extension, {viewType, contentWindow, uri, tabId}); + this.extensionContexts.set(windowId, context); + }, + + /** + * Close the ExtensionPageContextChild belonging to the given window, if any. + * + * @param {number} windowId The inner window ID of the destroyed context. + */ + destroyExtensionContext(windowId) { + let context = this.extensionContexts.get(windowId); + if (context) { + context.unload(); + this.extensionContexts.delete(windowId); + } + }, + + shutdownExtension(extensionId) { + for (let [windowId, context] of this.extensionContexts) { + if (context.extension.id == extensionId) { + context.shutdown(); + this.extensionContexts.delete(windowId); + } + } + }, +}; + +// TODO(robwu): Change this condition when addons move to a separate process. +if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) { + Object.keys(ExtensionChild).forEach(function(key) { + if (typeof ExtensionChild[key] == "function") { + // :/ + ExtensionChild[key] = () => {}; + } + }); +} + +Object.assign(ExtensionChild, { + ChildAPIManager, + Messenger, + Port, +}); + diff --git a/toolkit/components/webextensions/ExtensionCommon.jsm b/toolkit/components/webextensions/ExtensionCommon.jsm new file mode 100644 index 000000000..a339fb27e --- /dev/null +++ b/toolkit/components/webextensions/ExtensionCommon.jsm @@ -0,0 +1,680 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 contains utilities and base classes for logic which is + * common between the parent and child process, and in particular + * between ExtensionParent.jsm and ExtensionChild.jsm. + */ + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +/* exported ExtensionCommon */ + +this.EXPORTED_SYMBOLS = ["ExtensionCommon"]; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", + "resource://gre/modules/MessageChannel.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +var { + EventEmitter, + ExtensionError, + SpreadArgs, + getConsole, + getInnerWindowID, + getUniqueId, + runSafeSync, + runSafeSyncWithoutClone, + instanceOf, +} = ExtensionUtils; + +XPCOMUtils.defineLazyGetter(this, "console", getConsole); + +class BaseContext { + constructor(envType, extension) { + this.envType = envType; + this.onClose = new Set(); + this.checkedLastError = false; + this._lastError = null; + this.contextId = getUniqueId(); + this.unloaded = false; + this.extension = extension; + this.jsonSandbox = null; + this.active = true; + this.incognito = null; + this.messageManager = null; + this.docShell = null; + this.contentWindow = null; + this.innerWindowID = 0; + } + + setContentWindow(contentWindow) { + let {document} = contentWindow; + let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell); + + this.innerWindowID = getInnerWindowID(contentWindow); + this.messageManager = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + + if (this.incognito == null) { + this.incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow); + } + + MessageChannel.setupMessageManagers([this.messageManager]); + + let onPageShow = event => { + if (!event || event.target === document) { + this.docShell = docShell; + this.contentWindow = contentWindow; + this.active = true; + } + }; + let onPageHide = event => { + if (!event || event.target === document) { + // Put this off until the next tick. + Promise.resolve().then(() => { + this.docShell = null; + this.contentWindow = null; + this.active = false; + }); + } + }; + + onPageShow(); + contentWindow.addEventListener("pagehide", onPageHide, true); + contentWindow.addEventListener("pageshow", onPageShow, true); + this.callOnClose({ + close: () => { + onPageHide(); + if (this.active) { + contentWindow.removeEventListener("pagehide", onPageHide, true); + contentWindow.removeEventListener("pageshow", onPageShow, true); + } + }, + }); + } + + get cloneScope() { + throw new Error("Not implemented"); + } + + get principal() { + throw new Error("Not implemented"); + } + + runSafe(...args) { + if (this.unloaded) { + Cu.reportError("context.runSafe called after context unloaded"); + } else if (!this.active) { + Cu.reportError("context.runSafe called while context is inactive"); + } else { + return runSafeSync(this, ...args); + } + } + + runSafeWithoutClone(...args) { + if (this.unloaded) { + Cu.reportError("context.runSafeWithoutClone called after context unloaded"); + } else if (!this.active) { + Cu.reportError("context.runSafeWithoutClone called while context is inactive"); + } else { + return runSafeSyncWithoutClone(...args); + } + } + + checkLoadURL(url, options = {}) { + let ssm = Services.scriptSecurityManager; + + let flags = ssm.STANDARD; + if (!options.allowScript) { + flags |= ssm.DISALLOW_SCRIPT; + } + if (!options.allowInheritsPrincipal) { + flags |= ssm.DISALLOW_INHERIT_PRINCIPAL; + } + if (options.dontReportErrors) { + flags |= ssm.DONT_REPORT_ERRORS; + } + + try { + ssm.checkLoadURIStrWithPrincipal(this.principal, url, flags); + } catch (e) { + return false; + } + return true; + } + + /** + * Safely call JSON.stringify() on an object that comes from an + * extension. + * + * @param {array} args Arguments for JSON.stringify() + * @returns {string} The stringified representation of obj + */ + jsonStringify(...args) { + if (!this.jsonSandbox) { + this.jsonSandbox = Cu.Sandbox(this.principal, { + sameZoneAs: this.cloneScope, + wantXrays: false, + }); + } + + return Cu.waiveXrays(this.jsonSandbox.JSON).stringify(...args); + } + + callOnClose(obj) { + this.onClose.add(obj); + } + + forgetOnClose(obj) { + this.onClose.delete(obj); + } + + /** + * A wrapper around MessageChannel.sendMessage which adds the extension ID + * to the recipient object, and ensures replies are not processed after the + * context has been unloaded. + * + * @param {nsIMessageManager} target + * @param {string} messageName + * @param {object} data + * @param {object} [options] + * @param {object} [options.sender] + * @param {object} [options.recipient] + * + * @returns {Promise} + */ + sendMessage(target, messageName, data, options = {}) { + options.recipient = options.recipient || {}; + options.sender = options.sender || {}; + + options.recipient.extensionId = this.extension.id; + options.sender.extensionId = this.extension.id; + options.sender.contextId = this.contextId; + + return MessageChannel.sendMessage(target, messageName, data, options); + } + + get lastError() { + this.checkedLastError = true; + return this._lastError; + } + + set lastError(val) { + this.checkedLastError = false; + this._lastError = val; + } + + /** + * Normalizes the given error object for use by the target scope. If + * the target is an error object which belongs to that scope, it is + * returned as-is. If it is an ordinary object with a `message` + * property, it is converted into an error belonging to the target + * scope. If it is an Error object which does *not* belong to the + * clone scope, it is reported, and converted to an unexpected + * exception error. + * + * @param {Error|object} error + * @returns {Error} + */ + normalizeError(error) { + if (error instanceof this.cloneScope.Error) { + return error; + } + let message; + if (instanceOf(error, "Object") || error instanceof ExtensionError) { + message = error.message; + } else if (typeof error == "object" && + this.principal.subsumes(Cu.getObjectPrincipal(error))) { + message = error.message; + } else { + Cu.reportError(error); + } + message = message || "An unexpected error occurred"; + return new this.cloneScope.Error(message); + } + + /** + * Sets the value of `.lastError` to `error`, calls the given + * callback, and reports an error if the value has not been checked + * when the callback returns. + * + * @param {object} error An object with a `message` property. May + * optionally be an `Error` object belonging to the target scope. + * @param {function} callback The callback to call. + * @returns {*} The return value of callback. + */ + withLastError(error, callback) { + this.lastError = this.normalizeError(error); + try { + return callback(); + } finally { + if (!this.checkedLastError) { + Cu.reportError(`Unchecked lastError value: ${this.lastError}`); + } + this.lastError = null; + } + } + + /** + * Wraps the given promise so it can be safely returned to extension + * code in this context. + * + * If `callback` is provided, however, it is used as a completion + * function for the promise, and no promise is returned. In this case, + * the callback is called when the promise resolves or rejects. In the + * latter case, `lastError` is set to the rejection value, and the + * callback function must check `browser.runtime.lastError` or + * `extension.runtime.lastError` in order to prevent it being reported + * to the console. + * + * @param {Promise} promise The promise with which to wrap the + * callback. May resolve to a `SpreadArgs` instance, in which case + * each element will be used as a separate argument. + * + * Unless the promise object belongs to the cloneScope global, its + * resolution value is cloned into cloneScope prior to calling the + * `callback` function or resolving the wrapped promise. + * + * @param {function} [callback] The callback function to wrap + * + * @returns {Promise|undefined} If callback is null, a promise object + * belonging to the target scope. Otherwise, undefined. + */ + wrapPromise(promise, callback = null) { + let runSafe = this.runSafe.bind(this); + if (promise instanceof this.cloneScope.Promise) { + runSafe = this.runSafeWithoutClone.bind(this); + } + + if (callback) { + promise.then( + args => { + if (this.unloaded) { + dump(`Promise resolved after context unloaded\n`); + } else if (!this.active) { + dump(`Promise resolved while context is inactive\n`); + } else if (args instanceof SpreadArgs) { + runSafe(callback, ...args); + } else { + runSafe(callback, args); + } + }, + error => { + this.withLastError(error, () => { + if (this.unloaded) { + dump(`Promise rejected after context unloaded\n`); + } else if (!this.active) { + dump(`Promise rejected while context is inactive\n`); + } else { + this.runSafeWithoutClone(callback); + } + }); + }); + } else { + return new this.cloneScope.Promise((resolve, reject) => { + promise.then( + value => { + if (this.unloaded) { + dump(`Promise resolved after context unloaded\n`); + } else if (!this.active) { + dump(`Promise resolved while context is inactive\n`); + } else if (value instanceof SpreadArgs) { + runSafe(resolve, value.length == 1 ? value[0] : value); + } else { + runSafe(resolve, value); + } + }, + value => { + if (this.unloaded) { + dump(`Promise rejected after context unloaded: ${value && value.message}\n`); + } else if (!this.active) { + dump(`Promise rejected while context is inactive: ${value && value.message}\n`); + } else { + this.runSafeWithoutClone(reject, this.normalizeError(value)); + } + }); + }); + } + } + + unload() { + this.unloaded = true; + + MessageChannel.abortResponses({ + extensionId: this.extension.id, + contextId: this.contextId, + }); + + for (let obj of this.onClose) { + obj.close(); + } + } + + /** + * A simple proxy for unload(), for use with callOnClose(). + */ + close() { + this.unload(); + } +} + +/** + * An object that runs the implementation of a schema API. Instantiations of + * this interfaces are used by Schemas.jsm. + * + * @interface + */ +class SchemaAPIInterface { + /** + * Calls this as a function that returns its return value. + * + * @abstract + * @param {Array} args The parameters for the function. + * @returns {*} The return value of the invoked function. + */ + callFunction(args) { + throw new Error("Not implemented"); + } + + /** + * Calls this as a function and ignores its return value. + * + * @abstract + * @param {Array} args The parameters for the function. + */ + callFunctionNoReturn(args) { + throw new Error("Not implemented"); + } + + /** + * Calls this as a function that completes asynchronously. + * + * @abstract + * @param {Array} args The parameters for the function. + * @param {function(*)} [callback] The callback to be called when the function + * completes. + * @returns {Promise|undefined} Must be void if `callback` is set, and a + * promise otherwise. The promise is resolved when the function completes. + */ + callAsyncFunction(args, callback) { + throw new Error("Not implemented"); + } + + /** + * Retrieves the value of this as a property. + * + * @abstract + * @returns {*} The value of the property. + */ + getProperty() { + throw new Error("Not implemented"); + } + + /** + * Assigns the value to this as property. + * + * @abstract + * @param {string} value The new value of the property. + */ + setProperty(value) { + throw new Error("Not implemented"); + } + + /** + * Registers a `listener` to this as an event. + * + * @abstract + * @param {function} listener The callback to be called when the event fires. + * @param {Array} args Extra parameters for EventManager.addListener. + * @see EventManager.addListener + */ + addListener(listener, args) { + throw new Error("Not implemented"); + } + + /** + * Checks whether `listener` is listening to this as an event. + * + * @abstract + * @param {function} listener The event listener. + * @returns {boolean} Whether `listener` is registered with this as an event. + * @see EventManager.hasListener + */ + hasListener(listener) { + throw new Error("Not implemented"); + } + + /** + * Unregisters `listener` from this as an event. + * + * @abstract + * @param {function} listener The event listener. + * @see EventManager.removeListener + */ + removeListener(listener) { + throw new Error("Not implemented"); + } +} + +/** + * An object that runs a locally implemented API. + */ +class LocalAPIImplementation extends SchemaAPIInterface { + /** + * Constructs an implementation of the `name` method or property of `pathObj`. + * + * @param {object} pathObj The object containing the member with name `name`. + * @param {string} name The name of the implemented member. + * @param {BaseContext} context The context in which the schema is injected. + */ + constructor(pathObj, name, context) { + super(); + this.pathObj = pathObj; + this.name = name; + this.context = context; + } + + callFunction(args) { + return this.pathObj[this.name](...args); + } + + callFunctionNoReturn(args) { + this.pathObj[this.name](...args); + } + + callAsyncFunction(args, callback) { + let promise; + try { + promise = this.pathObj[this.name](...args) || Promise.resolve(); + } catch (e) { + promise = Promise.reject(e); + } + return this.context.wrapPromise(promise, callback); + } + + getProperty() { + return this.pathObj[this.name]; + } + + setProperty(value) { + this.pathObj[this.name] = value; + } + + addListener(listener, args) { + try { + this.pathObj[this.name].addListener.call(null, listener, ...args); + } catch (e) { + throw this.context.normalizeError(e); + } + } + + hasListener(listener) { + return this.pathObj[this.name].hasListener.call(null, listener); + } + + removeListener(listener) { + this.pathObj[this.name].removeListener.call(null, listener); + } +} + +/** + * This object loads the ext-*.js scripts that define the extension API. + * + * This class instance is shared with the scripts that it loads, so that the + * ext-*.js scripts and the instantiator can communicate with each other. + */ +class SchemaAPIManager extends EventEmitter { + /** + * @param {string} processType + * "main" - The main, one and only chrome browser process. + * "addon" - An addon process. + * "content" - A content process. + */ + constructor(processType) { + super(); + this.processType = processType; + this.global = this._createExtGlobal(); + this._scriptScopes = []; + this._schemaApis = { + addon_parent: [], + addon_child: [], + content_parent: [], + content_child: [], + }; + } + + /** + * Create a global object that is used as the shared global for all ext-*.js + * scripts that are loaded via `loadScript`. + * + * @returns {object} A sandbox that is used as the global by `loadScript`. + */ + _createExtGlobal() { + let global = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { + wantXrays: false, + sandboxName: `Namespace of ext-*.js scripts for ${this.processType}`, + }); + + Object.assign(global, {global, Cc, Ci, Cu, Cr, XPCOMUtils, extensions: this}); + + XPCOMUtils.defineLazyGetter(global, "console", getConsole); + + XPCOMUtils.defineLazyModuleGetter(global, "require", + "resource://devtools/shared/Loader.jsm"); + + return global; + } + + /** + * Load an ext-*.js script. The script runs in its own scope, if it wishes to + * share state with another script it can assign to the `global` variable. If + * it wishes to communicate with this API manager, use `extensions`. + * + * @param {string} scriptUrl The URL of the ext-*.js script. + */ + loadScript(scriptUrl) { + // Create the object in the context of the sandbox so that the script runs + // in the sandbox's context instead of here. + let scope = Cu.createObjectIn(this.global); + + Services.scriptloader.loadSubScript(scriptUrl, scope, "UTF-8"); + + // Save the scope to avoid it being garbage collected. + this._scriptScopes.push(scope); + } + + /** + * Called by an ext-*.js script to register an API. + * + * @param {string} namespace The API namespace. + * Intended to match the namespace of the generated API, but not used at + * the moment - see bugzil.la/1295774. + * @param {string} envType Restricts the API to contexts that run in the + * given environment. Must be one of the following: + * - "addon_parent" - addon APIs that runs in the main process. + * - "addon_child" - addon APIs that runs in an addon process. + * - "content_parent" - content script APIs that runs in the main process. + * - "content_child" - content script APIs that runs in a content process. + * @param {function(BaseContext)} getAPI A function that returns an object + * that will be merged with |chrome| and |browser|. The next example adds + * the create, update and remove methods to the tabs API. + * + * registerSchemaAPI("tabs", "addon_parent", (context) => ({ + * tabs: { create, update }, + * })); + * registerSchemaAPI("tabs", "addon_parent", (context) => ({ + * tabs: { remove }, + * })); + */ + registerSchemaAPI(namespace, envType, getAPI) { + this._schemaApis[envType].push({namespace, getAPI}); + } + + /** + * Exports all registered scripts to `obj`. + * + * @param {BaseContext} context The context for which the API bindings are + * generated. + * @param {object} obj The destination of the API. + */ + generateAPIs(context, obj) { + let apis = this._schemaApis[context.envType]; + if (!apis) { + Cu.reportError(`No APIs have been registered for ${context.envType}`); + return; + } + SchemaAPIManager.generateAPIs(context, apis, obj); + } + + /** + * Mash together all the APIs from `apis` into `obj`. + * + * @param {BaseContext} context The context for which the API bindings are + * generated. + * @param {Array} apis A list of objects, see `registerSchemaAPI`. + * @param {object} obj The destination of the API. + */ + static generateAPIs(context, apis, obj) { + // Recursively copy properties from source to dest. + function copy(dest, source) { + for (let prop in source) { + let desc = Object.getOwnPropertyDescriptor(source, prop); + if (typeof(desc.value) == "object") { + if (!(prop in dest)) { + dest[prop] = {}; + } + copy(dest[prop], source[prop]); + } else { + Object.defineProperty(dest, prop, desc); + } + } + } + + for (let api of apis) { + if (Schemas.checkPermissions(api.namespace, context.extension)) { + api = api.getAPI(context); + copy(obj, api); + } + } + } +} + +const ExtensionCommon = { + BaseContext, + LocalAPIImplementation, + SchemaAPIInterface, + SchemaAPIManager, +}; diff --git a/toolkit/components/webextensions/ExtensionContent.jsm b/toolkit/components/webextensions/ExtensionContent.jsm new file mode 100644 index 000000000..9b9a02091 --- /dev/null +++ b/toolkit/components/webextensions/ExtensionContent.jsm @@ -0,0 +1,1048 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["ExtensionContent"]; + +/* globals ExtensionContent */ + +/* + * This file handles the content process side of extensions. It mainly + * takes care of content script injection, content script APIs, and + * messaging. + * + * This file is also the initial entry point for addon processes. + * ExtensionChild.jsm is responsible for functionality specific to addon + * processes. + */ + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/AppConstants.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement", + "resource://gre/modules/ExtensionManagement.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector", + "resource:///modules/translation/LanguageDetector.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern", + "resource://gre/modules/MatchPattern.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MatchGlobs", + "resource://gre/modules/MatchPattern.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", + "resource://gre/modules/MessageChannel.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils", + "resource://gre/modules/PromiseUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames", + "resource://gre/modules/WebNavigationFrames.jsm"); + +Cu.import("resource://gre/modules/ExtensionChild.jsm"); +Cu.import("resource://gre/modules/ExtensionCommon.jsm"); +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +const { + EventEmitter, + LocaleData, + defineLazyGetter, + flushJarCache, + getInnerWindowID, + promiseDocumentReady, + runSafeSyncWithoutClone, +} = ExtensionUtils; + +const { + BaseContext, + SchemaAPIManager, +} = ExtensionCommon; + +const { + ChildAPIManager, + Messenger, +} = ExtensionChild; + +XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole); + +const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content"; + +function isWhenBeforeOrSame(when1, when2) { + let table = {"document_start": 0, + "document_end": 1, + "document_idle": 2}; + return table[when1] <= table[when2]; +} + +var apiManager = new class extends SchemaAPIManager { + constructor() { + super("content"); + this.initialized = false; + } + + generateAPIs(...args) { + if (!this.initialized) { + this.initialized = true; + for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS_CONTENT)) { + this.loadScript(value); + } + } + return super.generateAPIs(...args); + } + + registerSchemaAPI(namespace, envType, getAPI) { + if (envType == "content_child") { + super.registerSchemaAPI(namespace, envType, getAPI); + } + } +}(); + +// Represents a content script. +function Script(extension, options, deferred = PromiseUtils.defer()) { + this.extension = extension; + this.options = options; + this.run_at = this.options.run_at; + this.js = this.options.js || []; + this.css = this.options.css || []; + this.remove_css = this.options.remove_css; + this.match_about_blank = this.options.match_about_blank; + + this.deferred = deferred; + + this.matches_ = new MatchPattern(this.options.matches); + this.exclude_matches_ = new MatchPattern(this.options.exclude_matches || null); + // TODO: MatchPattern should pre-mangle host-only patterns so that we + // don't need to call a separate match function. + this.matches_host_ = new MatchPattern(this.options.matchesHost || null); + this.include_globs_ = new MatchGlobs(this.options.include_globs); + this.exclude_globs_ = new MatchGlobs(this.options.exclude_globs); + + this.requiresCleanup = !this.remove_css && (this.css.length > 0 || options.cssCode); +} + +Script.prototype = { + get cssURLs() { + // We can handle CSS urls (css) and CSS code (cssCode). + let urls = []; + for (let url of this.css) { + urls.push(this.extension.baseURI.resolve(url)); + } + + if (this.options.cssCode) { + let url = "data:text/css;charset=utf-8," + encodeURIComponent(this.options.cssCode); + urls.push(url); + } + + return urls; + }, + + matches(window) { + let uri = window.document.documentURIObject; + let principal = window.document.nodePrincipal; + + // If mozAddonManager is present on this page, don't allow + // content scripts. + if (window.navigator.mozAddonManager !== undefined) { + return false; + } + + if (this.match_about_blank && ["about:blank", "about:srcdoc"].includes(uri.spec)) { + // When matching about:blank/srcdoc documents, the checks below + // need to be performed against the "owner" document's URI. + uri = principal.URI; + } + + // Documents from data: URIs also inherit the principal. + if (Services.netUtils.URIChainHasFlags(uri, Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT)) { + if (!this.match_about_blank) { + return false; + } + uri = principal.URI; + } + + if (!(this.matches_.matches(uri) || this.matches_host_.matchesIgnoringPath(uri))) { + return false; + } + + if (this.exclude_matches_.matches(uri)) { + return false; + } + + if (this.options.include_globs != null) { + if (!this.include_globs_.matches(uri.spec)) { + return false; + } + } + + if (this.exclude_globs_.matches(uri.spec)) { + return false; + } + + if (this.options.frame_id != null) { + if (WebNavigationFrames.getFrameId(window) != this.options.frame_id) { + return false; + } + } else if (!this.options.all_frames && window.top != window) { + return false; + } + + return true; + }, + + cleanup(window) { + if (!this.remove_css) { + let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + for (let url of this.cssURLs) { + runSafeSyncWithoutClone(winUtils.removeSheetUsingURIString, url, winUtils.AUTHOR_SHEET); + } + } + }, + + /** + * Tries to inject this script into the given window and sandbox, if + * there are pending operations for the window's current load state. + * + * @param {Window} window + * The DOM Window to inject the scripts and CSS into. + * @param {Sandbox} sandbox + * A Sandbox inheriting from `window` in which to evaluate the + * injected scripts. + * @param {function} shouldRun + * A function which, when passed the document load state that a + * script is expected to run at, returns `true` if we should + * currently be injecting scripts for that load state. + * + * For initial injection of a script, this function should + * return true if the document is currently in or has already + * passed through the given state. For injections triggered by + * document state changes, it should only return true if the + * given state exactly matches the state that triggered the + * change. + * @param {string} when + * The document's current load state, or if triggered by a + * document state change, the new document state that triggered + * the injection. + */ + tryInject(window, sandbox, shouldRun, when) { + if (shouldRun("document_start")) { + let {cssURLs} = this; + if (cssURLs.length > 0) { + let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + let method = this.remove_css ? winUtils.removeSheetUsingURIString : winUtils.loadSheetUsingURIString; + for (let url of cssURLs) { + runSafeSyncWithoutClone(method, url, winUtils.AUTHOR_SHEET); + } + + this.deferred.resolve(); + } + } + + let result; + let scheduled = this.run_at || "document_idle"; + if (shouldRun(scheduled)) { + for (let [i, url] of this.js.entries()) { + let options = { + target: sandbox, + charset: "UTF-8", + // Inject the last script asynchronously unless we're expected to + // inject before any page scripts have run, and we haven't already + // missed that boat. + async: (i === this.js.length - 1) && + (this.run_at !== "document_start" || when !== "document_start"), + }; + try { + result = Services.scriptloader.loadSubScriptWithOptions(url, options); + } catch (e) { + Cu.reportError(e); + this.deferred.reject(e); + } + } + + if (this.options.jsCode) { + try { + result = Cu.evalInSandbox(this.options.jsCode, sandbox, "latest"); + } catch (e) { + Cu.reportError(e); + this.deferred.reject(e); + } + } + + this.deferred.resolve(result); + } + }, +}; + +function getWindowMessageManager(contentWindow) { + let ir = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor); + try { + return ir.getInterface(Ci.nsIContentFrameMessageManager); + } catch (e) { + // Some windows don't support this interface (hidden window). + return null; + } +} + +var DocumentManager; +var ExtensionManager; + +/** + * An execution context for semi-privileged extension content scripts. + * + * This is the child side of the ContentScriptContextParent class + * defined in ExtensionParent.jsm. + */ +class ContentScriptContextChild extends BaseContext { + constructor(extension, contentWindow, contextOptions = {}) { + super("content_child", extension); + + let {isExtensionPage} = contextOptions; + + this.isExtensionPage = isExtensionPage; + + this.setContentWindow(contentWindow); + + let frameId = WebNavigationFrames.getFrameId(contentWindow); + this.frameId = frameId; + + this.scripts = []; + + let contentPrincipal = contentWindow.document.nodePrincipal; + let ssm = Services.scriptSecurityManager; + + // copy origin attributes from the content window origin attributes to + // preserve the user context id. overwrite the addonId. + let attrs = contentPrincipal.originAttributes; + attrs.addonId = this.extension.id; + let extensionPrincipal = ssm.createCodebasePrincipal(this.extension.baseURI, attrs); + + let principal; + if (ssm.isSystemPrincipal(contentPrincipal)) { + // Make sure we don't hand out the system principal by accident. + // also make sure that the null principal has the right origin attributes + principal = ssm.createNullPrincipal(attrs); + } else { + principal = [contentPrincipal, extensionPrincipal]; + } + + if (isExtensionPage) { + if (ExtensionManagement.getAddonIdForWindow(this.contentWindow) != this.extension.id) { + throw new Error("Invalid target window for this extension context"); + } + // This is an iframe with content script API enabled and its principal should be the + // contentWindow itself. (we create a sandbox with the contentWindow as principal and with X-rays disabled + // because it enables us to create the APIs object in this sandbox object and then copying it + // into the iframe's window, see Bug 1214658 for rationale) + this.sandbox = Cu.Sandbox(contentWindow, { + sandboxPrototype: contentWindow, + sameZoneAs: contentWindow, + wantXrays: false, + isWebExtensionContentScript: true, + }); + } else { + // This metadata is required by the Developer Tools, in order for + // the content script to be associated with both the extension and + // the tab holding the content page. + let metadata = { + "inner-window-id": this.innerWindowID, + addonId: attrs.addonId, + }; + + this.sandbox = Cu.Sandbox(principal, { + metadata, + sandboxPrototype: contentWindow, + sameZoneAs: contentWindow, + wantXrays: true, + isWebExtensionContentScript: true, + wantExportHelpers: true, + wantGlobalProperties: ["XMLHttpRequest", "fetch"], + originAttributes: attrs, + }); + + Cu.evalInSandbox(` + window.JSON = JSON; + window.XMLHttpRequest = XMLHttpRequest; + window.fetch = fetch; + `, this.sandbox); + } + + Object.defineProperty(this, "principal", { + value: Cu.getObjectPrincipal(this.sandbox), + enumerable: true, + configurable: true, + }); + + this.url = contentWindow.location.href; + + defineLazyGetter(this, "chromeObj", () => { + let chromeObj = Cu.createObjectIn(this.sandbox); + + Schemas.inject(chromeObj, this.childManager); + return chromeObj; + }); + + Schemas.exportLazyGetter(this.sandbox, "browser", () => this.chromeObj); + Schemas.exportLazyGetter(this.sandbox, "chrome", () => this.chromeObj); + + // This is an iframe with content script API enabled (bug 1214658) + if (isExtensionPage) { + Schemas.exportLazyGetter(this.contentWindow, + "browser", () => this.chromeObj); + Schemas.exportLazyGetter(this.contentWindow, + "chrome", () => this.chromeObj); + } + } + + get cloneScope() { + return this.sandbox; + } + + execute(script, shouldRun, when) { + script.tryInject(this.contentWindow, this.sandbox, shouldRun, when); + } + + addScript(script, when) { + let state = DocumentManager.getWindowState(this.contentWindow); + this.execute(script, scheduled => isWhenBeforeOrSame(scheduled, state), when); + + // Save the script in case it has pending operations in later load + // states, but only if we're before document_idle, or require cleanup. + if (state != "document_idle" || script.requiresCleanup) { + this.scripts.push(script); + } + } + + triggerScripts(documentState) { + for (let script of this.scripts) { + this.execute(script, scheduled => scheduled == documentState, documentState); + } + if (documentState == "document_idle") { + // Don't bother saving scripts after document_idle. + this.scripts = this.scripts.filter(script => script.requiresCleanup); + } + } + + close() { + super.unload(); + + if (this.contentWindow) { + for (let script of this.scripts) { + if (script.requiresCleanup) { + script.cleanup(this.contentWindow); + } + } + + // Overwrite the content script APIs with an empty object if the APIs objects are still + // defined in the content window (bug 1214658). + if (this.isExtensionPage) { + Cu.createObjectIn(this.contentWindow, {defineAs: "browser"}); + Cu.createObjectIn(this.contentWindow, {defineAs: "chrome"}); + } + } + Cu.nukeSandbox(this.sandbox); + this.sandbox = null; + } +} + +defineLazyGetter(ContentScriptContextChild.prototype, "messenger", function() { + // The |sender| parameter is passed directly to the extension. + let sender = {id: this.extension.uuid, frameId: this.frameId, url: this.url}; + let filter = {extensionId: this.extension.id}; + let optionalFilter = {frameId: this.frameId}; + + return new Messenger(this, [this.messageManager], sender, filter, optionalFilter); +}); + +defineLazyGetter(ContentScriptContextChild.prototype, "childManager", function() { + let localApis = {}; + apiManager.generateAPIs(this, localApis); + + let childManager = new ChildAPIManager(this, this.messageManager, localApis, { + envType: "content_parent", + url: this.url, + }); + + this.callOnClose(childManager); + + return childManager; +}); + +// Responsible for creating ExtensionContexts and injecting content +// scripts into them when new documents are created. +DocumentManager = { + extensionCount: 0, + + // Map[windowId -> Map[extensionId -> ContentScriptContextChild]] + contentScriptWindows: new Map(), + + // Map[windowId -> ContentScriptContextChild] + extensionPageWindows: new Map(), + + init() { + Services.obs.addObserver(this, "content-document-global-created", false); + Services.obs.addObserver(this, "document-element-inserted", false); + Services.obs.addObserver(this, "inner-window-destroyed", false); + }, + + uninit() { + Services.obs.removeObserver(this, "content-document-global-created"); + Services.obs.removeObserver(this, "document-element-inserted"); + Services.obs.removeObserver(this, "inner-window-destroyed"); + }, + + getWindowState(contentWindow) { + let readyState = contentWindow.document.readyState; + if (readyState == "complete") { + return "document_idle"; + } + if (readyState == "interactive") { + return "document_end"; + } + return "document_start"; + }, + + loadInto(window) { + // Enable the content script APIs should be available in subframes' window + // if it is recognized as a valid addon id (see Bug 1214658 for rationale). + const { + NO_PRIVILEGES, + CONTENTSCRIPT_PRIVILEGES, + FULL_PRIVILEGES, + } = ExtensionManagement.API_LEVELS; + let extensionId = ExtensionManagement.getAddonIdForWindow(window); + let apiLevel = ExtensionManagement.getAPILevelForWindow(window, extensionId); + + if (apiLevel != NO_PRIVILEGES) { + let extension = ExtensionManager.get(extensionId); + if (extension) { + if (apiLevel == CONTENTSCRIPT_PRIVILEGES) { + DocumentManager.getExtensionPageContext(extension, window); + } else if (apiLevel == FULL_PRIVILEGES) { + ExtensionChild.createExtensionContext(extension, window); + } + } + } + }, + + observe: function(subject, topic, data) { + // For some types of documents (about:blank), we only see the first + // notification, for others (data: URIs) we only observe the second. + if (topic == "content-document-global-created" || topic == "document-element-inserted") { + let document = subject; + let window = document && document.defaultView; + + if (topic == "content-document-global-created") { + window = subject; + document = window && window.document; + } + + if (!document || !document.location || !window) { + return; + } + + // Make sure we only load into frames that ExtensionContent.init + // was called on (i.e., not frames for social or sidebars). + let mm = getWindowMessageManager(window); + if (!mm || !ExtensionContent.globals.has(mm)) { + return; + } + + // Load on document-element-inserted, except for about:blank which doesn't + // see it, and needs special late handling on DOMContentLoaded event. + if (topic === "document-element-inserted") { + this.loadInto(window); + this.trigger("document_start", window); + } + + /* eslint-disable mozilla/balanced-listeners */ + window.addEventListener("DOMContentLoaded", this, true); + window.addEventListener("load", this, true); + /* eslint-enable mozilla/balanced-listeners */ + } else if (topic == "inner-window-destroyed") { + let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + + MessageChannel.abortResponses({innerWindowID: windowId}); + + // Close any existent content-script context for the destroyed window. + if (this.contentScriptWindows.has(windowId)) { + let extensions = this.contentScriptWindows.get(windowId); + for (let [, context] of extensions) { + context.close(); + } + + this.contentScriptWindows.delete(windowId); + } + + // Close any existent iframe extension page context for the destroyed window. + if (this.extensionPageWindows.has(windowId)) { + let context = this.extensionPageWindows.get(windowId); + context.close(); + this.extensionPageWindows.delete(windowId); + } + + ExtensionChild.destroyExtensionContext(windowId); + } + }, + + handleEvent: function(event) { + let window = event.currentTarget; + if (event.target != window.document) { + // We use capturing listeners so we have precedence over content script + // listeners, but only care about events targeted to the element we're + // listening on. + return; + } + window.removeEventListener(event.type, this, true); + + // Need to check if we're still on the right page? Greasemonkey does this. + + if (event.type == "DOMContentLoaded") { + // By this time, we can be sure if this is an explicit about:blank + // document, and if it needs special late loading and fake trigger. + if (window.location.href === "about:blank") { + this.loadInto(window); + this.trigger("document_start", window); + } + this.trigger("document_end", window); + } else if (event.type == "load") { + this.trigger("document_idle", window); + } + }, + + // Used to executeScript, insertCSS and removeCSS. + executeScript(global, extensionId, options) { + let extension = ExtensionManager.get(extensionId); + + let executeInWin = (window) => { + let deferred = PromiseUtils.defer(); + let script = new Script(extension, options, deferred); + + if (script.matches(window)) { + let context = this.getContentScriptContext(extension, window); + context.addScript(script); + return deferred.promise; + } + return null; + }; + + let promises = Array.from(this.enumerateWindows(global.docShell), executeInWin) + .filter(promise => promise); + + if (!promises.length) { + let details = {}; + for (let key of ["all_frames", "frame_id", "matches_about_blank", "matchesHost"]) { + if (key in options) { + details[key] = options[key]; + } + } + + return Promise.reject({message: `No window matching ${JSON.stringify(details)}`}); + } + if (!options.all_frames && promises.length > 1) { + return Promise.reject({message: `Internal error: Script matched multiple windows`}); + } + return Promise.all(promises); + }, + + enumerateWindows: function* (docShell) { + let window = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + yield window; + + for (let i = 0; i < docShell.childCount; i++) { + let child = docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell); + yield* this.enumerateWindows(child); + } + }, + + getContentScriptGlobalsForWindow(window) { + let winId = getInnerWindowID(window); + let extensions = this.contentScriptWindows.get(winId); + + if (extensions) { + return Array.from(extensions.values(), ctx => ctx.sandbox); + } + + return []; + }, + + getContentScriptContext(extension, window) { + let winId = getInnerWindowID(window); + if (!this.contentScriptWindows.has(winId)) { + this.contentScriptWindows.set(winId, new Map()); + } + + let extensions = this.contentScriptWindows.get(winId); + if (!extensions.has(extension.id)) { + let context = new ContentScriptContextChild(extension, window); + extensions.set(extension.id, context); + } + + return extensions.get(extension.id); + }, + + getExtensionPageContext(extension, window) { + let winId = getInnerWindowID(window); + + let context = this.extensionPageWindows.get(winId); + if (!context) { + let context = new ContentScriptContextChild(extension, window, {isExtensionPage: true}); + this.extensionPageWindows.set(winId, context); + } + + return context; + }, + + startupExtension(extensionId) { + if (this.extensionCount == 0) { + this.init(); + } + this.extensionCount++; + + let extension = ExtensionManager.get(extensionId); + for (let global of ExtensionContent.globals.keys()) { + // Note that we miss windows in the bfcache here. In theory we + // could execute content scripts on a pageshow event for that + // window, but that seems extreme. + for (let window of this.enumerateWindows(global.docShell)) { + for (let script of extension.scripts) { + if (script.matches(window)) { + let context = this.getContentScriptContext(extension, window); + context.addScript(script); + } + } + } + } + }, + + shutdownExtension(extensionId) { + // Clean up content-script contexts on extension shutdown. + for (let [, extensions] of this.contentScriptWindows) { + let context = extensions.get(extensionId); + if (context) { + context.close(); + extensions.delete(extensionId); + } + } + + // Clean up iframe extension page contexts on extension shutdown. + for (let [winId, context] of this.extensionPageWindows) { + if (context.extension.id == extensionId) { + context.close(); + this.extensionPageWindows.delete(winId); + } + } + + ExtensionChild.shutdownExtension(extensionId); + + MessageChannel.abortResponses({extensionId}); + + this.extensionCount--; + if (this.extensionCount == 0) { + this.uninit(); + } + }, + + trigger(when, window) { + if (when === "document_start") { + for (let extension of ExtensionManager.extensions.values()) { + for (let script of extension.scripts) { + if (script.matches(window)) { + let context = this.getContentScriptContext(extension, window); + context.addScript(script, when); + } + } + } + } else { + let contexts = this.contentScriptWindows.get(getInnerWindowID(window)) || new Map(); + for (let context of contexts.values()) { + context.triggerScripts(when); + } + } + }, +}; + +// Represents a browser extension in the content process. +class BrowserExtensionContent extends EventEmitter { + constructor(data) { + super(); + + this.id = data.id; + this.uuid = data.uuid; + this.data = data; + this.instanceId = data.instanceId; + + this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`; + Services.cpmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this); + + this.scripts = data.content_scripts.map(scriptData => new Script(this, scriptData)); + this.webAccessibleResources = new MatchGlobs(data.webAccessibleResources); + this.whiteListedHosts = new MatchPattern(data.whiteListedHosts); + this.permissions = data.permissions; + this.principal = data.principal; + + this.localeData = new LocaleData(data.localeData); + + this.manifest = data.manifest; + this.baseURI = Services.io.newURI(data.baseURL, null, null); + + // Only used in addon processes. + this.views = new Set(); + + let uri = Services.io.newURI(data.resourceURL, null, null); + + if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { + // Extension.jsm takes care of this in the parent. + ExtensionManagement.startupExtension(this.uuid, uri, this); + } + } + + shutdown() { + Services.cpmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this); + + if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { + ExtensionManagement.shutdownExtension(this.uuid); + } + } + + emit(event, ...args) { + Services.cpmm.sendAsyncMessage(this.MESSAGE_EMIT_EVENT, {event, args}); + + super.emit(event, ...args); + } + + receiveMessage({name, data}) { + if (name === this.MESSAGE_EMIT_EVENT) { + super.emit(data.event, ...data.args); + } + } + + localizeMessage(...args) { + return this.localeData.localizeMessage(...args); + } + + localize(...args) { + return this.localeData.localize(...args); + } + + hasPermission(perm) { + let match = /^manifest:(.*)/.exec(perm); + if (match) { + return this.manifest[match[1]] != null; + } + return this.permissions.has(perm); + } +} + +ExtensionManager = { + // Map[extensionId, BrowserExtensionContent] + extensions: new Map(), + + init() { + Schemas.init(); + ExtensionChild.initOnce(); + + Services.cpmm.addMessageListener("Extension:Startup", this); + Services.cpmm.addMessageListener("Extension:Shutdown", this); + Services.cpmm.addMessageListener("Extension:FlushJarCache", this); + + if (Services.cpmm.initialProcessData && "Extension:Extensions" in Services.cpmm.initialProcessData) { + let extensions = Services.cpmm.initialProcessData["Extension:Extensions"]; + for (let data of extensions) { + this.extensions.set(data.id, new BrowserExtensionContent(data)); + DocumentManager.startupExtension(data.id); + } + } + }, + + get(extensionId) { + return this.extensions.get(extensionId); + }, + + receiveMessage({name, data}) { + let extension; + switch (name) { + case "Extension:Startup": { + extension = new BrowserExtensionContent(data); + + this.extensions.set(data.id, extension); + + DocumentManager.startupExtension(data.id); + + Services.cpmm.sendAsyncMessage("Extension:StartupComplete"); + break; + } + + case "Extension:Shutdown": { + extension = this.extensions.get(data.id); + extension.shutdown(); + + DocumentManager.shutdownExtension(data.id); + + this.extensions.delete(data.id); + break; + } + + case "Extension:FlushJarCache": { + let nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile", + "initWithPath"); + let file = new nsIFile(data.path); + flushJarCache(file); + Services.cpmm.sendAsyncMessage("Extension:FlushJarCacheComplete"); + break; + } + } + }, +}; + +class ExtensionGlobal { + constructor(global) { + this.global = global; + + MessageChannel.addListener(global, "Extension:Capture", this); + MessageChannel.addListener(global, "Extension:DetectLanguage", this); + MessageChannel.addListener(global, "Extension:Execute", this); + MessageChannel.addListener(global, "WebNavigation:GetFrame", this); + MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this); + + this.windowId = global.content + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .outerWindowID; + + global.sendAsyncMessage("Extension:TopWindowID", {windowId: this.windowId}); + } + + uninit() { + this.global.sendAsyncMessage("Extension:RemoveTopWindowID", {windowId: this.windowId}); + } + + get messageFilterStrict() { + return { + innerWindowID: getInnerWindowID(this.global.content), + }; + } + + receiveMessage({target, messageName, recipient, data}) { + switch (messageName) { + case "Extension:Capture": + return this.handleExtensionCapture(data.width, data.height, data.options); + case "Extension:DetectLanguage": + return this.handleDetectLanguage(target); + case "Extension:Execute": + return this.handleExtensionExecute(target, recipient.extensionId, data.options); + case "WebNavigation:GetFrame": + return this.handleWebNavigationGetFrame(data.options); + case "WebNavigation:GetAllFrames": + return this.handleWebNavigationGetAllFrames(); + } + } + + handleExtensionCapture(width, height, options) { + let win = this.global.content; + + const XHTML_NS = "http://www.w3.org/1999/xhtml"; + let canvas = win.document.createElementNS(XHTML_NS, "canvas"); + canvas.width = width; + canvas.height = height; + canvas.mozOpaque = true; + + let ctx = canvas.getContext("2d"); + + // We need to scale the image to the visible size of the browser, + // in order for the result to appear as the user sees it when + // settings like full zoom come into play. + ctx.scale(canvas.width / win.innerWidth, canvas.height / win.innerHeight); + + ctx.drawWindow(win, win.scrollX, win.scrollY, win.innerWidth, win.innerHeight, "#fff"); + + return canvas.toDataURL(`image/${options.format}`, options.quality / 100); + } + + handleDetectLanguage(target) { + let doc = target.content.document; + + return promiseDocumentReady(doc).then(() => { + let elem = doc.documentElement; + + let language = (elem.getAttribute("xml:lang") || elem.getAttribute("lang") || + doc.contentLanguage || null); + + // We only want the last element of the TLD here. + // Only country codes have any effect on the results, but other + // values cause no harm. + let tld = doc.location.hostname.match(/[a-z]*$/)[0]; + + // The CLD2 library used by the language detector is capable of + // analyzing raw HTML. Unfortunately, that takes much more memory, + // and since it's hosted by emscripten, and therefore can't shrink + // its heap after it's grown, it has a performance cost. + // So we send plain text instead. + let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"].createInstance(Ci.nsIDocumentEncoder); + encoder.init(doc, "text/plain", encoder.SkipInvisibleContent); + let text = encoder.encodeToStringWithMaxLength(60 * 1024); + + let encoding = doc.characterSet; + + return LanguageDetector.detectLanguage({language, tld, text, encoding}) + .then(result => result.language === "un" ? "und" : result.language); + }); + } + + // Used to executeScript, insertCSS and removeCSS. + handleExtensionExecute(target, extensionId, options) { + return DocumentManager.executeScript(target, extensionId, options).then(result => { + try { + // Make sure we can structured-clone the result value before + // we try to send it back over the message manager. + Cu.cloneInto(result, target); + } catch (e) { + return Promise.reject({message: "Script returned non-structured-clonable data"}); + } + return result; + }); + } + + handleWebNavigationGetFrame({frameId}) { + return WebNavigationFrames.getFrame(this.global.docShell, frameId); + } + + handleWebNavigationGetAllFrames() { + return WebNavigationFrames.getAllFrames(this.global.docShell); + } +} + +this.ExtensionContent = { + globals: new Map(), + + init(global) { + this.globals.set(global, new ExtensionGlobal(global)); + ExtensionChild.init(global); + }, + + uninit(global) { + ExtensionChild.uninit(global); + this.globals.get(global).uninit(); + this.globals.delete(global); + }, + + // This helper is exported to be integrated in the devtools RDP actors, + // that can use it to retrieve the existent WebExtensions ContentScripts + // of a target window and be able to show the ContentScripts source in the + // DevTools Debugger panel. + getContentScriptGlobalsForWindow(window) { + return DocumentManager.getContentScriptGlobalsForWindow(window); + }, +}; + +ExtensionManager.init(); diff --git a/toolkit/components/webextensions/ExtensionManagement.jsm b/toolkit/components/webextensions/ExtensionManagement.jsm new file mode 100644 index 000000000..324c5b71b --- /dev/null +++ b/toolkit/components/webextensions/ExtensionManagement.jsm @@ -0,0 +1,321 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["ExtensionManagement"]; + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/AppConstants.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils", + "resource://gre/modules/ExtensionUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionUtils.getConsole()); + +XPCOMUtils.defineLazyGetter(this, "UUIDMap", () => { + let {UUIDMap} = Cu.import("resource://gre/modules/Extension.jsm", {}); + return UUIDMap; +}); + +/* + * This file should be kept short and simple since it's loaded even + * when no extensions are running. + */ + +// Keep track of frame IDs for content windows. Mostly we can just use +// the outer window ID as the frame ID. However, the API specifies +// that top-level windows have a frame ID of 0. So we need to keep +// track of which windows are top-level. This code listens to messages +// from ExtensionContent to do that. +var Frames = { + // Window IDs of top-level content windows. + topWindowIds: new Set(), + + init() { + if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { + return; + } + + Services.mm.addMessageListener("Extension:TopWindowID", this); + Services.mm.addMessageListener("Extension:RemoveTopWindowID", this, true); + }, + + isTopWindowId(windowId) { + return this.topWindowIds.has(windowId); + }, + + // Convert an outer window ID to a frame ID. An outer window ID of 0 + // is invalid. + getId(windowId) { + if (this.isTopWindowId(windowId)) { + return 0; + } + if (windowId == 0) { + return -1; + } + return windowId; + }, + + // Convert an outer window ID for a parent window to a frame + // ID. Outer window IDs follow the same convention that + // |window.top.parent === window.top|. The API works differently, + // giving a frame ID of -1 for the the parent of a top-level + // window. This function handles the conversion. + getParentId(parentWindowId, windowId) { + if (parentWindowId == windowId) { + // We have a top-level window. + return -1; + } + + // Not a top-level window. Just return the ID as normal. + return this.getId(parentWindowId); + }, + + receiveMessage({name, data}) { + switch (name) { + case "Extension:TopWindowID": + // FIXME: Need to handle the case where the content process + // crashes. Right now we leak its top window IDs. + this.topWindowIds.add(data.windowId); + break; + + case "Extension:RemoveTopWindowID": + this.topWindowIds.delete(data.windowId); + break; + } + }, +}; +Frames.init(); + +var APIs = { + apis: new Map(), + + register(namespace, schema, script) { + if (this.apis.has(namespace)) { + throw new Error(`API namespace already exists: ${namespace}`); + } + + this.apis.set(namespace, {schema, script}); + }, + + unregister(namespace) { + if (!this.apis.has(namespace)) { + throw new Error(`API namespace does not exist: ${namespace}`); + } + + this.apis.delete(namespace); + }, +}; + +function getURLForExtension(id, path = "") { + let uuid = UUIDMap.get(id, false); + if (!uuid) { + Cu.reportError(`Called getURLForExtension on unmapped extension ${id}`); + return null; + } + return `moz-extension://${uuid}/${path}`; +} + +// This object manages various platform-level issues related to +// moz-extension:// URIs. It lives here so that it can be used in both +// the parent and child processes. +// +// moz-extension URIs have the form moz-extension://uuid/path. Each +// extension has its own UUID, unique to the machine it's installed +// on. This is easier and more secure than using the extension ID, +// since it makes it slightly harder to fingerprint for extensions if +// each user uses different URIs for the extension. +var Service = { + initialized: false, + + // Map[uuid -> extension]. + // extension can be an Extension (parent process) or BrowserExtensionContent (child process). + uuidMap: new Map(), + + init() { + let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService); + aps = aps.wrappedJSObject; + this.aps = aps; + aps.setExtensionURILoadCallback(this.extensionURILoadableByAnyone.bind(this)); + aps.setExtensionURIToAddonIdCallback(this.extensionURIToAddonID.bind(this)); + }, + + // Called when a new extension is loaded. + startupExtension(uuid, uri, extension) { + if (!this.initialized) { + this.initialized = true; + this.init(); + } + + // Create the moz-extension://uuid mapping. + let handler = Services.io.getProtocolHandler("moz-extension"); + handler.QueryInterface(Ci.nsISubstitutingProtocolHandler); + handler.setSubstitution(uuid, uri); + + this.uuidMap.set(uuid, extension); + this.aps.setAddonHasPermissionCallback(extension.id, extension.hasPermission.bind(extension)); + this.aps.setAddonLoadURICallback(extension.id, this.checkAddonMayLoad.bind(this, extension)); + this.aps.setAddonLocalizeCallback(extension.id, extension.localize.bind(extension)); + this.aps.setAddonCSP(extension.id, extension.manifest.content_security_policy); + this.aps.setBackgroundPageUrlCallback(uuid, this.generateBackgroundPageUrl.bind(this, extension)); + }, + + // Called when an extension is unloaded. + shutdownExtension(uuid) { + let extension = this.uuidMap.get(uuid); + this.uuidMap.delete(uuid); + this.aps.setAddonHasPermissionCallback(extension.id, null); + this.aps.setAddonLoadURICallback(extension.id, null); + this.aps.setAddonLocalizeCallback(extension.id, null); + this.aps.setAddonCSP(extension.id, null); + this.aps.setBackgroundPageUrlCallback(uuid, null); + + let handler = Services.io.getProtocolHandler("moz-extension"); + handler.QueryInterface(Ci.nsISubstitutingProtocolHandler); + handler.setSubstitution(uuid, null); + }, + + // Return true if the given URI can be loaded from arbitrary web + // content. The manifest.json |web_accessible_resources| directive + // determines this. + extensionURILoadableByAnyone(uri) { + let uuid = uri.host; + let extension = this.uuidMap.get(uuid); + if (!extension || !extension.webAccessibleResources) { + return false; + } + + let path = uri.QueryInterface(Ci.nsIURL).filePath; + if (path.length > 0 && path[0] == "/") { + path = path.substr(1); + } + return extension.webAccessibleResources.matches(path); + }, + + // Checks whether a given extension can load this URI (typically via + // an XML HTTP request). The manifest.json |permissions| directive + // determines this. + checkAddonMayLoad(extension, uri) { + return extension.whiteListedHosts.matchesIgnoringPath(uri); + }, + + generateBackgroundPageUrl(extension) { + let background_scripts = extension.manifest.background && + extension.manifest.background.scripts; + if (!background_scripts) { + return; + } + let html = "\n\n"; + for (let script of background_scripts) { + script = script.replace(/"/g, """); + html += `\n`; + } + html += "\n\n"; + return "data:text/html;charset=utf-8," + encodeURIComponent(html); + }, + + // Finds the add-on ID associated with a given moz-extension:// URI. + // This is used to set the addonId on the originAttributes for the + // nsIPrincipal attached to the URI. + extensionURIToAddonID(uri) { + let uuid = uri.host; + let extension = this.uuidMap.get(uuid); + return extension ? extension.id : undefined; + }, +}; + +// API Levels Helpers + +// Find the add-on associated with this document via the +// principal's originAttributes. This value is computed by +// extensionURIToAddonID, which ensures that we don't inject our +// API into webAccessibleResources or remote web pages. +function getAddonIdForWindow(window) { + return Cu.getObjectPrincipal(window).originAttributes.addonId; +} + +const API_LEVELS = Object.freeze({ + NO_PRIVILEGES: 0, + CONTENTSCRIPT_PRIVILEGES: 1, + FULL_PRIVILEGES: 2, +}); + +// Finds the API Level ("FULL_PRIVILEGES", "CONTENTSCRIPT_PRIVILEGES", "NO_PRIVILEGES") +// with a given a window object. +function getAPILevelForWindow(window, addonId) { + const {NO_PRIVILEGES, CONTENTSCRIPT_PRIVILEGES, FULL_PRIVILEGES} = API_LEVELS; + + // Non WebExtension URLs and WebExtension URLs from a different extension + // has no access to APIs. + if (!addonId || getAddonIdForWindow(window) != addonId) { + return NO_PRIVILEGES; + } + + // Extension pages running in the content process always defaults to + // "content script API level privileges". + if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { + return CONTENTSCRIPT_PRIVILEGES; + } + + let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell); + + // Handling of ExtensionPages running inside sub-frames. + if (docShell.sameTypeParent) { + let parentWindow = docShell.sameTypeParent.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + + // The option page iframe embedded in the about:addons tab should have + // full API level privileges. (see Bug 1256282 for rationale) + let parentDocument = parentWindow.document; + let parentIsSystemPrincipal = Services.scriptSecurityManager + .isSystemPrincipal(parentDocument.nodePrincipal); + if (parentDocument.location.href == "about:addons" && parentIsSystemPrincipal) { + return FULL_PRIVILEGES; + } + + // The addon iframes embedded in a addon page from with the same addonId + // should have the same privileges of the sameTypeParent. + // (see Bug 1258347 for rationale) + let parentSameAddonPrivileges = getAPILevelForWindow(parentWindow, addonId); + if (parentSameAddonPrivileges > NO_PRIVILEGES) { + return parentSameAddonPrivileges; + } + + // In all the other cases, WebExtension URLs loaded into sub-frame UI + // will have "content script API level privileges". + // (see Bug 1214658 for rationale) + return CONTENTSCRIPT_PRIVILEGES; + } + + // WebExtension URLs loaded into top frames UI could have full API level privileges. + return FULL_PRIVILEGES; +} + +this.ExtensionManagement = { + startupExtension: Service.startupExtension.bind(Service), + shutdownExtension: Service.shutdownExtension.bind(Service), + + registerAPI: APIs.register.bind(APIs), + unregisterAPI: APIs.unregister.bind(APIs), + + getFrameId: Frames.getId.bind(Frames), + getParentFrameId: Frames.getParentId.bind(Frames), + + getURLForExtension, + + // exported API Level Helpers + getAddonIdForWindow, + getAPILevelForWindow, + API_LEVELS, + + APIs, +}; diff --git a/toolkit/components/webextensions/ExtensionParent.jsm b/toolkit/components/webextensions/ExtensionParent.jsm new file mode 100644 index 000000000..b88500d1e --- /dev/null +++ b/toolkit/components/webextensions/ExtensionParent.jsm @@ -0,0 +1,551 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 contains code for managing APIs that need to run in the + * parent process, and handles the parent side of operations that need + * to be proxied from ExtensionChild.jsm. + */ + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +/* exported ExtensionParent */ + +this.EXPORTED_SYMBOLS = ["ExtensionParent"]; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", + "resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", + "resource://gre/modules/MessageChannel.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NativeApp", + "resource://gre/modules/NativeMessaging.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); + +Cu.import("resource://gre/modules/ExtensionCommon.jsm"); +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +var { + BaseContext, + SchemaAPIManager, +} = ExtensionCommon; + +var { + MessageManagerProxy, + SpreadArgs, + defineLazyGetter, + findPathInObject, +} = ExtensionUtils; + +const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json"; +const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas"; +const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts"; + +let schemaURLs = new Set(); + +if (!AppConstants.RELEASE_OR_BETA) { + schemaURLs.add("chrome://extensions/content/schemas/experiments.json"); +} + +let GlobalManager; +let ParentAPIManager; +let ProxyMessenger; + +// This object loads the ext-*.js scripts that define the extension API. +let apiManager = new class extends SchemaAPIManager { + constructor() { + super("main"); + this.initialized = null; + } + + // Loads all the ext-*.js scripts currently registered. + lazyInit() { + if (this.initialized) { + return this.initialized; + } + + // Load order matters here. The base manifest defines types which are + // extended by other schemas, so needs to be loaded first. + let promise = Schemas.load(BASE_SCHEMA).then(() => { + let promises = []; + for (let [/* name */, url] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCHEMAS)) { + promises.push(Schemas.load(url)); + } + for (let url of schemaURLs) { + promises.push(Schemas.load(url)); + } + return Promise.all(promises); + }); + + for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS)) { + this.loadScript(value); + } + + this.initialized = promise; + return this.initialized; + } + + registerSchemaAPI(namespace, envType, getAPI) { + if (envType == "addon_parent" || envType == "content_parent") { + super.registerSchemaAPI(namespace, envType, getAPI); + } + } +}(); + +// Subscribes to messages related to the extension messaging API and forwards it +// to the relevant message manager. The "sender" field for the `onMessage` and +// `onConnect` events are updated if needed. +ProxyMessenger = { + _initialized: false, + init() { + if (this._initialized) { + return; + } + this._initialized = true; + + // TODO(robwu): When addons move to a separate process, we should use the + // parent process manager(s) of the addon process(es) instead of the + // in-process one. + let pipmm = Services.ppmm.getChildAt(0); + // Listen on the global frame message manager because content scripts send + // and receive extension messages via their frame. + // Listen on the parent process message manager because `runtime.connect` + // and `runtime.sendMessage` requests must be delivered to all frames in an + // addon process (by the API contract). + // And legacy addons are not associated with a frame, so that is another + // reason for having a parent process manager here. + let messageManagers = [Services.mm, pipmm]; + + MessageChannel.addListener(messageManagers, "Extension:Connect", this); + MessageChannel.addListener(messageManagers, "Extension:Message", this); + MessageChannel.addListener(messageManagers, "Extension:Port:Disconnect", this); + MessageChannel.addListener(messageManagers, "Extension:Port:PostMessage", this); + }, + + receiveMessage({target, messageName, channelId, sender, recipient, data, responseType}) { + if (recipient.toNativeApp) { + let {childId, toNativeApp} = recipient; + if (messageName == "Extension:Message") { + let context = ParentAPIManager.getContextById(childId); + return new NativeApp(context, toNativeApp).sendMessage(data); + } + if (messageName == "Extension:Connect") { + let context = ParentAPIManager.getContextById(childId); + NativeApp.onConnectNative(context, target.messageManager, data.portId, sender, toNativeApp); + return true; + } + // "Extension:Port:Disconnect" and "Extension:Port:PostMessage" for + // native messages are handled by NativeApp. + return; + } + let extension = GlobalManager.extensionMap.get(sender.extensionId); + let receiverMM = this._getMessageManagerForRecipient(recipient); + if (!extension || !receiverMM) { + return Promise.reject({ + result: MessageChannel.RESULT_NO_HANDLER, + message: "No matching message handler for the given recipient.", + }); + } + + if ((messageName == "Extension:Message" || + messageName == "Extension:Connect") && + apiManager.global.tabGetSender) { + // From ext-tabs.js, undefined on Android. + apiManager.global.tabGetSender(extension, target, sender); + } + return MessageChannel.sendMessage(receiverMM, messageName, data, { + sender, + recipient, + responseType, + }); + }, + + /** + * @param {object} recipient An object that was passed to + * `MessageChannel.sendMessage`. + * @returns {object|null} The message manager matching the recipient if found. + */ + _getMessageManagerForRecipient(recipient) { + let {extensionId, tabId} = recipient; + // tabs.sendMessage / tabs.connect + if (tabId) { + // `tabId` being set implies that the tabs API is supported, so we don't + // need to check whether `TabManager` exists. + let tab = apiManager.global.TabManager.getTab(tabId, null, null); + return tab && tab.linkedBrowser.messageManager; + } + + // runtime.sendMessage / runtime.connect + if (extensionId) { + // TODO(robwu): map the extensionId to the addon parent process's message + // manager when they run in a separate process. + return Services.ppmm.getChildAt(0); + } + + return null; + }, +}; + +// Responsible for loading extension APIs into the right globals. +GlobalManager = { + // Map[extension ID -> Extension]. Determines which extension is + // responsible for content under a particular extension ID. + extensionMap: new Map(), + initialized: false, + + init(extension) { + if (this.extensionMap.size == 0) { + ProxyMessenger.init(); + apiManager.on("extension-browser-inserted", this._onExtensionBrowser); + this.initialized = true; + } + + this.extensionMap.set(extension.id, extension); + }, + + uninit(extension) { + this.extensionMap.delete(extension.id); + + if (this.extensionMap.size == 0 && this.initialized) { + apiManager.off("extension-browser-inserted", this._onExtensionBrowser); + this.initialized = false; + } + }, + + _onExtensionBrowser(type, browser) { + browser.messageManager.loadFrameScript(`data:, + Components.utils.import("resource://gre/modules/ExtensionContent.jsm"); + ExtensionContent.init(this); + addEventListener("unload", function() { + ExtensionContent.uninit(this); + }); + `, false); + }, + + getExtension(extensionId) { + return this.extensionMap.get(extensionId); + }, + + injectInObject(context, isChromeCompat, dest) { + apiManager.generateAPIs(context, dest); + SchemaAPIManager.generateAPIs(context, context.extension.apis, dest); + }, +}; + +/** + * The proxied parent side of a context in ExtensionChild.jsm, for the + * parent side of a proxied API. + */ +class ProxyContextParent extends BaseContext { + constructor(envType, extension, params, xulBrowser, principal) { + super(envType, extension); + + this.uri = NetUtil.newURI(params.url); + + this.incognito = params.incognito; + + // This message manager is used by ParentAPIManager to send messages and to + // close the ProxyContext if the underlying message manager closes. This + // message manager object may change when `xulBrowser` swaps docshells, e.g. + // when a tab is moved to a different window. + this.messageManagerProxy = new MessageManagerProxy(xulBrowser); + + Object.defineProperty(this, "principal", { + value: principal, enumerable: true, configurable: true, + }); + + this.listenerProxies = new Map(); + + apiManager.emit("proxy-context-load", this); + } + + get cloneScope() { + return this.sandbox; + } + + get xulBrowser() { + return this.messageManagerProxy.eventTarget; + } + + get parentMessageManager() { + return this.messageManagerProxy.messageManager; + } + + shutdown() { + this.unload(); + } + + unload() { + if (this.unloaded) { + return; + } + this.messageManagerProxy.dispose(); + super.unload(); + apiManager.emit("proxy-context-unload", this); + } +} + +defineLazyGetter(ProxyContextParent.prototype, "apiObj", function() { + let obj = {}; + GlobalManager.injectInObject(this, false, obj); + return obj; +}); + +defineLazyGetter(ProxyContextParent.prototype, "sandbox", function() { + return Cu.Sandbox(this.principal); +}); + +/** + * The parent side of proxied API context for extension content script + * running in ExtensionContent.jsm. + */ +class ContentScriptContextParent extends ProxyContextParent { +} + +/** + * The parent side of proxied API context for extension page, such as a + * background script, a tab page, or a popup, running in + * ExtensionChild.jsm. + */ +class ExtensionPageContextParent extends ProxyContextParent { + constructor(envType, extension, params, xulBrowser) { + super(envType, extension, params, xulBrowser, extension.principal); + + this.viewType = params.viewType; + } + + // The window that contains this context. This may change due to moving tabs. + get xulWindow() { + return this.xulBrowser.ownerGlobal; + } + + get windowId() { + if (!apiManager.global.WindowManager || this.viewType == "background") { + return; + } + // viewType popup or tab: + return apiManager.global.WindowManager.getId(this.xulWindow); + } + + get tabId() { + if (!apiManager.global.TabManager) { + return; // Not yet supported on Android. + } + let {gBrowser} = this.xulBrowser.ownerGlobal; + let tab = gBrowser && gBrowser.getTabForBrowser(this.xulBrowser); + return tab && apiManager.global.TabManager.getId(tab); + } + + onBrowserChange(browser) { + super.onBrowserChange(browser); + this.xulBrowser = browser; + } + + shutdown() { + apiManager.emit("page-shutdown", this); + super.shutdown(); + } +} + +ParentAPIManager = { + proxyContexts: new Map(), + + init() { + Services.obs.addObserver(this, "message-manager-close", false); + + Services.mm.addMessageListener("API:CreateProxyContext", this); + Services.mm.addMessageListener("API:CloseProxyContext", this, true); + Services.mm.addMessageListener("API:Call", this); + Services.mm.addMessageListener("API:AddListener", this); + Services.mm.addMessageListener("API:RemoveListener", this); + }, + + observe(subject, topic, data) { + if (topic === "message-manager-close") { + let mm = subject; + for (let [childId, context] of this.proxyContexts) { + if (context.parentMessageManager === mm) { + this.closeProxyContext(childId); + } + } + } + }, + + shutdownExtension(extensionId) { + for (let [childId, context] of this.proxyContexts) { + if (context.extension.id == extensionId) { + context.shutdown(); + this.proxyContexts.delete(childId); + } + } + }, + + receiveMessage({name, data, target}) { + switch (name) { + case "API:CreateProxyContext": + this.createProxyContext(data, target); + break; + + case "API:CloseProxyContext": + this.closeProxyContext(data.childId); + break; + + case "API:Call": + this.call(data, target); + break; + + case "API:AddListener": + this.addListener(data, target); + break; + + case "API:RemoveListener": + this.removeListener(data); + break; + } + }, + + createProxyContext(data, target) { + let {envType, extensionId, childId, principal} = data; + if (this.proxyContexts.has(childId)) { + throw new Error("A WebExtension context with the given ID already exists!"); + } + + let extension = GlobalManager.getExtension(extensionId); + if (!extension) { + throw new Error(`No WebExtension found with ID ${extensionId}`); + } + + let context; + if (envType == "addon_parent") { + // Privileged addon contexts can only be loaded in documents whose main + // frame is also the same addon. + if (principal.URI.prePath !== extension.baseURI.prePath || + !target.contentPrincipal.subsumes(principal)) { + throw new Error(`Refused to create privileged WebExtension context for ${principal.URI.spec}`); + } + context = new ExtensionPageContextParent(envType, extension, data, target); + } else if (envType == "content_parent") { + context = new ContentScriptContextParent(envType, extension, data, target, principal); + } else { + throw new Error(`Invalid WebExtension context envType: ${envType}`); + } + this.proxyContexts.set(childId, context); + }, + + closeProxyContext(childId) { + let context = this.proxyContexts.get(childId); + if (context) { + context.unload(); + this.proxyContexts.delete(childId); + } + }, + + call(data, target) { + let context = this.getContextById(data.childId); + if (context.parentMessageManager !== target.messageManager) { + throw new Error("Got message on unexpected message manager"); + } + + let reply = result => { + if (!context.parentMessageManager) { + Cu.reportError("Cannot send function call result: other side closed connection"); + return; + } + + context.parentMessageManager.sendAsyncMessage( + "API:CallResult", + Object.assign({ + childId: data.childId, + callId: data.callId, + }, result)); + }; + + try { + let args = Cu.cloneInto(data.args, context.sandbox); + let result = findPathInObject(context.apiObj, data.path)(...args); + + if (data.callId) { + result = result || Promise.resolve(); + + result.then(result => { + result = result instanceof SpreadArgs ? [...result] : [result]; + + reply({result}); + }, error => { + error = context.normalizeError(error); + reply({error: {message: error.message}}); + }); + } + } catch (e) { + if (data.callId) { + let error = context.normalizeError(e); + reply({error: {message: error.message}}); + } else { + Cu.reportError(e); + } + } + }, + + addListener(data, target) { + let context = this.getContextById(data.childId); + if (context.parentMessageManager !== target.messageManager) { + throw new Error("Got message on unexpected message manager"); + } + + let {childId} = data; + + function listener(...listenerArgs) { + return context.sendMessage( + context.parentMessageManager, + "API:RunListener", + { + childId, + listenerId: data.listenerId, + path: data.path, + args: listenerArgs, + }, + { + recipient: {childId}, + }); + } + + context.listenerProxies.set(data.listenerId, listener); + + let args = Cu.cloneInto(data.args, context.sandbox); + findPathInObject(context.apiObj, data.path).addListener(listener, ...args); + }, + + removeListener(data) { + let context = this.getContextById(data.childId); + let listener = context.listenerProxies.get(data.listenerId); + findPathInObject(context.apiObj, data.path).removeListener(listener); + }, + + getContextById(childId) { + let context = this.proxyContexts.get(childId); + if (!context) { + let error = new Error("WebExtension context not found!"); + Cu.reportError(error); + throw error; + } + return context; + }, +}; + +ParentAPIManager.init(); + + +const ExtensionParent = { + GlobalManager, + ParentAPIManager, + apiManager, +}; diff --git a/toolkit/components/webextensions/ExtensionStorage.jsm b/toolkit/components/webextensions/ExtensionStorage.jsm new file mode 100644 index 000000000..0b0ffb000 --- /dev/null +++ b/toolkit/components/webextensions/ExtensionStorage.jsm @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["ExtensionStorage"]; + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown", + "resource://gre/modules/AsyncShutdown.jsm"); + +function jsonReplacer(key, value) { + switch (typeof(value)) { + // Serialize primitive types as-is. + case "string": + case "number": + case "boolean": + return value; + + case "object": + if (value === null) { + return value; + } + + switch (Cu.getClassName(value, true)) { + // Serialize arrays and ordinary objects as-is. + case "Array": + case "Object": + return value; + + // Serialize Date objects and regular expressions as their + // string representations. + case "Date": + case "RegExp": + return String(value); + } + break; + } + + if (!key) { + // If this is the root object, and we can't serialize it, serialize + // the value to an empty object. + return {}; + } + + // Everything else, omit entirely. + return undefined; +} + +this.ExtensionStorage = { + cache: new Map(), + listeners: new Map(), + + /** + * Sanitizes the given value, and returns a JSON-compatible + * representation of it, based on the privileges of the given global. + * + * @param {value} value + * The value to sanitize. + * @param {Context} context + * The extension context in which to sanitize the value + * @returns {value} + * The sanitized value. + */ + sanitize(value, context) { + let json = context.jsonStringify(value, jsonReplacer); + return JSON.parse(json); + }, + + getExtensionDir(extensionId) { + return OS.Path.join(this.extensionDir, extensionId); + }, + + getStorageFile(extensionId) { + return OS.Path.join(this.extensionDir, extensionId, "storage.js"); + }, + + read(extensionId) { + if (this.cache.has(extensionId)) { + return this.cache.get(extensionId); + } + + let path = this.getStorageFile(extensionId); + let decoder = new TextDecoder(); + let promise = OS.File.read(path); + promise = promise.then(array => { + return JSON.parse(decoder.decode(array)); + }).catch((error) => { + if (!error.becauseNoSuchFile) { + Cu.reportError("Unable to parse JSON data for extension storage."); + } + return {}; + }); + this.cache.set(extensionId, promise); + return promise; + }, + + write(extensionId) { + let promise = this.read(extensionId).then(extData => { + let encoder = new TextEncoder(); + let array = encoder.encode(JSON.stringify(extData)); + let path = this.getStorageFile(extensionId); + OS.File.makeDir(this.getExtensionDir(extensionId), { + ignoreExisting: true, + from: OS.Constants.Path.profileDir, + }); + let promise = OS.File.writeAtomic(path, array); + return promise; + }).catch(() => { + // Make sure this promise is never rejected. + Cu.reportError("Unable to write JSON data for extension storage."); + }); + + AsyncShutdown.profileBeforeChange.addBlocker( + "ExtensionStorage: Finish writing extension data", + promise); + + return promise.then(() => { + AsyncShutdown.profileBeforeChange.removeBlocker(promise); + }); + }, + + set(extensionId, items, context) { + return this.read(extensionId).then(extData => { + let changes = {}; + for (let prop in items) { + let item = this.sanitize(items[prop], context); + changes[prop] = {oldValue: extData[prop], newValue: item}; + extData[prop] = item; + } + + this.notifyListeners(extensionId, changes); + + return this.write(extensionId); + }); + }, + + remove(extensionId, items) { + return this.read(extensionId).then(extData => { + let changes = {}; + for (let prop of [].concat(items)) { + changes[prop] = {oldValue: extData[prop]}; + delete extData[prop]; + } + + this.notifyListeners(extensionId, changes); + + return this.write(extensionId); + }); + }, + + clear(extensionId) { + return this.read(extensionId).then(extData => { + let changes = {}; + for (let prop of Object.keys(extData)) { + changes[prop] = {oldValue: extData[prop]}; + delete extData[prop]; + } + + this.notifyListeners(extensionId, changes); + + return this.write(extensionId); + }); + }, + + get(extensionId, keys) { + return this.read(extensionId).then(extData => { + let result = {}; + if (keys === null) { + Object.assign(result, extData); + } else if (typeof(keys) == "object" && !Array.isArray(keys)) { + for (let prop in keys) { + if (prop in extData) { + result[prop] = extData[prop]; + } else { + result[prop] = keys[prop]; + } + } + } else { + for (let prop of [].concat(keys)) { + if (prop in extData) { + result[prop] = extData[prop]; + } + } + } + + return result; + }); + }, + + addOnChangedListener(extensionId, listener) { + let listeners = this.listeners.get(extensionId) || new Set(); + listeners.add(listener); + this.listeners.set(extensionId, listeners); + }, + + removeOnChangedListener(extensionId, listener) { + let listeners = this.listeners.get(extensionId); + listeners.delete(listener); + }, + + notifyListeners(extensionId, changes) { + let listeners = this.listeners.get(extensionId); + if (listeners) { + for (let listener of listeners) { + listener(changes); + } + } + }, + + init() { + if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) { + return; + } + Services.obs.addObserver(this, "extension-invalidate-storage-cache", false); + Services.obs.addObserver(this, "xpcom-shutdown", false); + }, + + observe(subject, topic, data) { + if (topic == "xpcom-shutdown") { + Services.obs.removeObserver(this, "extension-invalidate-storage-cache"); + Services.obs.removeObserver(this, "xpcom-shutdown"); + } else if (topic == "extension-invalidate-storage-cache") { + this.cache.clear(); + } + }, +}; + +XPCOMUtils.defineLazyGetter(ExtensionStorage, "extensionDir", + () => OS.Path.join(OS.Constants.Path.profileDir, "browser-extension-data")); + +ExtensionStorage.init(); diff --git a/toolkit/components/webextensions/ExtensionTestCommon.jsm b/toolkit/components/webextensions/ExtensionTestCommon.jsm new file mode 100644 index 000000000..02453ddfd --- /dev/null +++ b/toolkit/components/webextensions/ExtensionTestCommon.jsm @@ -0,0 +1,343 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 contains extension testing helper logic which is common + * between all test suites. + */ + +/* exported ExtensionTestCommon, MockExtension */ + +this.EXPORTED_SYMBOLS = ["ExtensionTestCommon", "MockExtension"]; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.importGlobalProperties(["TextEncoder"]); + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", + "resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Extension", + "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent", + "resource://gre/modules/ExtensionParent.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); + +XPCOMUtils.defineLazyGetter(this, "apiManager", + () => ExtensionParent.apiManager); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "uuidGen", + "@mozilla.org/uuid-generator;1", + "nsIUUIDGenerator"); + +const { + flushJarCache, + instanceOf, +} = ExtensionUtils; + +XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole); + + +/** + * A skeleton Extension-like object, used for testing, which installs an + * add-on via the add-on manager when startup() is called, and + * uninstalles it on shutdown(). + * + * @param {string} id + * @param {nsIFile} file + * @param {nsIURI} rootURI + * @param {string} installType + */ +class MockExtension { + constructor(file, rootURI, installType) { + this.id = null; + this.file = file; + this.rootURI = rootURI; + this.installType = installType; + this.addon = null; + + let promiseEvent = eventName => new Promise(resolve => { + let onstartup = (msg, extension) => { + if (this.addon && extension.id == this.addon.id) { + apiManager.off(eventName, onstartup); + + this.id = extension.id; + this._extension = extension; + resolve(extension); + } + }; + apiManager.on(eventName, onstartup); + }); + + this._extension = null; + this._extensionPromise = promiseEvent("startup"); + this._readyPromise = promiseEvent("ready"); + } + + testMessage(...args) { + return this._extension.testMessage(...args); + } + + on(...args) { + this._extensionPromise.then(extension => { + extension.on(...args); + }); + } + + off(...args) { + this._extensionPromise.then(extension => { + extension.off(...args); + }); + } + + startup() { + if (this.installType == "temporary") { + return AddonManager.installTemporaryAddon(this.file).then(addon => { + this.addon = addon; + return this._readyPromise; + }); + } else if (this.installType == "permanent") { + return new Promise((resolve, reject) => { + AddonManager.getInstallForFile(this.file, install => { + let listener = { + onInstallFailed: reject, + onInstallEnded: (install, newAddon) => { + this.addon = newAddon; + resolve(this._readyPromise); + }, + }; + + install.addListener(listener); + install.install(); + }); + }); + } + throw new Error("installType must be one of: temporary, permanent"); + } + + shutdown() { + this.addon.uninstall(); + return this.cleanupGeneratedFile(); + } + + cleanupGeneratedFile() { + flushJarCache(this.file); + return OS.File.remove(this.file.path); + } +} + +class ExtensionTestCommon { + /** + * This code is designed to make it easy to test a WebExtension + * without creating a bunch of files. Everything is contained in a + * single JSON blob. + * + * Properties: + * "background": "" + * A script to be loaded as the background script. + * The "background" section of the "manifest" property is overwritten + * if this is provided. + * "manifest": {...} + * Contents of manifest.json + * "files": {"filename1": "contents1", ...} + * Data to be included as files. Can be referenced from the manifest. + * If a manifest file is provided here, it takes precedence over + * a generated one. Always use "/" as a directory separator. + * Directories should appear here only implicitly (as a prefix + * to file names) + * + * To make things easier, the value of "background" and "files"[] can + * be a function, which is converted to source that is run. + * + * The generated extension is stored in the system temporary directory, + * and an nsIFile object pointing to it is returned. + * + * @param {object} data + * @returns {nsIFile} + */ + static generateXPI(data) { + let manifest = data.manifest; + if (!manifest) { + manifest = {}; + } + + let files = data.files; + if (!files) { + files = {}; + } + + function provide(obj, keys, value, override = false) { + if (keys.length == 1) { + if (!(keys[0] in obj) || override) { + obj[keys[0]] = value; + } + } else { + if (!(keys[0] in obj)) { + obj[keys[0]] = {}; + } + provide(obj[keys[0]], keys.slice(1), value, override); + } + } + + provide(manifest, ["name"], "Generated extension"); + provide(manifest, ["manifest_version"], 2); + provide(manifest, ["version"], "1.0"); + + if (data.background) { + let bgScript = uuidGen.generateUUID().number + ".js"; + + provide(manifest, ["background", "scripts"], [bgScript], true); + files[bgScript] = data.background; + } + + provide(files, ["manifest.json"], manifest); + + if (data.embedded) { + // Package this as a webextension embedded inside a legacy + // extension. + + let xpiFiles = { + "install.rdf": ` + + + + + + + + + + `, + + "bootstrap.js": ` + function install() {} + function uninstall() {} + function shutdown() {} + + function startup(data) { + data.webExtension.startup(); + } + `, + }; + + for (let [path, data] of Object.entries(files)) { + xpiFiles[`webextension/${path}`] = data; + } + + files = xpiFiles; + } + + return this.generateZipFile(files); + } + + static generateZipFile(files, baseName = "generated-extension.xpi") { + let ZipWriter = Components.Constructor("@mozilla.org/zipwriter;1", "nsIZipWriter"); + let zipW = new ZipWriter(); + + let file = FileUtils.getFile("TmpD", [baseName]); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); + + const MODE_WRONLY = 0x02; + const MODE_TRUNCATE = 0x20; + zipW.open(file, MODE_WRONLY | MODE_TRUNCATE); + + // Needs to be in microseconds for some reason. + let time = Date.now() * 1000; + + function generateFile(filename) { + let components = filename.split("/"); + let path = ""; + for (let component of components.slice(0, -1)) { + path += component + "/"; + if (!zipW.hasEntry(path)) { + zipW.addEntryDirectory(path, time, false); + } + } + } + + for (let filename in files) { + let script = files[filename]; + if (typeof(script) == "function") { + script = "(" + script.toString() + ")()"; + } else if (instanceOf(script, "Object") || instanceOf(script, "Array")) { + script = JSON.stringify(script); + } + + if (!instanceOf(script, "ArrayBuffer")) { + script = new TextEncoder("utf-8").encode(script).buffer; + } + + let stream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"].createInstance(Ci.nsIArrayBufferInputStream); + stream.setData(script, 0, script.byteLength); + + generateFile(filename); + zipW.addEntryStream(filename, time, 0, stream, false); + } + + zipW.close(); + + return file; + } + + /** + * Generates a new extension using |Extension.generateXPI|, and initializes a + * new |Extension| instance which will execute it. + * + * @param {object} data + * @returns {Extension} + */ + static generate(data) { + let file = this.generateXPI(data); + + flushJarCache(file); + Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {path: file.path}); + + let fileURI = Services.io.newFileURI(file); + let jarURI = Services.io.newURI("jar:" + fileURI.spec + "!/", null, null); + + // This may be "temporary" or "permanent". + if (data.useAddonManager) { + return new MockExtension(file, jarURI, data.useAddonManager); + } + + let id; + if (data.manifest) { + if (data.manifest.applications && data.manifest.applications.gecko) { + id = data.manifest.applications.gecko.id; + } else if (data.manifest.browser_specific_settings && data.manifest.browser_specific_settings.gecko) { + id = data.manifest.browser_specific_settings.gecko.id; + } + } + if (!id) { + id = uuidGen.generateUUID().number; + } + + return new Extension({ + id, + resourceURI: jarURI, + cleanupFile: file, + }); + } +} diff --git a/toolkit/components/webextensions/ExtensionUtils.jsm b/toolkit/components/webextensions/ExtensionUtils.jsm new file mode 100644 index 000000000..e7f768c07 --- /dev/null +++ b/toolkit/components/webextensions/ExtensionUtils.jsm @@ -0,0 +1,1215 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["ExtensionUtils"]; + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const Cr = Components.results; + +const INTEGER = /^[1-9]\d*$/; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", + "resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI", + "resource://gre/modules/Console.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector", + "resource:///modules/translation/LanguageDetector.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Locale", + "resource://gre/modules/Locale.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", + "resource://gre/modules/MessageChannel.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Preferences", + "resource://gre/modules/Preferences.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService", + "@mozilla.org/content/style-sheet-service;1", + "nsIStyleSheetService"); + +function getConsole() { + return new ConsoleAPI({ + maxLogLevelPref: "extensions.webextensions.log.level", + prefix: "WebExtensions", + }); +} + +XPCOMUtils.defineLazyGetter(this, "console", getConsole); + +let nextId = 0; +const {uniqueProcessID} = Services.appinfo; + +function getUniqueId() { + return `${nextId++}-${uniqueProcessID}`; +} + +/** + * An Error subclass for which complete error messages are always passed + * to extensions, rather than being interpreted as an unknown error. + */ +class ExtensionError extends Error {} + +function filterStack(error) { + return String(error.stack).replace(/(^.*(Task\.jsm|Promise-backend\.js).*\n)+/gm, "\n"); +} + +// Run a function and report exceptions. +function runSafeSyncWithoutClone(f, ...args) { + try { + return f(...args); + } catch (e) { + dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(Error())}]]\n`); + Cu.reportError(e); + } +} + +// Run a function and report exceptions. +function runSafeWithoutClone(f, ...args) { + if (typeof(f) != "function") { + dump(`Extension error: expected function\n${filterStack(Error())}`); + return; + } + + Promise.resolve().then(() => { + runSafeSyncWithoutClone(f, ...args); + }); +} + +// Run a function, cloning arguments into context.cloneScope, and +// report exceptions. |f| is expected to be in context.cloneScope. +function runSafeSync(context, f, ...args) { + if (context.unloaded) { + Cu.reportError("runSafeSync called after context unloaded"); + return; + } + + try { + args = Cu.cloneInto(args, context.cloneScope); + } catch (e) { + Cu.reportError(e); + dump(`runSafe failure: cloning into ${context.cloneScope}: ${e}\n\n${filterStack(Error())}`); + } + return runSafeSyncWithoutClone(f, ...args); +} + +// Run a function, cloning arguments into context.cloneScope, and +// report exceptions. |f| is expected to be in context.cloneScope. +function runSafe(context, f, ...args) { + try { + args = Cu.cloneInto(args, context.cloneScope); + } catch (e) { + Cu.reportError(e); + dump(`runSafe failure: cloning into ${context.cloneScope}: ${e}\n\n${filterStack(Error())}`); + } + if (context.unloaded) { + dump(`runSafe failure: context is already unloaded ${filterStack(new Error())}\n`); + return undefined; + } + return runSafeWithoutClone(f, ...args); +} + +function getInnerWindowID(window) { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .currentInnerWindowID; +} + +// Return true if the given value is an instance of the given +// native type. +function instanceOf(value, type) { + return {}.toString.call(value) == `[object ${type}]`; +} + +// Extend the object |obj| with the property descriptors of each object in +// |args|. +function extend(obj, ...args) { + for (let arg of args) { + let props = [...Object.getOwnPropertyNames(arg), + ...Object.getOwnPropertySymbols(arg)]; + for (let prop of props) { + let descriptor = Object.getOwnPropertyDescriptor(arg, prop); + Object.defineProperty(obj, prop, descriptor); + } + } + + return obj; +} + +/** + * Similar to a WeakMap, but creates a new key with the given + * constructor if one is not present. + */ +class DefaultWeakMap extends WeakMap { + constructor(defaultConstructor, init) { + super(init); + this.defaultConstructor = defaultConstructor; + } + + get(key) { + if (!this.has(key)) { + this.set(key, this.defaultConstructor(key)); + } + return super.get(key); + } +} + +class DefaultMap extends Map { + constructor(defaultConstructor, init) { + super(init); + this.defaultConstructor = defaultConstructor; + } + + get(key) { + if (!this.has(key)) { + this.set(key, this.defaultConstructor(key)); + } + return super.get(key); + } +} + +class SpreadArgs extends Array { + constructor(args) { + super(); + this.push(...args); + } +} + +// Manages icon details for toolbar buttons in the |pageAction| and +// |browserAction| APIs. +let IconDetails = { + // Normalizes the various acceptable input formats into an object + // with icon size as key and icon URL as value. + // + // If a context is specified (function is called from an extension): + // Throws an error if an invalid icon size was provided or the + // extension is not allowed to load the specified resources. + // + // If no context is specified, instead of throwing an error, this + // function simply logs a warning message. + normalize(details, extension, context = null) { + let result = {}; + + try { + if (details.imageData) { + let imageData = details.imageData; + + if (typeof imageData == "string") { + imageData = {"19": imageData}; + } + + for (let size of Object.keys(imageData)) { + if (!INTEGER.test(size)) { + throw new ExtensionError(`Invalid icon size ${size}, must be an integer`); + } + result[size] = imageData[size]; + } + } + + if (details.path) { + let path = details.path; + if (typeof path != "object") { + path = {"19": path}; + } + + let baseURI = context ? context.uri : extension.baseURI; + + for (let size of Object.keys(path)) { + if (!INTEGER.test(size)) { + throw new ExtensionError(`Invalid icon size ${size}, must be an integer`); + } + + let url = baseURI.resolve(path[size]); + + // The Chrome documentation specifies these parameters as + // relative paths. We currently accept absolute URLs as well, + // which means we need to check that the extension is allowed + // to load them. This will throw an error if it's not allowed. + try { + Services.scriptSecurityManager.checkLoadURIStrWithPrincipal( + extension.principal, url, + Services.scriptSecurityManager.DISALLOW_SCRIPT); + } catch (e) { + throw new ExtensionError(`Illegal URL ${url}`); + } + + result[size] = url; + } + } + } catch (e) { + // Function is called from extension code, delegate error. + if (context) { + throw e; + } + // If there's no context, it's because we're handling this + // as a manifest directive. Log a warning rather than + // raising an error. + extension.manifestError(`Invalid icon data: ${e}`); + } + + return result; + }, + + // Returns the appropriate icon URL for the given icons object and the + // screen resolution of the given window. + getPreferredIcon(icons, extension = null, size = 16) { + const DEFAULT = "chrome://browser/content/extension.svg"; + + let bestSize = null; + if (icons[size]) { + bestSize = size; + } else if (icons[2 * size]) { + bestSize = 2 * size; + } else { + let sizes = Object.keys(icons) + .map(key => parseInt(key, 10)) + .sort((a, b) => a - b); + + bestSize = sizes.find(candidate => candidate > size) || sizes.pop(); + } + + if (bestSize) { + return {size: bestSize, icon: icons[bestSize]}; + } + + return {size, icon: DEFAULT}; + }, + + convertImageURLToDataURL(imageURL, contentWindow, browserWindow, size = 18) { + return new Promise((resolve, reject) => { + let image = new contentWindow.Image(); + image.onload = function() { + let canvas = contentWindow.document.createElement("canvas"); + let ctx = canvas.getContext("2d"); + let dSize = size * browserWindow.devicePixelRatio; + + // Scales the image while maintaing width to height ratio. + // If the width and height differ, the image is centered using the + // smaller of the two dimensions. + let dWidth, dHeight, dx, dy; + if (this.width > this.height) { + dWidth = dSize; + dHeight = image.height * (dSize / image.width); + dx = 0; + dy = (dSize - dHeight) / 2; + } else { + dWidth = image.width * (dSize / image.height); + dHeight = dSize; + dx = (dSize - dWidth) / 2; + dy = 0; + } + + ctx.drawImage(this, 0, 0, this.width, this.height, dx, dy, dWidth, dHeight); + resolve(canvas.toDataURL("image/png")); + }; + image.onerror = reject; + image.src = imageURL; + }); + }, +}; + +const LISTENERS = Symbol("listeners"); + +class EventEmitter { + constructor() { + this[LISTENERS] = new Map(); + } + + /** + * Adds the given function as a listener for the given event. + * + * The listener function may optionally return a Promise which + * resolves when it has completed all operations which event + * dispatchers may need to block on. + * + * @param {string} event + * The name of the event to listen for. + * @param {function(string, ...any)} listener + * The listener to call when events are emitted. + */ + on(event, listener) { + if (!this[LISTENERS].has(event)) { + this[LISTENERS].set(event, new Set()); + } + + this[LISTENERS].get(event).add(listener); + } + + /** + * Removes the given function as a listener for the given event. + * + * @param {string} event + * The name of the event to stop listening for. + * @param {function(string, ...any)} listener + * The listener function to remove. + */ + off(event, listener) { + if (this[LISTENERS].has(event)) { + let set = this[LISTENERS].get(event); + + set.delete(listener); + if (!set.size) { + this[LISTENERS].delete(event); + } + } + } + + /** + * Triggers all listeners for the given event, and returns a promise + * which resolves when all listeners have been called, and any + * promises they have returned have likewise resolved. + * + * @param {string} event + * The name of the event to emit. + * @param {any} args + * Arbitrary arguments to pass to the listener functions, after + * the event name. + * @returns {Promise} + */ + emit(event, ...args) { + let listeners = this[LISTENERS].get(event) || new Set(); + + let promises = Array.from(listeners, listener => { + return runSafeSyncWithoutClone(listener, event, ...args); + }); + + return Promise.all(promises); + } +} + +function LocaleData(data) { + this.defaultLocale = data.defaultLocale; + this.selectedLocale = data.selectedLocale; + this.locales = data.locales || new Map(); + this.warnedMissingKeys = new Set(); + + // Map(locale-name -> Map(message-key -> localized-string)) + // + // Contains a key for each loaded locale, each of which is a + // Map of message keys to their localized strings. + this.messages = data.messages || new Map(); + + if (data.builtinMessages) { + this.messages.set(this.BUILTIN, data.builtinMessages); + } +} + + +LocaleData.prototype = { + // Representation of the object to send to content processes. This + // should include anything the content process might need. + serialize() { + return { + defaultLocale: this.defaultLocale, + selectedLocale: this.selectedLocale, + messages: this.messages, + locales: this.locales, + }; + }, + + BUILTIN: "@@BUILTIN_MESSAGES", + + has(locale) { + return this.messages.has(locale); + }, + + // https://developer.chrome.com/extensions/i18n + localizeMessage(message, substitutions = [], options = {}) { + let defaultOptions = { + locale: this.selectedLocale, + defaultValue: "", + cloneScope: null, + }; + + options = Object.assign(defaultOptions, options); + + let locales = new Set([this.BUILTIN, options.locale, this.defaultLocale] + .filter(locale => this.messages.has(locale))); + + // Message names are case-insensitive, so normalize them to lower-case. + message = message.toLowerCase(); + for (let locale of locales) { + let messages = this.messages.get(locale); + if (messages.has(message)) { + let str = messages.get(message); + + if (!Array.isArray(substitutions)) { + substitutions = [substitutions]; + } + + let replacer = (matched, index, dollarSigns) => { + if (index) { + // This is not quite Chrome-compatible. Chrome consumes any number + // of digits following the $, but only accepts 9 substitutions. We + // accept any number of substitutions. + index = parseInt(index, 10) - 1; + return index in substitutions ? substitutions[index] : ""; + } + // For any series of contiguous `$`s, the first is dropped, and + // the rest remain in the output string. + return dollarSigns; + }; + return str.replace(/\$(?:([1-9]\d*)|(\$+))/g, replacer); + } + } + + // Check for certain pre-defined messages. + if (message == "@@ui_locale") { + return this.uiLocale; + } else if (message.startsWith("@@bidi_")) { + let registry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry); + let rtl = registry.isLocaleRTL("global"); + + if (message == "@@bidi_dir") { + return rtl ? "rtl" : "ltr"; + } else if (message == "@@bidi_reversed_dir") { + return rtl ? "ltr" : "rtl"; + } else if (message == "@@bidi_start_edge") { + return rtl ? "right" : "left"; + } else if (message == "@@bidi_end_edge") { + return rtl ? "left" : "right"; + } + } + + if (!this.warnedMissingKeys.has(message)) { + let error = `Unknown localization message ${message}`; + if (options.cloneScope) { + error = new options.cloneScope.Error(error); + } + Cu.reportError(error); + this.warnedMissingKeys.add(message); + } + return options.defaultValue; + }, + + // Localize a string, replacing all |__MSG_(.*)__| tokens with the + // matching string from the current locale, as determined by + // |this.selectedLocale|. + // + // This may not be called before calling either |initLocale| or + // |initAllLocales|. + localize(str, locale = this.selectedLocale) { + if (!str) { + return str; + } + + return str.replace(/__MSG_([A-Za-z0-9@_]+?)__/g, (matched, message) => { + return this.localizeMessage(message, [], {locale, defaultValue: matched}); + }); + }, + + // Validates the contents of a locale JSON file, normalizes the + // messages into a Map of message key -> localized string pairs. + addLocale(locale, messages, extension) { + let result = new Map(); + + // Chrome does not document the semantics of its localization + // system very well. It handles replacements by pre-processing + // messages, replacing |$[a-zA-Z0-9@_]+$| tokens with the value of their + // replacements. Later, it processes the resulting string for + // |$[0-9]| replacements. + // + // Again, it does not document this, but it accepts any number + // of sequential |$|s, and replaces them with that number minus + // 1. It also accepts |$| followed by any number of sequential + // digits, but refuses to process a localized string which + // provides more than 9 substitutions. + if (!instanceOf(messages, "Object")) { + extension.packagingError(`Invalid locale data for ${locale}`); + return result; + } + + for (let key of Object.keys(messages)) { + let msg = messages[key]; + + if (!instanceOf(msg, "Object") || typeof(msg.message) != "string") { + extension.packagingError(`Invalid locale message data for ${locale}, message ${JSON.stringify(key)}`); + continue; + } + + // Substitutions are case-insensitive, so normalize all of their names + // to lower-case. + let placeholders = new Map(); + if (instanceOf(msg.placeholders, "Object")) { + for (let key of Object.keys(msg.placeholders)) { + placeholders.set(key.toLowerCase(), msg.placeholders[key]); + } + } + + let replacer = (match, name) => { + let replacement = placeholders.get(name.toLowerCase()); + if (instanceOf(replacement, "Object") && "content" in replacement) { + return replacement.content; + } + return ""; + }; + + let value = msg.message.replace(/\$([A-Za-z0-9@_]+)\$/g, replacer); + + // Message names are also case-insensitive, so normalize them to lower-case. + result.set(key.toLowerCase(), value); + } + + this.messages.set(locale, result); + return result; + }, + + get acceptLanguages() { + let result = Preferences.get("intl.accept_languages", "", Ci.nsIPrefLocalizedString); + return result.split(/\s*,\s*/g); + }, + + + get uiLocale() { + // Return the browser locale, but convert it to a Chrome-style + // locale code. + return Locale.getLocale().replace(/-/g, "_"); + }, +}; + +// This is a generic class for managing event listeners. Example usage: +// +// new EventManager(context, "api.subAPI", fire => { +// let listener = (...) => { +// // Fire any listeners registered with addListener. +// fire(arg1, arg2); +// }; +// // Register the listener. +// SomehowRegisterListener(listener); +// return () => { +// // Return a way to unregister the listener. +// SomehowUnregisterListener(listener); +// }; +// }).api() +// +// The result is an object with addListener, removeListener, and +// hasListener methods. |context| is an add-on scope (either an +// ExtensionContext in the chrome process or ExtensionContext in a +// content process). |name| is for debugging. |register| is a function +// to register the listener. |register| is only called once, even if +// multiple listeners are registered. |register| should return an +// unregister function that will unregister the listener. +function EventManager(context, name, register) { + this.context = context; + this.name = name; + this.register = register; + this.unregister = null; + this.callbacks = new Set(); +} + +EventManager.prototype = { + addListener(callback) { + if (typeof(callback) != "function") { + dump(`Expected function\n${Error().stack}`); + return; + } + if (this.context.unloaded) { + dump(`Cannot add listener to ${this.name} after context unloaded`); + return; + } + + if (!this.callbacks.size) { + this.context.callOnClose(this); + + let fireFunc = this.fire.bind(this); + let fireWithoutClone = this.fireWithoutClone.bind(this); + fireFunc.withoutClone = fireWithoutClone; + this.unregister = this.register(fireFunc); + } + this.callbacks.add(callback); + }, + + removeListener(callback) { + if (!this.callbacks.size) { + return; + } + + this.callbacks.delete(callback); + if (this.callbacks.size == 0) { + this.unregister(); + this.unregister = null; + + this.context.forgetOnClose(this); + } + }, + + hasListener(callback) { + return this.callbacks.has(callback); + }, + + fire(...args) { + this._fireCommon("runSafe", args); + }, + + fireWithoutClone(...args) { + this._fireCommon("runSafeWithoutClone", args); + }, + + _fireCommon(runSafeMethod, args) { + for (let callback of this.callbacks) { + Promise.resolve(callback).then(callback => { + if (this.context.unloaded) { + dump(`${this.name} event fired after context unloaded.\n`); + } else if (!this.context.active) { + dump(`${this.name} event fired while context is inactive.\n`); + } else if (this.callbacks.has(callback)) { + this.context[runSafeMethod](callback, ...args); + } + }); + } + }, + + close() { + if (this.callbacks.size) { + this.unregister(); + } + this.callbacks.clear(); + this.register = null; + this.unregister = null; + }, + + api() { + return { + addListener: callback => this.addListener(callback), + removeListener: callback => this.removeListener(callback), + hasListener: callback => this.hasListener(callback), + }; + }, +}; + +// Similar to EventManager, but it doesn't try to consolidate event +// notifications. Each addListener call causes us to register once. It +// allows extra arguments to be passed to addListener. +function SingletonEventManager(context, name, register) { + this.context = context; + this.name = name; + this.register = register; + this.unregister = new Map(); +} + +SingletonEventManager.prototype = { + addListener(callback, ...args) { + let wrappedCallback = (...args) => { + if (this.context.unloaded) { + dump(`${this.name} event fired after context unloaded.\n`); + } else if (this.unregister.has(callback)) { + return callback(...args); + } + }; + + let unregister = this.register(wrappedCallback, ...args); + this.unregister.set(callback, unregister); + this.context.callOnClose(this); + }, + + removeListener(callback) { + if (!this.unregister.has(callback)) { + return; + } + + let unregister = this.unregister.get(callback); + this.unregister.delete(callback); + unregister(); + }, + + hasListener(callback) { + return this.unregister.has(callback); + }, + + close() { + for (let unregister of this.unregister.values()) { + unregister(); + } + }, + + api() { + return { + addListener: (...args) => this.addListener(...args), + removeListener: (...args) => this.removeListener(...args), + hasListener: (...args) => this.hasListener(...args), + }; + }, +}; + +// Simple API for event listeners where events never fire. +function ignoreEvent(context, name) { + return { + addListener: function(callback) { + let id = context.extension.id; + let frame = Components.stack.caller; + let msg = `In add-on ${id}, attempting to use listener "${name}", which is unimplemented.`; + let scriptError = Cc["@mozilla.org/scripterror;1"] + .createInstance(Ci.nsIScriptError); + scriptError.init(msg, frame.filename, null, frame.lineNumber, + frame.columnNumber, Ci.nsIScriptError.warningFlag, + "content javascript"); + let consoleService = Cc["@mozilla.org/consoleservice;1"] + .getService(Ci.nsIConsoleService); + consoleService.logMessage(scriptError); + }, + removeListener: function(callback) {}, + hasListener: function(callback) {}, + }; +} + +// Copy an API object from |source| into the scope |dest|. +function injectAPI(source, dest) { + for (let prop in source) { + // Skip names prefixed with '_'. + if (prop[0] == "_") { + continue; + } + + let desc = Object.getOwnPropertyDescriptor(source, prop); + if (typeof(desc.value) == "function") { + Cu.exportFunction(desc.value, dest, {defineAs: prop}); + } else if (typeof(desc.value) == "object") { + let obj = Cu.createObjectIn(dest, {defineAs: prop}); + injectAPI(desc.value, obj); + } else { + Object.defineProperty(dest, prop, desc); + } + } +} + +/** + * Returns a Promise which resolves when the given document's DOM has + * fully loaded. + * + * @param {Document} doc The document to await the load of. + * @returns {Promise} + */ +function promiseDocumentReady(doc) { + if (doc.readyState == "interactive" || doc.readyState == "complete") { + return Promise.resolve(doc); + } + + return new Promise(resolve => { + doc.addEventListener("DOMContentLoaded", function onReady(event) { + if (event.target === event.currentTarget) { + doc.removeEventListener("DOMContentLoaded", onReady, true); + resolve(doc); + } + }, true); + }); +} + +/** + * Returns a Promise which resolves when the given document is fully + * loaded. + * + * @param {Document} doc The document to await the load of. + * @returns {Promise} + */ +function promiseDocumentLoaded(doc) { + if (doc.readyState == "complete") { + return Promise.resolve(doc); + } + + return new Promise(resolve => { + doc.defaultView.addEventListener("load", function onReady(event) { + doc.defaultView.removeEventListener("load", onReady); + resolve(doc); + }); + }); +} + +/** + * Returns a Promise which resolves when the given event is dispatched to the + * given element. + * + * @param {Element} element + * The element on which to listen. + * @param {string} eventName + * The event to listen for. + * @param {boolean} [useCapture = true] + * If true, listen for the even in the capturing rather than + * bubbling phase. + * @param {Event} [test] + * An optional test function which, when called with the + * observer's subject and data, should return true if this is the + * expected event, false otherwise. + * @returns {Promise} + */ +function promiseEvent(element, eventName, useCapture = true, test = event => true) { + return new Promise(resolve => { + function listener(event) { + if (test(event)) { + element.removeEventListener(eventName, listener, useCapture); + resolve(event); + } + } + element.addEventListener(eventName, listener, useCapture); + }); +} + +/** + * Returns a Promise which resolves the given observer topic has been + * observed. + * + * @param {string} topic + * The topic to observe. + * @param {function(nsISupports, string)} [test] + * An optional test function which, when called with the + * observer's subject and data, should return true if this is the + * expected notification, false otherwise. + * @returns {Promise} + */ +function promiseObserved(topic, test = () => true) { + return new Promise(resolve => { + let observer = (subject, topic, data) => { + if (test(subject, data)) { + Services.obs.removeObserver(observer, topic); + resolve({subject, data}); + } + }; + Services.obs.addObserver(observer, topic, false); + }); +} + +function getMessageManager(target) { + if (target instanceof Ci.nsIFrameLoaderOwner) { + return target.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; + } + return target.QueryInterface(Ci.nsIMessageSender); +} + +function flushJarCache(jarFile) { + Services.obs.notifyObservers(jarFile, "flush-cache-entry", null); +} + +const PlatformInfo = Object.freeze({ + os: (function() { + let os = AppConstants.platform; + if (os == "macosx") { + os = "mac"; + } + return os; + })(), + arch: (function() { + let abi = Services.appinfo.XPCOMABI; + let [arch] = abi.split("-"); + if (arch == "x86") { + arch = "x86-32"; + } else if (arch == "x86_64") { + arch = "x86-64"; + } + return arch; + })(), +}); + +function detectLanguage(text) { + return LanguageDetector.detectLanguage(text).then(result => ({ + isReliable: result.confident, + languages: result.languages.map(lang => { + return { + language: lang.languageCode, + percentage: lang.percent, + }; + }), + })); +} + +/** + * Convert any of several different representations of a date/time to a Date object. + * Accepts several formats: + * a Date object, an ISO8601 string, or a number of milliseconds since the epoch as + * either a number or a string. + * + * @param {Date|string|number} date + * The date to convert. + * @returns {Date} + * A Date object + */ +function normalizeTime(date) { + // Of all the formats we accept the "number of milliseconds since the epoch as a string" + // is an outlier, everything else can just be passed directly to the Date constructor. + return new Date((typeof date == "string" && /^\d+$/.test(date)) + ? parseInt(date, 10) : date); +} + +const stylesheetMap = new DefaultMap(url => { + let uri = NetUtil.newURI(url); + return styleSheetService.preloadSheet(uri, styleSheetService.AGENT_SHEET); +}); + +/** + * Defines a lazy getter for the given property on the given object. The + * first time the property is accessed, the return value of the getter + * is defined on the current `this` object with the given property name. + * Importantly, this means that a lazy getter defined on an object + * prototype will be invoked separately for each object instance that + * it's accessed on. + * + * @param {object} object + * The prototype object on which to define the getter. + * @param {string|Symbol} prop + * The property name for which to define the getter. + * @param {function} getter + * The function to call in order to generate the final property + * value. + */ +function defineLazyGetter(object, prop, getter) { + let redefine = (obj, value) => { + Object.defineProperty(obj, prop, { + enumerable: true, + configurable: true, + writable: true, + value, + }); + return value; + }; + + Object.defineProperty(object, prop, { + enumerable: true, + configurable: true, + + get() { + return redefine(this, getter.call(this)); + }, + + set(value) { + redefine(this, value); + }, + }); +} + +function findPathInObject(obj, path, printErrors = true) { + let parent; + for (let elt of path.split(".")) { + if (!obj || !(elt in obj)) { + if (printErrors) { + Cu.reportError(`WebExtension API ${path} not found (it may be unimplemented by Firefox).`); + } + return null; + } + + parent = obj; + obj = obj[elt]; + } + + if (typeof obj === "function") { + return obj.bind(parent); + } + return obj; +} + +/** + * Acts as a proxy for a message manager or message manager owner, and + * tracks docShell swaps so that messages are always sent to the same + * receiver, even if it is moved to a different . + * + * @param {nsIMessageSender|Element} target + * The target message manager on which to send messages, or the + * element which owns it. + */ +class MessageManagerProxy { + constructor(target) { + this.listeners = new DefaultMap(() => new Map()); + + if (target instanceof Ci.nsIMessageSender) { + Object.defineProperty(this, "messageManager", { + value: target, + configurable: true, + writable: true, + }); + } else { + this.addListeners(target); + } + } + + /** + * Disposes of the proxy object, removes event listeners, and drops + * all references to the underlying message manager. + * + * Must be called before the last reference to the proxy is dropped, + * unless the underlying message manager or is also being + * destroyed. + */ + dispose() { + if (this.eventTarget) { + this.removeListeners(this.eventTarget); + this.eventTarget = null; + } else { + this.messageManager = null; + } + } + + /** + * Returns true if the given target is the same as, or owns, the given + * message manager. + * + * @param {nsIMessageSender|MessageManagerProxy|Element} target + * The message manager, MessageManagerProxy, or + * element agaisnt which to match. + * @param {nsIMessageSender} messageManager + * The message manager against which to match `target`. + * + * @returns {boolean} + * True if `messageManager` is the same object as `target`, or + * `target` is a MessageManagerProxy or element that + * is tied to it. + */ + static matches(target, messageManager) { + return target === messageManager || target.messageManager === messageManager; + } + + /** + * @property {nsIMessageSender|null} messageManager + * The message manager that is currently being proxied. This + * may change during the life of the proxy object, so should + * not be stored elsewhere. + */ + get messageManager() { + return this.eventTarget && this.eventTarget.messageManager; + } + + /** + * Sends a message on the proxied message manager. + * + * @param {array} args + * Arguments to be passed verbatim to the underlying + * sendAsyncMessage method. + * @returns {undefined} + */ + sendAsyncMessage(...args) { + if (this.messageManager) { + return this.messageManager.sendAsyncMessage(...args); + } + /* globals uneval */ + Cu.reportError(`Cannot send message: Other side disconnected: ${uneval(args)}`); + } + + /** + * Adds a message listener to the current message manager, and + * transfers it to the new message manager after a docShell swap. + * + * @param {string} message + * The name of the message to listen for. + * @param {nsIMessageListener} listener + * The listener to add. + * @param {boolean} [listenWhenClosed = false] + * If true, the listener will receive messages which were sent + * after the remote side of the listener began closing. + */ + addMessageListener(message, listener, listenWhenClosed = false) { + this.messageManager.addMessageListener(message, listener, listenWhenClosed); + this.listeners.get(message).set(listener, listenWhenClosed); + } + + /** + * Adds a message listener from the current message manager. + * + * @param {string} message + * The name of the message to stop listening for. + * @param {nsIMessageListener} listener + * The listener to remove. + */ + removeMessageListener(message, listener) { + this.messageManager.removeMessageListener(message, listener); + + let listeners = this.listeners.get(message); + listeners.delete(listener); + if (!listeners.size) { + this.listeners.delete(message); + } + } + + /** + * @private + * Iterates over all of the currently registered message listeners. + */ + * iterListeners() { + for (let [message, listeners] of this.listeners) { + for (let [listener, listenWhenClosed] of listeners) { + yield {message, listener, listenWhenClosed}; + } + } + } + + /** + * @private + * Adds docShell swap listeners to the message manager owner. + * + * @param {Element} target + * The target element. + */ + addListeners(target) { + target.addEventListener("SwapDocShells", this); + + for (let {message, listener, listenWhenClosed} of this.iterListeners()) { + target.addMessageListener(message, listener, listenWhenClosed); + } + + this.eventTarget = target; + } + + /** + * @private + * Removes docShell swap listeners to the message manager owner. + * + * @param {Element} target + * The target element. + */ + removeListeners(target) { + target.removeEventListener("SwapDocShells", this); + + for (let {message, listener} of this.iterListeners()) { + target.removeMessageListener(message, listener); + } + } + + handleEvent(event) { + if (event.type == "SwapDocShells") { + this.removeListeners(this.eventTarget); + this.addListeners(event.detail); + } + } +} + +this.ExtensionUtils = { + defineLazyGetter, + detectLanguage, + extend, + findPathInObject, + flushJarCache, + getConsole, + getInnerWindowID, + getMessageManager, + getUniqueId, + ignoreEvent, + injectAPI, + instanceOf, + normalizeTime, + promiseDocumentLoaded, + promiseDocumentReady, + promiseEvent, + promiseObserved, + runSafe, + runSafeSync, + runSafeSyncWithoutClone, + runSafeWithoutClone, + stylesheetMap, + DefaultMap, + DefaultWeakMap, + EventEmitter, + EventManager, + ExtensionError, + IconDetails, + LocaleData, + MessageManagerProxy, + PlatformInfo, + SingletonEventManager, + SpreadArgs, +}; diff --git a/toolkit/components/webextensions/ExtensionXPCShellUtils.jsm b/toolkit/components/webextensions/ExtensionXPCShellUtils.jsm new file mode 100644 index 000000000..339709a19 --- /dev/null +++ b/toolkit/components/webextensions/ExtensionXPCShellUtils.jsm @@ -0,0 +1,306 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["ExtensionTestUtils"]; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Components.utils.import("resource://gre/modules/Task.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Extension", + "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyGetter(this, "Management", () => { + const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {}); + return Management; +}); + +/* exported ExtensionTestUtils */ + +let BASE_MANIFEST = Object.freeze({ + "applications": Object.freeze({ + "gecko": Object.freeze({ + "id": "test@web.ext", + }), + }), + + "manifest_version": 2, + + "name": "name", + "version": "0", +}); + +class ExtensionWrapper { + constructor(extension, testScope) { + this.extension = extension; + this.testScope = testScope; + + this.state = "uninitialized"; + + this.testResolve = null; + this.testDone = new Promise(resolve => { this.testResolve = resolve; }); + + this.messageHandler = new Map(); + this.messageAwaiter = new Map(); + + this.messageQueue = new Set(); + + this.attachListeners(); + + this.testScope.do_register_cleanup(() => { + if (this.messageQueue.size) { + let names = Array.from(this.messageQueue, ([msg]) => msg); + this.testScope.equal(JSON.stringify(names), "[]", "message queue is empty"); + } + if (this.messageAwaiter.size) { + let names = Array.from(this.messageAwaiter.keys()); + this.testScope.equal(JSON.stringify(names), "[]", "no tasks awaiting on messages"); + } + }); + + this.testScope.do_register_cleanup(() => { + if (this.state == "pending" || this.state == "running") { + this.testScope.equal(this.state, "unloaded", "Extension left running at test shutdown"); + return this.unload(); + } else if (extension.state == "unloading") { + this.testScope.equal(this.state, "unloaded", "Extension not fully unloaded at test shutdown"); + } + }); + + this.testScope.do_print(`Extension loaded`); + } + + attachListeners() { + /* eslint-disable mozilla/balanced-listeners */ + this.extension.on("test-eq", (kind, pass, msg, expected, actual) => { + this.testScope.ok(pass, `${msg} - Expected: ${expected}, Actual: ${actual}`); + }); + this.extension.on("test-log", (kind, pass, msg) => { + this.testScope.do_print(msg); + }); + this.extension.on("test-result", (kind, pass, msg) => { + this.testScope.ok(pass, msg); + }); + this.extension.on("test-done", (kind, pass, msg, expected, actual) => { + this.testScope.ok(pass, msg); + this.testResolve(msg); + }); + + this.extension.on("test-message", (kind, msg, ...args) => { + let handler = this.messageHandler.get(msg); + if (handler) { + handler(...args); + } else { + this.messageQueue.add([msg, ...args]); + this.checkMessages(); + } + }); + /* eslint-enable mozilla/balanced-listeners */ + } + + startup() { + if (this.state != "uninitialized") { + throw new Error("Extension already started"); + } + this.state = "pending"; + + return this.extension.startup().then( + result => { + this.state = "running"; + + return result; + }, + error => { + this.state = "failed"; + + return Promise.reject(error); + }); + } + + unload() { + if (this.state != "running") { + throw new Error("Extension not running"); + } + this.state = "unloading"; + + this.extension.shutdown(); + + this.state = "unloaded"; + + return Promise.resolve(); + } + + /* + * This method marks the extension unloading without actually calling + * shutdown, since shutting down a MockExtension causes it to be uninstalled. + * + * Normally you shouldn't need to use this unless you need to test something + * that requires a restart, such as updates. + */ + markUnloaded() { + if (this.state != "running") { + throw new Error("Extension not running"); + } + this.state = "unloaded"; + + return Promise.resolve(); + } + + sendMessage(...args) { + this.extension.testMessage(...args); + } + + awaitFinish(msg) { + return this.testDone.then(actual => { + if (msg) { + this.testScope.equal(actual, msg, "test result correct"); + } + return actual; + }); + } + + checkMessages() { + for (let message of this.messageQueue) { + let [msg, ...args] = message; + + let listener = this.messageAwaiter.get(msg); + if (listener) { + this.messageQueue.delete(message); + this.messageAwaiter.delete(msg); + + listener.resolve(...args); + return; + } + } + } + + checkDuplicateListeners(msg) { + if (this.messageHandler.has(msg) || this.messageAwaiter.has(msg)) { + throw new Error("only one message handler allowed"); + } + } + + awaitMessage(msg) { + return new Promise(resolve => { + this.checkDuplicateListeners(msg); + + this.messageAwaiter.set(msg, {resolve}); + this.checkMessages(); + }); + } + + onMessage(msg, callback) { + this.checkDuplicateListeners(msg); + this.messageHandler.set(msg, callback); + } +} + +var ExtensionTestUtils = { + BASE_MANIFEST, + + normalizeManifest: Task.async(function* (manifest, baseManifest = BASE_MANIFEST) { + yield Management.lazyInit(); + + let errors = []; + let context = { + url: null, + + logError: error => { + errors.push(error); + }, + + preprocessors: {}, + }; + + manifest = Object.assign({}, baseManifest, manifest); + + let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context); + normalized.errors = errors; + + return normalized; + }), + + currentScope: null, + + profileDir: null, + + init(scope) { + this.currentScope = scope; + + this.profileDir = scope.do_get_profile(); + + // We need to load at least one frame script into every message + // manager to ensure that the scriptable wrapper for its global gets + // created before we try to access it externally. If we don't, we + // fail sanity checks on debug builds the first time we try to + // create a wrapper, because we should never have a global without a + // cached wrapper. + Services.mm.loadFrameScript("data:text/javascript,//", true); + + + let tmpD = this.profileDir.clone(); + tmpD.append("tmp"); + tmpD.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + let dirProvider = { + getFile(prop, persistent) { + persistent.value = false; + if (prop == "TmpD") { + return tmpD.clone(); + } + return null; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]), + }; + Services.dirsvc.registerProvider(dirProvider); + + + scope.do_register_cleanup(() => { + tmpD.remove(true); + Services.dirsvc.unregisterProvider(dirProvider); + + this.currentScope = null; + }); + }, + + addonManagerStarted: false, + + mockAppInfo() { + const {updateAppInfo} = Cu.import("resource://testing-common/AppInfo.jsm", {}); + updateAppInfo({ + ID: "xpcshell@tests.mozilla.org", + name: "XPCShell", + version: "48", + platformVersion: "48", + }); + }, + + startAddonManager() { + if (this.addonManagerStarted) { + return; + } + this.addonManagerStarted = true; + this.mockAppInfo(); + + let manager = Cc["@mozilla.org/addons/integration;1"].getService(Ci.nsIObserver) + .QueryInterface(Ci.nsITimerCallback); + manager.observe(null, "addons-startup", null); + }, + + loadExtension(data) { + let extension = Extension.generate(data); + + return new ExtensionWrapper(extension, this.currentScope); + }, +}; diff --git a/toolkit/components/webextensions/LegacyExtensionsUtils.jsm b/toolkit/components/webextensions/LegacyExtensionsUtils.jsm new file mode 100644 index 000000000..7632548e3 --- /dev/null +++ b/toolkit/components/webextensions/LegacyExtensionsUtils.jsm @@ -0,0 +1,250 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["LegacyExtensionsUtils"]; + +/* exported LegacyExtensionsUtils, LegacyExtensionContext */ + +/** + * This file exports helpers for Legacy Extensions that want to embed a webextensions + * and exchange messages with the embedded WebExtension. + */ + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Extension", + "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +Cu.import("resource://gre/modules/ExtensionChild.jsm"); +Cu.import("resource://gre/modules/ExtensionCommon.jsm"); + +var { + BaseContext, +} = ExtensionCommon; + +var { + Messenger, +} = ExtensionChild; + +/** + * Instances created from this class provide to a legacy extension + * a simple API to exchange messages with a webextension. + */ +var LegacyExtensionContext = class extends BaseContext { + /** + * Create a new LegacyExtensionContext given a target Extension instance. + * + * @param {Extension} targetExtension + * The webextension instance associated with this context. This will be the + * instance of the newly created embedded webextension when this class is + * used through the EmbeddedWebExtensionsUtils. + */ + constructor(targetExtension) { + super("legacy_extension", targetExtension); + + // Legacy Extensions (xul overlays, bootstrap restartless and Addon SDK) + // runs with a systemPrincipal. + let addonPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + Object.defineProperty( + this, "principal", + {value: addonPrincipal, enumerable: true, configurable: true} + ); + + let cloneScope = Cu.Sandbox(this.principal, {}); + Cu.setSandboxMetadata(cloneScope, {addonId: targetExtension.id}); + Object.defineProperty( + this, "cloneScope", + {value: cloneScope, enumerable: true, configurable: true, writable: true} + ); + + let sender = {id: targetExtension.uuid}; + let filter = {extensionId: targetExtension.id}; + // Legacy addons live in the main process. Messages from other addons are + // Messages from WebExtensions are sent to the main process and forwarded via + // the parent process manager to the legacy extension. + this.messenger = new Messenger(this, [Services.cpmm], sender, filter); + + this.api = { + browser: { + runtime: { + onConnect: this.messenger.onConnect("runtime.onConnect"), + onMessage: this.messenger.onMessage("runtime.onMessage"), + }, + }, + }; + } + + /** + * This method is called when the extension shuts down or is unloaded, + * and it nukes the cloneScope sandbox, if any. + */ + unload() { + if (this.unloaded) { + throw new Error("Error trying to unload LegacyExtensionContext twice."); + } + super.unload(); + Cu.nukeSandbox(this.cloneScope); + this.cloneScope = null; + } +}; + +var EmbeddedExtensionManager; + +/** + * Instances of this class are used internally by the exported EmbeddedWebExtensionsUtils + * to manage the embedded webextension instance and the related LegacyExtensionContext + * instance used to exchange messages with it. + */ +class EmbeddedExtension { + /** + * Create a new EmbeddedExtension given the add-on id and the base resource URI of the + * container add-on (the webextension resources will be loaded from the "webextension/" + * subdir of the base resource URI for the legacy extension add-on). + * + * @param {Object} containerAddonParams + * An object with the following properties: + * @param {string} containerAddonParams.id + * The Add-on id of the Legacy Extension which will contain the embedded webextension. + * @param {nsIURI} containerAddonParams.resourceURI + * The nsIURI of the Legacy Extension container add-on. + */ + constructor({id, resourceURI}) { + this.addonId = id; + this.resourceURI = resourceURI; + + // Setup status flag. + this.started = false; + } + + /** + * Start the embedded webextension. + * + * @returns {Promise} A promise which resolve to the API exposed to the + * legacy context. + */ + startup() { + if (this.started) { + return Promise.reject(new Error("This embedded extension has already been started")); + } + + // Setup the startup promise. + this.startupPromise = new Promise((resolve, reject) => { + let embeddedExtensionURI = Services.io.newURI("webextension/", null, this.resourceURI); + + // This is the instance of the WebExtension embedded in the hybrid add-on. + this.extension = new Extension({ + id: this.addonId, + resourceURI: embeddedExtensionURI, + }); + + // This callback is register to the "startup" event, emitted by the Extension instance + // after the extension manifest.json has been loaded without any errors, but before + // starting any of the defined contexts (which give the legacy part a chance to subscribe + // runtime.onMessage/onConnect listener before the background page has been loaded). + const onBeforeStarted = () => { + this.extension.off("startup", onBeforeStarted); + + // Resolve the startup promise and reset the startupError. + this.started = true; + this.startupPromise = null; + + // Create the legacy extension context, the legacy container addon + // needs to use it before the embedded webextension startup, + // because it is supposed to be used during the legacy container startup + // to subscribe its message listeners (which are supposed to be able to + // receive any message that the embedded part can try to send to it + // during its startup). + this.context = new LegacyExtensionContext(this.extension); + + // Destroy the LegacyExtensionContext cloneScope when + // the embedded webextensions is unloaded. + this.extension.callOnClose({ + close: () => { + this.context.unload(); + }, + }); + + // resolve startupPromise to execute any pending shutdown that has been + // chained to it. + resolve(this.context.api); + }; + + this.extension.on("startup", onBeforeStarted); + + // Run ambedded extension startup and catch any error during embedded extension + // startup. + this.extension.startup().catch((err) => { + this.started = false; + this.startupPromise = null; + this.extension.off("startup", onBeforeStarted); + + reject(err); + }); + }); + + return this.startupPromise; + } + + /** + * Shuts down the embedded webextension. + * + * @returns {Promise} a promise that is resolved when the shutdown has been done + */ + shutdown() { + EmbeddedExtensionManager.untrackEmbeddedExtension(this); + + // If there is a pending startup, wait to be completed and then shutdown. + if (this.startupPromise) { + return this.startupPromise.then(() => { + this.extension.shutdown(); + }); + } + + // Run shutdown now if the embedded webextension has been correctly started + if (this.extension && this.started && !this.extension.hasShutdown) { + this.extension.shutdown(); + } + + return Promise.resolve(); + } +} + +// Keep track on the created EmbeddedExtension instances and destroy +// them when their container addon is going to be disabled or uninstalled. +EmbeddedExtensionManager = { + // Map of the existent EmbeddedExtensions instances by addon id. + embeddedExtensionsByAddonId: new Map(), + + untrackEmbeddedExtension(embeddedExtensionInstance) { + // Remove this instance from the tracked embedded extensions + let id = embeddedExtensionInstance.addonId; + if (this.embeddedExtensionsByAddonId.get(id) == embeddedExtensionInstance) { + this.embeddedExtensionsByAddonId.delete(id); + } + }, + + getEmbeddedExtensionFor({id, resourceURI}) { + let embeddedExtension = this.embeddedExtensionsByAddonId.get(id); + + if (!embeddedExtension) { + embeddedExtension = new EmbeddedExtension({id, resourceURI}); + // Keep track of the embedded extension instance. + this.embeddedExtensionsByAddonId.set(id, embeddedExtension); + } + + return embeddedExtension; + }, +}; + +this.LegacyExtensionsUtils = { + getEmbeddedExtensionFor: (addon) => { + return EmbeddedExtensionManager.getEmbeddedExtensionFor(addon); + }, +}; diff --git a/toolkit/components/webextensions/MessageChannel.jsm b/toolkit/components/webextensions/MessageChannel.jsm new file mode 100644 index 000000000..c5b326405 --- /dev/null +++ b/toolkit/components/webextensions/MessageChannel.jsm @@ -0,0 +1,797 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 provides wrappers around standard message managers to + * simplify bidirectional communication. It currently allows a caller to + * send a message to a single listener, and receive a reply. If there + * are no matching listeners, or the message manager disconnects before + * a reply is received, the caller is returned an error. + * + * The listener end may specify filters for the messages it wishes to + * receive, and the sender end likewise may specify recipient tags to + * match the filters. + * + * The message handler on the listener side may return its response + * value directly, or may return a promise, the resolution or rejection + * of which will be returned instead. The sender end likewise receives a + * promise which resolves or rejects to the listener's response. + * + * + * A basic setup works something like this: + * + * A content script adds a message listener to its global + * nsIContentFrameMessageManager, with an appropriate set of filters: + * + * { + * init(messageManager, window, extensionID) { + * this.window = window; + * + * MessageChannel.addListener( + * messageManager, "ContentScript:TouchContent", + * this); + * + * this.messageFilterStrict = { + * innerWindowID: getInnerWindowID(window), + * extensionID: extensionID, + * }; + * + * this.messageFilterPermissive = { + * outerWindowID: getOuterWindowID(window), + * }; + * }, + * + * receiveMessage({ target, messageName, sender, recipient, data }) { + * if (messageName == "ContentScript:TouchContent") { + * return new Promise(resolve => { + * this.touchWindow(data.touchWith, result => { + * resolve({ touchResult: result }); + * }); + * }); + * } + * }, + * }; + * + * A script in the parent process sends a message to the content process + * via a tab message manager, including recipient tags to match its + * filter, and an optional sender tag to identify itself: + * + * let data = { touchWith: "pencil" }; + * let sender = { extensionID, contextID }; + * let recipient = { innerWindowID: tab.linkedBrowser.innerWindowID, extensionID }; + * + * MessageChannel.sendMessage( + * tab.linkedBrowser.messageManager, "ContentScript:TouchContent", + * data, {recipient, sender} + * ).then(result => { + * alert(result.touchResult); + * }); + * + * Since the lifetimes of message senders and receivers may not always + * match, either side of the message channel may cancel pending + * responses which match its sender or recipient tags. + * + * For the above client, this might be done from an + * inner-window-destroyed observer, when its target scope is destroyed: + * + * observe(subject, topic, data) { + * if (topic == "inner-window-destroyed") { + * let innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + * + * MessageChannel.abortResponses({ innerWindowID }); + * } + * }, + * + * From the parent, it may be done when its context is being destroyed: + * + * onDestroy() { + * MessageChannel.abortResponses({ + * extensionID: this.extensionID, + * contextID: this.contextID, + * }); + * }, + * + */ + +this.EXPORTED_SYMBOLS = ["MessageChannel"]; + +/* globals MessageChannel */ + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils", + "resource://gre/modules/ExtensionUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils", + "resource://gre/modules/PromiseUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + +XPCOMUtils.defineLazyGetter(this, "MessageManagerProxy", + () => ExtensionUtils.MessageManagerProxy); + +/** + * Handles the mapping and dispatching of messages to their registered + * handlers. There is one broker per message manager and class of + * messages. Each class of messages is mapped to one native message + * name, e.g., "MessageChannel:Message", and is dispatched to handlers + * based on an internal message name, e.g., "Extension:ExecuteScript". + */ +class FilteringMessageManager { + /** + * @param {string} messageName + * The name of the native message this broker listens for. + * @param {function} callback + * A function which is called for each message after it has been + * mapped to its handler. The function receives two arguments: + * + * result: + * An object containing either a `handler` or an `error` property. + * If no error occurs, `handler` will be a matching handler that + * was registered by `addHandler`. Otherwise, the `error` property + * will contain an object describing the error. + * + * data: + * An object describing the message, as defined in + * `MessageChannel.addListener`. + * @param {nsIMessageListenerManager} messageManager + */ + constructor(messageName, callback, messageManager) { + this.messageName = messageName; + this.callback = callback; + this.messageManager = messageManager; + + this.messageManager.addMessageListener(this.messageName, this, true); + + this.handlers = new Map(); + } + + /** + * Receives a message from our message manager, maps it to a handler, and + * passes the result to our message callback. + */ + receiveMessage({data, target}) { + let handlers = Array.from(this.getHandlers(data.messageName, data.sender, data.recipient)); + + data.target = target; + this.callback(handlers, data); + } + + /** + * Iterates over all handlers for the given message name. If `recipient` + * is provided, only iterates over handlers whose filters match it. + * + * @param {string|number} messageName + * The message for which to return handlers. + * @param {object} sender + * The sender data on which to filter handlers. + * @param {object} recipient + * The recipient data on which to filter handlers. + */ + * getHandlers(messageName, sender, recipient) { + let handlers = this.handlers.get(messageName) || new Set(); + for (let handler of handlers) { + if (MessageChannel.matchesFilter(handler.messageFilterStrict || {}, recipient) && + MessageChannel.matchesFilter(handler.messageFilterPermissive || {}, recipient, false) && + (!handler.filterMessage || handler.filterMessage(sender, recipient))) { + yield handler; + } + } + } + + /** + * Registers a handler for the given message. + * + * @param {string} messageName + * The internal message name for which to register the handler. + * @param {object} handler + * An opaque handler object. The object may have a + * `messageFilterStrict` and/or a `messageFilterPermissive` + * property and/or a `filterMessage` method on which to filter messages. + * + * Final dispatching is handled by the message callback passed to + * the constructor. + */ + addHandler(messageName, handler) { + if (!this.handlers.has(messageName)) { + this.handlers.set(messageName, new Set()); + } + + this.handlers.get(messageName).add(handler); + } + + /** + * Unregisters a handler for the given message. + * + * @param {string} messageName + * The internal message name for which to unregister the handler. + * @param {object} handler + * The handler object to unregister. + */ + removeHandler(messageName, handler) { + this.handlers.get(messageName).delete(handler); + } +} + +/** + * Manages mappings of message managers to their corresponding message + * brokers. Brokers are lazily created for each message manager the + * first time they are accessed. In the case of content frame message + * managers, they are also automatically destroyed when the frame + * unload event fires. + */ +class FilteringMessageManagerMap extends Map { + // Unfortunately, we can't use a WeakMap for this, because message + // managers do not support preserved wrappers. + + /** + * @param {string} messageName + * The native message name passed to `FilteringMessageManager` constructors. + * @param {function} callback + * The message callback function passed to + * `FilteringMessageManager` constructors. + */ + constructor(messageName, callback) { + super(); + + this.messageName = messageName; + this.callback = callback; + } + + /** + * Returns, and possibly creates, a message broker for the given + * message manager. + * + * @param {nsIMessageListenerManager} target + * The message manager for which to return a broker. + * + * @returns {FilteringMessageManager} + */ + get(target) { + if (this.has(target)) { + return super.get(target); + } + + let broker = new FilteringMessageManager(this.messageName, this.callback, target); + this.set(target, broker); + + if (target instanceof Ci.nsIDOMEventTarget) { + let onUnload = event => { + target.removeEventListener("unload", onUnload); + this.delete(target); + }; + target.addEventListener("unload", onUnload); + } + + return broker; + } +} + +const MESSAGE_MESSAGE = "MessageChannel:Message"; +const MESSAGE_RESPONSE = "MessageChannel:Response"; + +this.MessageChannel = { + init() { + Services.obs.addObserver(this, "message-manager-close", false); + Services.obs.addObserver(this, "message-manager-disconnect", false); + + this.messageManagers = new FilteringMessageManagerMap( + MESSAGE_MESSAGE, this._handleMessage.bind(this)); + + this.responseManagers = new FilteringMessageManagerMap( + MESSAGE_RESPONSE, this._handleResponse.bind(this)); + + /** + * Contains a list of pending responses, either waiting to be + * received or waiting to be sent. @see _addPendingResponse + */ + this.pendingResponses = new Set(); + }, + + RESULT_SUCCESS: 0, + RESULT_DISCONNECTED: 1, + RESULT_NO_HANDLER: 2, + RESULT_MULTIPLE_HANDLERS: 3, + RESULT_ERROR: 4, + RESULT_NO_RESPONSE: 5, + + REASON_DISCONNECTED: { + result: this.RESULT_DISCONNECTED, + message: "Message manager disconnected", + }, + + /** + * Specifies that only a single listener matching the specified + * recipient tag may be listening for the given message, at the other + * end of the target message manager. + * + * If no matching listeners exist, a RESULT_NO_HANDLER error will be + * returned. If multiple matching listeners exist, a + * RESULT_MULTIPLE_HANDLERS error will be returned. + */ + RESPONSE_SINGLE: 0, + + /** + * If multiple message managers matching the specified recipient tag + * are listening for a message, all listeners are notified, but only + * the first response or error is returned. + * + * Only handlers which return a value other than `undefined` are + * considered to have responded. Returning a Promise which evaluates + * to `undefined` is interpreted as an explicit response. + * + * If no matching listeners exist, a RESULT_NO_HANDLER error will be + * returned. If no listeners return a response, a RESULT_NO_RESPONSE + * error will be returned. + */ + RESPONSE_FIRST: 1, + + /** + * If multiple message managers matching the specified recipient tag + * are listening for a message, all listeners are notified, and all + * responses are returned as an array, once all listeners have + * replied. + */ + RESPONSE_ALL: 2, + + /** + * Fire-and-forget: The sender of this message does not expect a reply. + */ + RESPONSE_NONE: 3, + + /** + * Initializes message handlers for the given message managers if needed. + * + * @param {Array} messageManagers + */ + setupMessageManagers(messageManagers) { + for (let mm of messageManagers) { + // This call initializes a FilteringMessageManager for |mm| if needed. + // The FilteringMessageManager must be created to make sure that senders + // of messages that expect a reply, such as MessageChannel:Message, do + // actually receive a default reply even if there are no explicit message + // handlers. + this.messageManagers.get(mm); + } + }, + + /** + * Returns true if the properties of the `data` object match those in + * the `filter` object. Matching is done on a strict equality basis, + * and the behavior varies depending on the value of the `strict` + * parameter. + * + * @param {object} filter + * The filter object to match against. + * @param {object} data + * The data object being matched. + * @param {boolean} [strict=false] + * If true, all properties in the `filter` object have a + * corresponding property in `data` with the same value. If + * false, properties present in both objects must have the same + * value. + * @returns {boolean} True if the objects match. + */ + matchesFilter(filter, data, strict = true) { + if (strict) { + return Object.keys(filter).every(key => { + return key in data && data[key] === filter[key]; + }); + } + return Object.keys(filter).every(key => { + return !(key in data) || data[key] === filter[key]; + }); + }, + + /** + * Adds a message listener to the given message manager. + * + * @param {nsIMessageListenerManager|Array} targets + * The message managers on which to listen. + * @param {string|number} messageName + * The name of the message to listen for. + * @param {MessageReceiver} handler + * The handler to dispatch to. Must be an object with the following + * properties: + * + * receiveMessage: + * A method which is called for each message received by the + * listener. The method takes one argument, an object, with the + * following properties: + * + * messageName: + * The internal message name, as passed to `sendMessage`. + * + * target: + * The message manager which received this message. + * + * channelId: + * The internal ID of the transaction, used to map responses to + * the original sender. + * + * sender: + * An object describing the sender, as passed to `sendMessage`. + * + * recipient: + * An object describing the recipient, as passed to + * `sendMessage`. + * + * data: + * The contents of the message, as passed to `sendMessage`. + * + * The method may return any structured-clone-compatible + * object, which will be returned as a response to the message + * sender. It may also instead return a `Promise`, the + * resolution or rejection value of which will likewise be + * returned to the message sender. + * + * messageFilterStrict: + * An object containing arbitrary properties on which to filter + * received messages. Messages will only be dispatched to this + * object if the `recipient` object passed to `sendMessage` + * matches this filter, as determined by `matchesFilter` with + * `strict=true`. + * + * messageFilterPermissive: + * An object containing arbitrary properties on which to filter + * received messages. Messages will only be dispatched to this + * object if the `recipient` object passed to `sendMessage` + * matches this filter, as determined by `matchesFilter` with + * `strict=false`. + * + * filterMessage: + * An optional function that prevents the handler from handling a + * message by returning `false`. See `getHandlers` for the parameters. + */ + addListener(targets, messageName, handler) { + for (let target of [].concat(targets)) { + this.messageManagers.get(target).addHandler(messageName, handler); + } + }, + + /** + * Removes a message listener from the given message manager. + * + * @param {nsIMessageListenerManager|Array} targets + * The message managers on which to stop listening. + * @param {string|number} messageName + * The name of the message to stop listening for. + * @param {MessageReceiver} handler + * The handler to stop dispatching to. + */ + removeListener(targets, messageName, handler) { + for (let target of [].concat(targets)) { + if (this.messageManagers.has(target)) { + this.messageManagers.get(target).removeHandler(messageName, handler); + } + } + }, + + /** + * Sends a message via the given message manager. Returns a promise which + * resolves or rejects with the return value of the message receiver. + * + * The promise also rejects if there is no matching listener, or the other + * side of the message manager disconnects before the response is received. + * + * @param {nsIMessageSender} target + * The message manager on which to send the message. + * @param {string} messageName + * The name of the message to send, as passed to `addListener`. + * @param {object} data + * A structured-clone-compatible object to send to the message + * recipient. + * @param {object} [options] + * An object containing any of the following properties: + * @param {object} [options.recipient] + * A structured-clone-compatible object to identify the message + * recipient. The object must match the `messageFilterStrict` and + * `messageFilterPermissive` filters defined by recipients in order + * for the message to be received. + * @param {object} [options.sender] + * A structured-clone-compatible object to identify the message + * sender. This object may also be used to avoid delivering the + * message to the sender, and as a filter to prematurely + * abort responses when the sender is being destroyed. + * @see `abortResponses`. + * @param {integer} [options.responseType=RESPONSE_SINGLE] + * Specifies the type of response expected. See the `RESPONSE_*` + * contents for details. + * @returns {Promise} + */ + sendMessage(target, messageName, data, options = {}) { + let sender = options.sender || {}; + let recipient = options.recipient || {}; + let responseType = options.responseType || this.RESPONSE_SINGLE; + + let channelId = ExtensionUtils.getUniqueId(); + let message = {messageName, channelId, sender, recipient, data, responseType}; + + if (responseType == this.RESPONSE_NONE) { + try { + target.sendAsyncMessage(MESSAGE_MESSAGE, message); + } catch (e) { + // Caller is not expecting a reply, so dump the error to the console. + Cu.reportError(e); + return Promise.reject(e); + } + return Promise.resolve(); // Not expecting any reply. + } + + let deferred = PromiseUtils.defer(); + deferred.sender = recipient; + deferred.messageManager = target; + + this._addPendingResponse(deferred); + + // The channel ID is used as the message name when routing responses. + // Add a message listener to the response broker, and remove it once + // we've gotten (or canceled) a response. + let broker = this.responseManagers.get(target); + broker.addHandler(channelId, deferred); + + let cleanup = () => { + broker.removeHandler(channelId, deferred); + }; + deferred.promise.then(cleanup, cleanup); + + try { + target.sendAsyncMessage(MESSAGE_MESSAGE, message); + } catch (e) { + deferred.reject(e); + } + return deferred.promise; + }, + + _callHandlers(handlers, data) { + let responseType = data.responseType; + + // At least one handler is required for all response types but + // RESPONSE_ALL. + if (handlers.length == 0 && responseType != this.RESPONSE_ALL) { + return Promise.reject({result: MessageChannel.RESULT_NO_HANDLER, + message: "No matching message handler"}); + } + + if (responseType == this.RESPONSE_SINGLE) { + if (handlers.length > 1) { + return Promise.reject({result: MessageChannel.RESULT_MULTIPLE_HANDLERS, + message: `Multiple matching handlers for ${data.messageName}`}); + } + + // Note: We use `new Promise` rather than `Promise.resolve` here + // so that errors from the handler are trapped and converted into + // rejected promises. + return new Promise(resolve => { + resolve(handlers[0].receiveMessage(data)); + }); + } + + let responses = handlers.map(handler => { + try { + return handler.receiveMessage(data); + } catch (e) { + return Promise.reject(e); + } + }); + responses = responses.filter(response => response !== undefined); + + switch (responseType) { + case this.RESPONSE_FIRST: + if (responses.length == 0) { + return Promise.reject({result: MessageChannel.RESULT_NO_RESPONSE, + message: "No handler returned a response"}); + } + + return Promise.race(responses); + + case this.RESPONSE_ALL: + return Promise.all(responses); + } + return Promise.reject({message: "Invalid response type"}); + }, + + /** + * Handles dispatching message callbacks from the message brokers to their + * appropriate `MessageReceivers`, and routing the responses back to the + * original senders. + * + * Each handler object is a `MessageReceiver` object as passed to + * `addListener`. + * + * @param {Array} handlers + * @param {object} data + * @param {nsIMessageSender|{messageManager:nsIMessageSender}} data.target + */ + _handleMessage(handlers, data) { + if (data.responseType == this.RESPONSE_NONE) { + handlers.forEach(handler => { + // The sender expects no reply, so dump any errors to the console. + new Promise(resolve => { + resolve(handler.receiveMessage(data)); + }).catch(e => { + Cu.reportError(e.stack ? `${e}\n${e.stack}` : e.message || e); + }); + }); + // Note: Unhandled messages are silently dropped. + return; + } + + let target = new MessageManagerProxy(data.target); + + let deferred = { + sender: data.sender, + messageManager: target, + }; + deferred.promise = new Promise((resolve, reject) => { + deferred.reject = reject; + + this._callHandlers(handlers, data).then(resolve, reject); + }).then( + value => { + let response = { + result: this.RESULT_SUCCESS, + messageName: data.channelId, + recipient: {}, + value, + }; + + target.sendAsyncMessage(MESSAGE_RESPONSE, response); + }, + error => { + let response = { + result: this.RESULT_ERROR, + messageName: data.channelId, + recipient: {}, + error: {}, + }; + + if (error && typeof(error) == "object") { + if (error.result) { + response.result = error.result; + } + // Error objects are not structured-clonable, so just copy + // over the important properties. + for (let key of ["fileName", "filename", "lineNumber", + "columnNumber", "message", "stack", "result"]) { + if (key in error) { + response.error[key] = error[key]; + } + } + } + + target.sendAsyncMessage(MESSAGE_RESPONSE, response); + }).catch(e => { + Cu.reportError(e); + }).then(() => { + target.dispose(); + }); + + this._addPendingResponse(deferred); + }, + + /** + * Handles message callbacks from the response brokers. + * + * Each handler object is a deferred object created by `sendMessage`, and + * should be resolved or rejected based on the contents of the response. + * + * @param {Array} handlers + * @param {object} data + * @param {nsIMessageSender|{messageManager:nsIMessageSender}} data.target + */ + _handleResponse(handlers, data) { + // If we have an error at this point, we have handler to report it to, + // so just log it. + if (handlers.length == 0) { + Cu.reportError(`No matching message response handler for ${data.messageName}`); + } else if (handlers.length > 1) { + Cu.reportError(`Multiple matching response handlers for ${data.messageName}`); + } else if (data.result === this.RESULT_SUCCESS) { + handlers[0].resolve(data.value); + } else { + handlers[0].reject(data.error); + } + }, + + /** + * Adds a pending response to the the `pendingResponses` list. + * + * The response object must be a deferred promise with the following + * properties: + * + * promise: + * The promise object which resolves or rejects when the response + * is no longer pending. + * + * reject: + * A function which, when called, causes the `promise` object to be + * rejected. + * + * sender: + * A sender object, as passed to `sendMessage. + * + * messageManager: + * The message manager the response will be sent or received on. + * + * When the promise resolves or rejects, it will be removed from the + * list. + * + * These values are used to clear pending responses when execution + * contexts are destroyed. + * + * @param {Deferred} deferred + */ + _addPendingResponse(deferred) { + let cleanup = () => { + this.pendingResponses.delete(deferred); + }; + this.pendingResponses.add(deferred); + deferred.promise.then(cleanup, cleanup); + }, + + /** + * Aborts any pending message responses to senders matching the given + * filter. + * + * @param {object} sender + * The object on which to filter senders, as determined by + * `matchesFilter`. + * @param {object} [reason] + * An optional object describing the reason the response was aborted. + * Will be passed to the promise rejection handler of all aborted + * responses. + */ + abortResponses(sender, reason = this.REASON_DISCONNECTED) { + for (let response of this.pendingResponses) { + if (this.matchesFilter(sender, response.sender)) { + response.reject(reason); + } + } + }, + + /** + * Aborts any pending message responses to the broker for the given + * message manager. + * + * @param {nsIMessageListenerManager} target + * The message manager for which to abort brokers. + * @param {object} reason + * An object describing the reason the responses were aborted. + * Will be passed to the promise rejection handler of all aborted + * responses. + */ + abortMessageManager(target, reason) { + for (let response of this.pendingResponses) { + if (MessageManagerProxy.matches(response.messageManager, target)) { + response.reject(reason); + } + } + }, + + observe(subject, topic, data) { + switch (topic) { + case "message-manager-close": + case "message-manager-disconnect": + try { + if (this.responseManagers.has(subject)) { + this.abortMessageManager(subject, this.REASON_DISCONNECTED); + } + } finally { + this.responseManagers.delete(subject); + this.messageManagers.delete(subject); + } + break; + } + }, +}; + +MessageChannel.init(); diff --git a/toolkit/components/webextensions/NativeMessaging.jsm b/toolkit/components/webextensions/NativeMessaging.jsm new file mode 100644 index 000000000..3d8658a3f --- /dev/null +++ b/toolkit/components/webextensions/NativeMessaging.jsm @@ -0,0 +1,443 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["HostManifestManager", "NativeApp"]; +/* globals NativeApp */ + +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {}); + +XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", + "resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown", + "resource://gre/modules/AsyncShutdown.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChild", + "resource://gre/modules/ExtensionChild.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Subprocess", + "resource://gre/modules/Subprocess.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout", + "resource://gre/modules/Timer.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "setTimeout", + "resource://gre/modules/Timer.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry", + "resource://gre/modules/WindowsRegistry.jsm"); + +const HOST_MANIFEST_SCHEMA = "chrome://extensions/content/schemas/native_host_manifest.json"; +const VALID_APPLICATION = /^\w+(\.\w+)*$/; + +// For a graceful shutdown (i.e., when the extension is unloaded or when it +// explicitly calls disconnect() on a native port), how long we give the native +// application to exit before we start trying to kill it. (in milliseconds) +const GRACEFUL_SHUTDOWN_TIME = 3000; + +// Hard limits on maximum message size that can be read/written +// These are defined in the native messaging documentation, note that +// the write limit is imposed by the "wire protocol" in which message +// boundaries are defined by preceding each message with its length as +// 4-byte unsigned integer so this is the largest value that can be +// represented. Good luck generating a serialized message that large, +// the practical write limit is likely to be dictated by available memory. +const MAX_READ = 1024 * 1024; +const MAX_WRITE = 0xffffffff; + +// Preferences that can lower the message size limits above, +// used for testing the limits. +const PREF_MAX_READ = "webextensions.native-messaging.max-input-message-bytes"; +const PREF_MAX_WRITE = "webextensions.native-messaging.max-output-message-bytes"; + +const REGPATH = "Software\\Mozilla\\NativeMessagingHosts"; + +this.HostManifestManager = { + _initializePromise: null, + _lookup: null, + + init() { + if (!this._initializePromise) { + let platform = AppConstants.platform; + if (platform == "win") { + this._lookup = this._winLookup; + } else if (platform == "macosx" || platform == "linux") { + let dirs = [ + Services.dirsvc.get("XREUserNativeMessaging", Ci.nsIFile).path, + Services.dirsvc.get("XRESysNativeMessaging", Ci.nsIFile).path, + ]; + this._lookup = (application, context) => this._tryPaths(application, dirs, context); + } else { + throw new Error(`Native messaging is not supported on ${AppConstants.platform}`); + } + this._initializePromise = Schemas.load(HOST_MANIFEST_SCHEMA); + } + return this._initializePromise; + }, + + _winLookup(application, context) { + const REGISTRY = Ci.nsIWindowsRegKey; + let regPath = `${REGPATH}\\${application}`; + let path = WindowsRegistry.readRegKey(REGISTRY.ROOT_KEY_CURRENT_USER, + regPath, "", REGISTRY.WOW64_64); + if (!path) { + path = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + regPath, "", REGISTRY.WOW64_64); + } + if (!path) { + return null; + } + return this._tryPath(path, application, context) + .then(manifest => manifest ? {path, manifest} : null); + }, + + _tryPath(path, application, context) { + return Promise.resolve() + .then(() => OS.File.read(path, {encoding: "utf-8"})) + .then(data => { + let manifest; + try { + manifest = JSON.parse(data); + } catch (ex) { + let msg = `Error parsing native host manifest ${path}: ${ex.message}`; + Cu.reportError(msg); + return null; + } + + let normalized = Schemas.normalize(manifest, "manifest.NativeHostManifest", context); + if (normalized.error) { + Cu.reportError(normalized.error); + return null; + } + manifest = normalized.value; + if (manifest.name != application) { + let msg = `Native host manifest ${path} has name property ${manifest.name} (expected ${application})`; + Cu.reportError(msg); + return null; + } + return normalized.value; + }).catch(ex => { + if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) { + return null; + } + throw ex; + }); + }, + + _tryPaths: Task.async(function* (application, dirs, context) { + for (let dir of dirs) { + let path = OS.Path.join(dir, `${application}.json`); + let manifest = yield this._tryPath(path, application, context); + if (manifest) { + return {path, manifest}; + } + } + return null; + }), + + /** + * Search for a valid native host manifest for the given application name. + * The directories searched and rules for manifest validation are all + * detailed in the native messaging documentation. + * + * @param {string} application The name of the applciation to search for. + * @param {object} context A context object as expected by Schemas.normalize. + * @returns {object} The contents of the validated manifest, or null if + * no valid manifest can be found for this application. + */ + lookupApplication(application, context) { + if (!VALID_APPLICATION.test(application)) { + throw new Error(`Invalid application "${application}"`); + } + return this.init().then(() => this._lookup(application, context)); + }, +}; + +this.NativeApp = class extends EventEmitter { + /** + * @param {BaseContext} context The context that initiated the native app. + * @param {string} application The identifier of the native app. + */ + constructor(context, application) { + super(); + + this.context = context; + this.name = application; + + // We want a close() notification when the window is destroyed. + this.context.callOnClose(this); + + this.proc = null; + this.readPromise = null; + this.sendQueue = []; + this.writePromise = null; + this.sentDisconnect = false; + + this.startupPromise = HostManifestManager.lookupApplication(application, context) + .then(hostInfo => { + // Put the two errors together to not leak information about whether a native + // application is installed to addons that do not have the right permission. + if (!hostInfo || !hostInfo.manifest.allowed_extensions.includes(context.extension.id)) { + throw new context.cloneScope.Error(`This extension does not have permission to use native application ${application} (or the application is not installed)`); + } + + let command = hostInfo.manifest.path; + if (AppConstants.platform == "win") { + // OS.Path.join() ignores anything before the last absolute path + // it sees, so if command is already absolute, it remains unchanged + // here. If it is relative, we get the proper absolute path here. + command = OS.Path.join(OS.Path.dirname(hostInfo.path), command); + } + + let subprocessOpts = { + command: command, + arguments: [hostInfo.path], + workdir: OS.Path.dirname(command), + stderr: "pipe", + }; + return Subprocess.call(subprocessOpts); + }).then(proc => { + this.startupPromise = null; + this.proc = proc; + this._startRead(); + this._startWrite(); + this._startStderrRead(); + }).catch(err => { + this.startupPromise = null; + Cu.reportError(err instanceof Error ? err : err.message); + this._cleanup(err); + }); + } + + /** + * Open a connection to a native messaging host. + * + * @param {BaseContext} context The context associated with the port. + * @param {nsIMessageSender} messageManager The message manager used to send + * and receive messages from the port's creator. + * @param {string} portId A unique internal ID that identifies the port. + * @param {object} sender The object describing the creator of the connection + * request. + * @param {string} application The name of the native messaging host. + */ + static onConnectNative(context, messageManager, portId, sender, application) { + let app = new NativeApp(context, application); + let port = new ExtensionChild.Port(context, messageManager, [Services.mm], "", portId, sender, sender); + app.once("disconnect", (what, err) => port.disconnect(err)); + + /* eslint-disable mozilla/balanced-listeners */ + app.on("message", (what, msg) => port.postMessage(msg)); + /* eslint-enable mozilla/balanced-listeners */ + + port.registerOnMessage(msg => app.send(msg)); + port.registerOnDisconnect(msg => app.close()); + } + + /** + * @param {BaseContext} context The scope from where `message` originates. + * @param {*} message A message from the extension, meant for a native app. + * @returns {ArrayBuffer} An ArrayBuffer that can be sent to the native app. + */ + static encodeMessage(context, message) { + message = context.jsonStringify(message); + let buffer = new TextEncoder().encode(message).buffer; + if (buffer.byteLength > NativeApp.maxWrite) { + throw new context.cloneScope.Error("Write too big"); + } + return buffer; + } + + // A port is definitely "alive" if this.proc is non-null. But we have + // to provide a live port object immediately when connecting so we also + // need to consider a port alive if proc is null but the startupPromise + // is still pending. + get _isDisconnected() { + return (!this.proc && !this.startupPromise); + } + + _startRead() { + if (this.readPromise) { + throw new Error("Entered _startRead() while readPromise is non-null"); + } + this.readPromise = this.proc.stdout.readUint32() + .then(len => { + if (len > NativeApp.maxRead) { + throw new this.context.cloneScope.Error(`Native application tried to send a message of ${len} bytes, which exceeds the limit of ${NativeApp.maxRead} bytes.`); + } + return this.proc.stdout.readJSON(len); + }).then(msg => { + this.emit("message", msg); + this.readPromise = null; + this._startRead(); + }).catch(err => { + if (err.errorCode != Subprocess.ERROR_END_OF_FILE) { + Cu.reportError(err instanceof Error ? err : err.message); + } + this._cleanup(err); + }); + } + + _startWrite() { + if (this.sendQueue.length == 0) { + return; + } + + if (this.writePromise) { + throw new Error("Entered _startWrite() while writePromise is non-null"); + } + + let buffer = this.sendQueue.shift(); + let uintArray = Uint32Array.of(buffer.byteLength); + + this.writePromise = Promise.all([ + this.proc.stdin.write(uintArray.buffer), + this.proc.stdin.write(buffer), + ]).then(() => { + this.writePromise = null; + this._startWrite(); + }).catch(err => { + Cu.reportError(err.message); + this._cleanup(err); + }); + } + + _startStderrRead() { + let proc = this.proc; + let app = this.name; + Task.spawn(function* () { + let partial = ""; + while (true) { + let data = yield proc.stderr.readString(); + if (data.length == 0) { + // We have hit EOF, just stop reading + if (partial) { + Services.console.logStringMessage(`stderr output from native app ${app}: ${partial}`); + } + break; + } + + let lines = data.split(/\r?\n/); + lines[0] = partial + lines[0]; + partial = lines.pop(); + + for (let line of lines) { + Services.console.logStringMessage(`stderr output from native app ${app}: ${line}`); + } + } + }); + } + + send(msg) { + if (this._isDisconnected) { + throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port"); + } + if (Cu.getClassName(msg, true) != "ArrayBuffer") { + // This error cannot be triggered by extensions; it indicates an error in + // our implementation. + throw new Error("The message to the native messaging host is not an ArrayBuffer"); + } + + let buffer = msg; + + if (buffer.byteLength > NativeApp.maxWrite) { + throw new this.context.cloneScope.Error("Write too big"); + } + + this.sendQueue.push(buffer); + if (!this.startupPromise && !this.writePromise) { + this._startWrite(); + } + } + + // Shut down the native application and also signal to the extension + // that the connect has been disconnected. + _cleanup(err) { + this.context.forgetOnClose(this); + + let doCleanup = () => { + // Set a timer to kill the process gracefully after one timeout + // interval and kill it forcefully after two intervals. + let timer = setTimeout(() => { + this.proc.kill(GRACEFUL_SHUTDOWN_TIME); + }, GRACEFUL_SHUTDOWN_TIME); + + let promise = Promise.all([ + this.proc.stdin.close() + .catch(err => { + if (err.errorCode != Subprocess.ERROR_END_OF_FILE) { + throw err; + } + }), + this.proc.wait(), + ]).then(() => { + this.proc = null; + clearTimeout(timer); + }); + + AsyncShutdown.profileBeforeChange.addBlocker( + `Native Messaging: Wait for application ${this.name} to exit`, + promise); + + promise.then(() => { + AsyncShutdown.profileBeforeChange.removeBlocker(promise); + }); + + return promise; + }; + + if (this.proc) { + doCleanup(); + } else if (this.startupPromise) { + this.startupPromise.then(doCleanup); + } + + if (!this.sentDisconnect) { + this.sentDisconnect = true; + if (err && err.errorCode == Subprocess.ERROR_END_OF_FILE) { + err = null; + } + this.emit("disconnect", err); + } + } + + // Called from Context when the extension is shut down. + close() { + this._cleanup(); + } + + sendMessage(msg) { + let responsePromise = new Promise((resolve, reject) => { + this.once("message", (what, msg) => { resolve(msg); }); + this.once("disconnect", (what, err) => { reject(err); }); + }); + + let result = this.startupPromise.then(() => { + this.send(msg); + return responsePromise; + }); + + result.then(() => { + this._cleanup(); + }, () => { + // Prevent the response promise from being reported as an + // unchecked rejection if the startup promise fails. + responsePromise.catch(() => {}); + + this._cleanup(); + }); + + return result; + } +}; + +XPCOMUtils.defineLazyPreferenceGetter(NativeApp, "maxRead", PREF_MAX_READ, MAX_READ); +XPCOMUtils.defineLazyPreferenceGetter(NativeApp, "maxWrite", PREF_MAX_WRITE, MAX_WRITE); diff --git a/toolkit/components/webextensions/Schemas.jsm b/toolkit/components/webextensions/Schemas.jsm new file mode 100644 index 000000000..159211c79 --- /dev/null +++ b/toolkit/components/webextensions/Schemas.jsm @@ -0,0 +1,2143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const Cr = Components.results; + +const global = this; + +Cu.importGlobalProperties(["URL"]); + +Cu.import("resource://gre/modules/NetUtil.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +var { + DefaultMap, + instanceOf, +} = ExtensionUtils; + +class DeepMap extends DefaultMap { + constructor() { + super(() => new DeepMap()); + } + + getPath(...keys) { + return keys.reduce((map, prop) => map.get(prop), this); + } +} + +XPCOMUtils.defineLazyServiceGetter(this, "contentPolicyService", + "@mozilla.org/addons/content-policy;1", + "nsIAddonContentPolicy"); + +this.EXPORTED_SYMBOLS = ["Schemas"]; + +/* globals Schemas, URL */ + +function readJSON(url) { + return new Promise((resolve, reject) => { + NetUtil.asyncFetch({uri: url, loadUsingSystemPrincipal: true}, (inputStream, status) => { + if (!Components.isSuccessCode(status)) { + // Convert status code to a string + let e = Components.Exception("", status); + reject(new Error(`Error while loading '${url}' (${e.name})`)); + return; + } + try { + let text = NetUtil.readInputStreamToString(inputStream, inputStream.available()); + + // Chrome JSON files include a license comment that we need to + // strip off for this to be valid JSON. As a hack, we just + // look for the first '[' character, which signals the start + // of the JSON content. + let index = text.indexOf("["); + text = text.slice(index); + + resolve(JSON.parse(text)); + } catch (e) { + reject(e); + } + }); + }); +} + +/** + * Defines a lazy getter for the given property on the given object. Any + * security wrappers are waived on the object before the property is + * defined, and the getter and setter methods are wrapped for the target + * scope. + * + * The given getter function is guaranteed to be called only once, even + * if the target scope retrieves the wrapped getter from the property + * descriptor and calls it directly. + * + * @param {object} object + * The object on which to define the getter. + * @param {string|Symbol} prop + * The property name for which to define the getter. + * @param {function} getter + * The function to call in order to generate the final property + * value. + */ +function exportLazyGetter(object, prop, getter) { + object = Cu.waiveXrays(object); + + let redefine = value => { + if (value === undefined) { + delete object[prop]; + } else { + Object.defineProperty(object, prop, { + enumerable: true, + configurable: true, + writable: true, + value, + }); + } + + getter = null; + + return value; + }; + + Object.defineProperty(object, prop, { + enumerable: true, + configurable: true, + + get: Cu.exportFunction(function() { + return redefine(getter.call(this)); + }, object), + + set: Cu.exportFunction(value => { + redefine(value); + }, object), + }); +} + +const POSTPROCESSORS = { + convertImageDataToURL(imageData, context) { + let document = context.cloneScope.document; + let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + canvas.width = imageData.width; + canvas.height = imageData.height; + canvas.getContext("2d").putImageData(imageData, 0, 0); + + return canvas.toDataURL("image/png"); + }, +}; + +// Parses a regular expression, with support for the Python extended +// syntax that allows setting flags by including the string (?im) +function parsePattern(pattern) { + let flags = ""; + let match = /^\(\?([im]*)\)(.*)/.exec(pattern); + if (match) { + [, flags, pattern] = match; + } + return new RegExp(pattern, flags); +} + +function getValueBaseType(value) { + let t = typeof(value); + if (t == "object") { + if (value === null) { + return "null"; + } else if (Array.isArray(value)) { + return "array"; + } else if (Object.prototype.toString.call(value) == "[object ArrayBuffer]") { + return "binary"; + } + } else if (t == "number") { + if (value % 1 == 0) { + return "integer"; + } + } + return t; +} + +// Methods of Context that are used by Schemas.normalize. These methods can be +// overridden at the construction of Context. +const CONTEXT_FOR_VALIDATION = [ + "checkLoadURL", + "hasPermission", + "logError", +]; + +// Methods of Context that are used by Schemas.inject. +// Callers of Schemas.inject should implement all of these methods. +const CONTEXT_FOR_INJECTION = [ + ...CONTEXT_FOR_VALIDATION, + "shouldInject", + "getImplementation", +]; + +/** + * A context for schema validation and error reporting. This class is only used + * internally within Schemas. + */ +class Context { + /** + * @param {object} params Provides the implementation of this class. + * @param {Array} overridableMethods + */ + constructor(params, overridableMethods = CONTEXT_FOR_VALIDATION) { + this.params = params; + + this.path = []; + this.preprocessors = { + localize(value, context) { + return value; + }, + }; + this.postprocessors = POSTPROCESSORS; + this.isChromeCompat = false; + + this.currentChoices = new Set(); + this.choicePathIndex = 0; + + for (let method of overridableMethods) { + if (method in params) { + this[method] = params[method].bind(params); + } + } + + let props = ["preprocessors", "isChromeCompat"]; + for (let prop of props) { + if (prop in params) { + if (prop in this && typeof this[prop] == "object") { + Object.assign(this[prop], params[prop]); + } else { + this[prop] = params[prop]; + } + } + } + } + + get choicePath() { + let path = this.path.slice(this.choicePathIndex); + return path.join("."); + } + + get cloneScope() { + return this.params.cloneScope; + } + + get url() { + return this.params.url; + } + + get principal() { + return this.params.principal || Services.scriptSecurityManager.createNullPrincipal({}); + } + + /** + * Checks whether `url` may be loaded by the extension in this context. + * + * @param {string} url The URL that the extension wished to load. + * @returns {boolean} Whether the context may load `url`. + */ + checkLoadURL(url) { + let ssm = Services.scriptSecurityManager; + try { + ssm.checkLoadURIStrWithPrincipal(this.principal, url, + ssm.DISALLOW_INHERIT_PRINCIPAL); + } catch (e) { + return false; + } + return true; + } + + /** + * Checks whether this context has the given permission. + * + * @param {string} permission + * The name of the permission to check. + * + * @returns {boolean} True if the context has the given permission. + */ + hasPermission(permission) { + return false; + } + + /** + * Returns an error result object with the given message, for return + * by Type normalization functions. + * + * If the context has a `currentTarget` value, this is prepended to + * the message to indicate the location of the error. + * + * @param {string} errorMessage + * The error message which will be displayed when this is the + * only possible matching schema. + * @param {string} choicesMessage + * The message describing the valid what constitutes a valid + * value for this schema, which will be displayed when multiple + * schema choices are available and none match. + * + * A caller may pass `null` to prevent a choice from being + * added, but this should *only* be done from code processing a + * choices type. + * @returns {object} + */ + error(errorMessage, choicesMessage = undefined) { + if (choicesMessage !== null) { + let {choicePath} = this; + if (choicePath) { + choicesMessage = `.${choicePath} must ${choicesMessage}`; + } + + this.currentChoices.add(choicesMessage); + } + + if (this.currentTarget) { + return {error: `Error processing ${this.currentTarget}: ${errorMessage}`}; + } + return {error: errorMessage}; + } + + /** + * Creates an `Error` object belonging to the current unprivileged + * scope. If there is no unprivileged scope associated with this + * context, the message is returned as a string. + * + * If the context has a `currentTarget` value, this is prepended to + * the message, in the same way as for the `error` method. + * + * @param {string} message + * @returns {Error} + */ + makeError(message) { + let {error} = this.error(message); + if (this.cloneScope) { + return new this.cloneScope.Error(error); + } + return error; + } + + /** + * Logs the given error to the console. May be overridden to enable + * custom logging. + * + * @param {Error|string} error + */ + logError(error) { + Cu.reportError(error); + } + + /** + * Returns the name of the value currently being normalized. For a + * nested object, this is usually approximately equivalent to the + * JavaScript property accessor for that property. Given: + * + * { foo: { bar: [{ baz: x }] } } + * + * When processing the value for `x`, the currentTarget is + * 'foo.bar.0.baz' + */ + get currentTarget() { + return this.path.join("."); + } + + /** + * Executes the given callback, and returns an array of choice strings + * passed to {@see #error} during its execution. + * + * @param {function} callback + * @returns {object} + * An object with a `result` property containing the return + * value of the callback, and a `choice` property containing + * an array of choices. + */ + withChoices(callback) { + let {currentChoices, choicePathIndex} = this; + + let choices = new Set(); + this.currentChoices = choices; + this.choicePathIndex = this.path.length; + + try { + let result = callback(); + + return {result, choices: Array.from(choices)}; + } finally { + this.currentChoices = currentChoices; + this.choicePathIndex = choicePathIndex; + + choices = Array.from(choices); + if (choices.length == 1) { + currentChoices.add(choices[0]); + } else if (choices.length) { + let n = choices.length - 1; + choices[n] = `or ${choices[n]}`; + + this.error(null, `must either [${choices.join(", ")}]`); + } + } + } + + /** + * Appends the given component to the `currentTarget` path to indicate + * that it is being processed, calls the given callback function, and + * then restores the original path. + * + * This is used to identify the path of the property being processed + * when reporting type errors. + * + * @param {string} component + * @param {function} callback + * @returns {*} + */ + withPath(component, callback) { + this.path.push(component); + try { + return callback(); + } finally { + this.path.pop(); + } + } +} + +/** + * Holds methods that run the actual implementation of the extension APIs. These + * methods are only called if the extension API invocation matches the signature + * as defined in the schema. Otherwise an error is reported to the context. + */ +class InjectionContext extends Context { + constructor(params) { + super(params, CONTEXT_FOR_INJECTION); + } + + /** + * Check whether the API should be injected. + * + * @abstract + * @param {string} namespace The namespace of the API. This may contain dots, + * e.g. in the case of "devtools.inspectedWindow". + * @param {string} [name] The name of the property in the namespace. + * `null` if we are checking whether the namespace should be injected. + * @param {Array} allowedContexts A list of additional contexts in which + * this API should be available. May include any of: + * "main" - The main chrome browser process. + * "addon" - An addon process. + * "content" - A content process. + * @returns {boolean} Whether the API should be injected. + */ + shouldInject(namespace, name, allowedContexts) { + throw new Error("Not implemented"); + } + + /** + * Generate the implementation for `namespace`.`name`. + * + * @abstract + * @param {string} namespace The full path to the namespace of the API, minus + * the name of the method or property. E.g. "storage.local". + * @param {string} name The name of the method, property or event. + * @returns {SchemaAPIInterface} The implementation of the API. + */ + getImplementation(namespace, name) { + throw new Error("Not implemented"); + } +} + +/** + * The methods in this singleton represent the "format" specifier for + * JSON Schema string types. + * + * Each method either returns a normalized version of the original + * value, or throws an error if the value is not valid for the given + * format. + */ +const FORMATS = { + url(string, context) { + let url = new URL(string).href; + + if (!context.checkLoadURL(url)) { + throw new Error(`Access denied for URL ${url}`); + } + return url; + }, + + relativeUrl(string, context) { + if (!context.url) { + // If there's no context URL, return relative URLs unresolved, and + // skip security checks for them. + try { + new URL(string); + } catch (e) { + return string; + } + } + + let url = new URL(string, context.url).href; + + if (!context.checkLoadURL(url)) { + throw new Error(`Access denied for URL ${url}`); + } + return url; + }, + + strictRelativeUrl(string, context) { + // Do not accept a string which resolves as an absolute URL, or any + // protocol-relative URL. + if (!string.startsWith("//")) { + try { + new URL(string); + } catch (e) { + return FORMATS.relativeUrl(string, context); + } + } + + throw new SyntaxError(`String ${JSON.stringify(string)} must be a relative URL`); + }, + + contentSecurityPolicy(string, context) { + let error = contentPolicyService.validateAddonCSP(string); + if (error != null) { + throw new SyntaxError(error); + } + return string; + }, + + date(string, context) { + // A valid ISO 8601 timestamp. + const PATTERN = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|([-+]\d{2}:?\d{2})))?$/; + if (!PATTERN.test(string)) { + throw new Error(`Invalid date string ${string}`); + } + // Our pattern just checks the format, we could still have invalid + // values (e.g., month=99 or month=02 and day=31). Let the Date + // constructor do the dirty work of validating. + if (isNaN(new Date(string))) { + throw new Error(`Invalid date string ${string}`); + } + return string; + }, +}; + +// Schema files contain namespaces, and each namespace contains types, +// properties, functions, and events. An Entry is a base class for +// types, properties, functions, and events. +class Entry { + constructor(schema = {}) { + /** + * If set to any value which evaluates as true, this entry is + * deprecated, and any access to it will result in a deprecation + * warning being logged to the browser console. + * + * If the value is a string, it will be appended to the deprecation + * message. If it contains the substring "${value}", it will be + * replaced with a string representation of the value being + * processed. + * + * If the value is any other truthy value, a generic deprecation + * message will be emitted. + */ + this.deprecated = false; + if ("deprecated" in schema) { + this.deprecated = schema.deprecated; + } + + /** + * @property {string} [preprocessor] + * If set to a string value, and a preprocessor of the same is + * defined in the validation context, it will be applied to this + * value prior to any normalization. + */ + this.preprocessor = schema.preprocess || null; + + /** + * @property {string} [postprocessor] + * If set to a string value, and a postprocessor of the same is + * defined in the validation context, it will be applied to this + * value after any normalization. + */ + this.postprocessor = schema.postprocess || null; + + /** + * @property {Array} allowedContexts A list of allowed contexts + * to consider before generating the API. + * These are not parsed by the schema, but passed to `shouldInject`. + */ + this.allowedContexts = schema.allowedContexts || []; + } + + /** + * Preprocess the given value with the preprocessor declared in + * `preprocessor`. + * + * @param {*} value + * @param {Context} context + * @returns {*} + */ + preprocess(value, context) { + if (this.preprocessor) { + return context.preprocessors[this.preprocessor](value, context); + } + return value; + } + + /** + * Postprocess the given result with the postprocessor declared in + * `postprocessor`. + * + * @param {object} result + * @param {Context} context + * @returns {object} + */ + postprocess(result, context) { + if (result.error || !this.postprocessor) { + return result; + } + + let value = context.postprocessors[this.postprocessor](result.value, context); + return {value}; + } + + /** + * Logs a deprecation warning for this entry, based on the value of + * its `deprecated` property. + * + * @param {Context} context + * @param {value} [value] + */ + logDeprecation(context, value = null) { + let message = "This property is deprecated"; + if (typeof(this.deprecated) == "string") { + message = this.deprecated; + if (message.includes("${value}")) { + try { + value = JSON.stringify(value); + } catch (e) { + value = String(value); + } + message = message.replace(/\$\{value\}/g, () => value); + } + } + + context.logError(context.makeError(message)); + } + + /** + * Checks whether the entry is deprecated and, if so, logs a + * deprecation message. + * + * @param {Context} context + * @param {value} [value] + */ + checkDeprecated(context, value = null) { + if (this.deprecated) { + this.logDeprecation(context, value); + } + } + + /** + * Injects JS values for the entry into the extension API + * namespace. The default implementation is to do nothing. + * `context` is used to call the actual implementation + * of a given function or event. + * + * @param {Array} path The API path, e.g. `["storage", "local"]`. + * @param {string} name The method name, e.g. "get". + * @param {object} dest The object where `path`.`name` should be stored. + * @param {InjectionContext} context + */ + inject(path, name, dest, context) { + } +} + +// Corresponds either to a type declared in the "types" section of the +// schema or else to any type object used throughout the schema. +class Type extends Entry { + /** + * @property {Array} EXTRA_PROPERTIES + * An array of extra properties which may be present for + * schemas of this type. + */ + static get EXTRA_PROPERTIES() { + return ["description", "deprecated", "preprocess", "postprocess", "allowedContexts"]; + } + + /** + * Parses the given schema object and returns an instance of this + * class which corresponds to its properties. + * + * @param {object} schema + * A JSON schema object which corresponds to a definition of + * this type. + * @param {Array} path + * The path to this schema object from the root schema, + * corresponding to the property names and array indices + * traversed during parsing in order to arrive at this schema + * object. + * @param {Array} [extraProperties] + * An array of extra property names which are valid for this + * schema in the current context. + * @returns {Type} + * An instance of this type which corresponds to the given + * schema object. + * @static + */ + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + return new this(schema); + } + + /** + * Checks that all of the properties present in the given schema + * object are valid properties for this type, and throws if invalid. + * + * @param {object} schema + * A JSON schema object. + * @param {Array} path + * The path to this schema object from the root schema, + * corresponding to the property names and array indices + * traversed during parsing in order to arrive at this schema + * object. + * @param {Array} [extra] + * An array of extra property names which are valid for this + * schema in the current context. + * @throws {Error} + * An error describing the first invalid property found in the + * schema object. + */ + static checkSchemaProperties(schema, path, extra = []) { + let allowedSet = new Set([...this.EXTRA_PROPERTIES, ...extra]); + + for (let prop of Object.keys(schema)) { + if (!allowedSet.has(prop)) { + throw new Error(`Internal error: Namespace ${path.join(".")} has invalid type property "${prop}" in type "${schema.id || JSON.stringify(schema)}"`); + } + } + } + + // Takes a value, checks that it has the correct type, and returns a + // "normalized" version of the value. The normalized version will + // include "nulls" in place of omitted optional properties. The + // result of this function is either {error: "Some type error"} or + // {value: }. + normalize(value, context) { + return context.error("invalid type"); + } + + // Unlike normalize, this function does a shallow check to see if + // |baseType| (one of the possible getValueBaseType results) is + // valid for this type. It returns true or false. It's used to fill + // in optional arguments to functions before actually type checking + + checkBaseType(baseType) { + return false; + } + + // Helper method that simply relies on checkBaseType to implement + // normalize. Subclasses can choose to use it or not. + normalizeBase(type, value, context) { + if (this.checkBaseType(getValueBaseType(value))) { + this.checkDeprecated(context, value); + return {value: this.preprocess(value, context)}; + } + + let choice; + if (/^[aeiou]/.test(type)) { + choice = `be an ${type} value`; + } else { + choice = `be a ${type} value`; + } + + return context.error(`Expected ${type} instead of ${JSON.stringify(value)}`, + choice); + } +} + +// Type that allows any value. +class AnyType extends Type { + normalize(value, context) { + this.checkDeprecated(context, value); + return this.postprocess({value}, context); + } + + checkBaseType(baseType) { + return true; + } +} + +// An untagged union type. +class ChoiceType extends Type { + static get EXTRA_PROPERTIES() { + return ["choices", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let choices = schema.choices.map(t => Schemas.parseSchema(t, path)); + return new this(schema, choices); + } + + constructor(schema, choices) { + super(schema); + this.choices = choices; + } + + extend(type) { + this.choices.push(...type.choices); + + return this; + } + + normalize(value, context) { + this.checkDeprecated(context, value); + + let error; + let {choices, result} = context.withChoices(() => { + for (let choice of this.choices) { + let r = choice.normalize(value, context); + if (!r.error) { + return r; + } + + error = r; + } + }); + + if (result) { + return result; + } + if (choices.length <= 1) { + return error; + } + + let n = choices.length - 1; + choices[n] = `or ${choices[n]}`; + + let message = `Value must either: ${choices.join(", ")}`; + + return context.error(message, null); + } + + checkBaseType(baseType) { + return this.choices.some(t => t.checkBaseType(baseType)); + } +} + +// This is a reference to another type--essentially a typedef. +class RefType extends Type { + static get EXTRA_PROPERTIES() { + return ["$ref", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let ref = schema.$ref; + let ns = path[0]; + if (ref.includes(".")) { + [ns, ref] = ref.split("."); + } + return new this(schema, ns, ref); + } + + // For a reference to a type named T declared in namespace NS, + // namespaceName will be NS and reference will be T. + constructor(schema, namespaceName, reference) { + super(schema); + this.namespaceName = namespaceName; + this.reference = reference; + } + + get targetType() { + let ns = Schemas.namespaces.get(this.namespaceName); + let type = ns.get(this.reference); + if (!type) { + throw new Error(`Internal error: Type ${this.reference} not found`); + } + return type; + } + + normalize(value, context) { + this.checkDeprecated(context, value); + return this.targetType.normalize(value, context); + } + + checkBaseType(baseType) { + return this.targetType.checkBaseType(baseType); + } +} + +class StringType extends Type { + static get EXTRA_PROPERTIES() { + return ["enum", "minLength", "maxLength", "pattern", "format", + ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let enumeration = schema.enum || null; + if (enumeration) { + // The "enum" property is either a list of strings that are + // valid values or else a list of {name, description} objects, + // where the .name values are the valid values. + enumeration = enumeration.map(e => { + if (typeof(e) == "object") { + return e.name; + } + return e; + }); + } + + let pattern = null; + if (schema.pattern) { + try { + pattern = parsePattern(schema.pattern); + } catch (e) { + throw new Error(`Internal error: Invalid pattern ${JSON.stringify(schema.pattern)}`); + } + } + + let format = null; + if (schema.format) { + if (!(schema.format in FORMATS)) { + throw new Error(`Internal error: Invalid string format ${schema.format}`); + } + format = FORMATS[schema.format]; + } + return new this(schema, enumeration, + schema.minLength || 0, + schema.maxLength || Infinity, + pattern, + format); + } + + constructor(schema, enumeration, minLength, maxLength, pattern, format) { + super(schema); + this.enumeration = enumeration; + this.minLength = minLength; + this.maxLength = maxLength; + this.pattern = pattern; + this.format = format; + } + + normalize(value, context) { + let r = this.normalizeBase("string", value, context); + if (r.error) { + return r; + } + value = r.value; + + if (this.enumeration) { + if (this.enumeration.includes(value)) { + return this.postprocess({value}, context); + } + + let choices = this.enumeration.map(JSON.stringify).join(", "); + + return context.error(`Invalid enumeration value ${JSON.stringify(value)}`, + `be one of [${choices}]`); + } + + if (value.length < this.minLength) { + return context.error(`String ${JSON.stringify(value)} is too short (must be ${this.minLength})`, + `be longer than ${this.minLength}`); + } + if (value.length > this.maxLength) { + return context.error(`String ${JSON.stringify(value)} is too long (must be ${this.maxLength})`, + `be shorter than ${this.maxLength}`); + } + + if (this.pattern && !this.pattern.test(value)) { + return context.error(`String ${JSON.stringify(value)} must match ${this.pattern}`, + `match the pattern ${this.pattern.toSource()}`); + } + + if (this.format) { + try { + r.value = this.format(r.value, context); + } catch (e) { + return context.error(String(e), `match the format "${this.format.name}"`); + } + } + + return r; + } + + checkBaseType(baseType) { + return baseType == "string"; + } + + inject(path, name, dest, context) { + if (this.enumeration) { + exportLazyGetter(dest, name, () => { + let obj = Cu.createObjectIn(dest); + for (let e of this.enumeration) { + obj[e.toUpperCase()] = e; + } + return obj; + }); + } + } +} + +let SubModuleType; +class ObjectType extends Type { + static get EXTRA_PROPERTIES() { + return ["properties", "patternProperties", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + if ("functions" in schema) { + return SubModuleType.parseSchema(schema, path, extraProperties); + } + + if (!("$extend" in schema)) { + // Only allow extending "properties" and "patternProperties". + extraProperties = ["additionalProperties", "isInstanceOf", ...extraProperties]; + } + this.checkSchemaProperties(schema, path, extraProperties); + + let parseProperty = (schema, extraProps = []) => { + return { + type: Schemas.parseSchema(schema, path, + ["unsupported", "onError", "permissions", ...extraProps]), + optional: schema.optional || false, + unsupported: schema.unsupported || false, + onError: schema.onError || null, + }; + }; + + // Parse explicit "properties" object. + let properties = Object.create(null); + for (let propName of Object.keys(schema.properties || {})) { + properties[propName] = parseProperty(schema.properties[propName], ["optional"]); + } + + // Parse regexp properties from "patternProperties" object. + let patternProperties = []; + for (let propName of Object.keys(schema.patternProperties || {})) { + let pattern; + try { + pattern = parsePattern(propName); + } catch (e) { + throw new Error(`Internal error: Invalid property pattern ${JSON.stringify(propName)}`); + } + + patternProperties.push({ + pattern, + type: parseProperty(schema.patternProperties[propName]), + }); + } + + // Parse "additionalProperties" schema. + let additionalProperties = null; + if (schema.additionalProperties) { + let type = schema.additionalProperties; + if (type === true) { + type = {"type": "any"}; + } + + additionalProperties = Schemas.parseSchema(type, path); + } + + return new this(schema, properties, additionalProperties, patternProperties, schema.isInstanceOf || null); + } + + constructor(schema, properties, additionalProperties, patternProperties, isInstanceOf) { + super(schema); + this.properties = properties; + this.additionalProperties = additionalProperties; + this.patternProperties = patternProperties; + this.isInstanceOf = isInstanceOf; + } + + extend(type) { + for (let key of Object.keys(type.properties)) { + if (key in this.properties) { + throw new Error(`InternalError: Attempt to extend an object with conflicting property "${key}"`); + } + this.properties[key] = type.properties[key]; + } + + this.patternProperties.push(...type.patternProperties); + + return this; + } + + checkBaseType(baseType) { + return baseType == "object"; + } + + /** + * Extracts the enumerable properties of the given object, including + * function properties which would normally be omitted by X-ray + * wrappers. + * + * @param {object} value + * @param {Context} context + * The current parse context. + * @returns {object} + * An object with an `error` or `value` property. + */ + extractProperties(value, context) { + // |value| should be a JS Xray wrapping an object in the + // extension compartment. This works well except when we need to + // access callable properties on |value| since JS Xrays don't + // support those. To work around the problem, we verify that + // |value| is a plain JS object (i.e., not anything scary like a + // Proxy). Then we copy the properties out of it into a normal + // object using a waiver wrapper. + + let klass = Cu.getClassName(value, true); + if (klass != "Object") { + throw context.error(`Expected a plain JavaScript object, got a ${klass}`, + `be a plain JavaScript object`); + } + + let properties = Object.create(null); + + let waived = Cu.waiveXrays(value); + for (let prop of Object.getOwnPropertyNames(waived)) { + let desc = Object.getOwnPropertyDescriptor(waived, prop); + if (desc.get || desc.set) { + throw context.error("Objects cannot have getters or setters on properties", + "contain no getter or setter properties"); + } + // Chrome ignores non-enumerable properties. + if (desc.enumerable) { + properties[prop] = Cu.unwaiveXrays(desc.value); + } + } + + return properties; + } + + checkProperty(context, prop, propType, result, properties, remainingProps) { + let {type, optional, unsupported, onError} = propType; + let error = null; + + if (unsupported) { + if (prop in properties) { + error = context.error(`Property "${prop}" is unsupported by Firefox`, + `not contain an unsupported "${prop}" property`); + } + } else if (prop in properties) { + if (optional && (properties[prop] === null || properties[prop] === undefined)) { + result[prop] = null; + } else { + let r = context.withPath(prop, () => type.normalize(properties[prop], context)); + if (r.error) { + error = r; + } else { + result[prop] = r.value; + properties[prop] = r.value; + } + } + remainingProps.delete(prop); + } else if (!optional) { + error = context.error(`Property "${prop}" is required`, + `contain the required "${prop}" property`); + } else if (optional !== "omit-key-if-missing") { + result[prop] = null; + } + + if (error) { + if (onError == "warn") { + context.logError(error.error); + } else if (onError != "ignore") { + throw error; + } + + result[prop] = null; + } + } + + normalize(value, context) { + try { + let v = this.normalizeBase("object", value, context); + if (v.error) { + return v; + } + value = v.value; + + if (this.isInstanceOf) { + if (Object.keys(this.properties).length || + this.patternProperties.length || + !(this.additionalProperties instanceof AnyType)) { + throw new Error("InternalError: isInstanceOf can only be used with objects that are otherwise unrestricted"); + } + + if (!instanceOf(value, this.isInstanceOf)) { + return context.error(`Object must be an instance of ${this.isInstanceOf}`, + `be an instance of ${this.isInstanceOf}`); + } + + // This is kind of a hack, but we can't normalize things that + // aren't JSON, so we just return them. + return this.postprocess({value}, context); + } + + let properties = this.extractProperties(value, context); + let remainingProps = new Set(Object.keys(properties)); + + let result = {}; + for (let prop of Object.keys(this.properties)) { + this.checkProperty(context, prop, this.properties[prop], result, + properties, remainingProps); + } + + for (let prop of Object.keys(properties)) { + for (let {pattern, type} of this.patternProperties) { + if (pattern.test(prop)) { + this.checkProperty(context, prop, type, result, + properties, remainingProps); + } + } + } + + if (this.additionalProperties) { + for (let prop of remainingProps) { + let type = this.additionalProperties; + let r = context.withPath(prop, () => type.normalize(properties[prop], context)); + if (r.error) { + return r; + } + result[prop] = r.value; + } + } else if (remainingProps.size == 1) { + return context.error(`Unexpected property "${[...remainingProps]}"`, + `not contain an unexpected "${[...remainingProps]}" property`); + } else if (remainingProps.size) { + let props = [...remainingProps].sort().join(", "); + return context.error(`Unexpected properties: ${props}`, + `not contain the unexpected properties [${props}]`); + } + + return this.postprocess({value: result}, context); + } catch (e) { + if (e.error) { + return e; + } + throw e; + } + } +} + +// This type is just a placeholder to be referred to by +// SubModuleProperty. No value is ever expected to have this type. +SubModuleType = class SubModuleType extends Type { + static get EXTRA_PROPERTIES() { + return ["functions", "events", "properties", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + // The path we pass in here is only used for error messages. + path = [...path, schema.id]; + let functions = schema.functions.map(fun => Schemas.parseFunction(path, fun)); + + return new this(functions); + } + + constructor(functions) { + super(); + this.functions = functions; + } +}; + +class NumberType extends Type { + normalize(value, context) { + let r = this.normalizeBase("number", value, context); + if (r.error) { + return r; + } + + if (isNaN(r.value) || !Number.isFinite(r.value)) { + return context.error("NaN and infinity are not valid", + "be a finite number"); + } + + return r; + } + + checkBaseType(baseType) { + return baseType == "number" || baseType == "integer"; + } +} + +class IntegerType extends Type { + static get EXTRA_PROPERTIES() { + return ["minimum", "maximum", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + return new this(schema, schema.minimum || -Infinity, schema.maximum || Infinity); + } + + constructor(schema, minimum, maximum) { + super(schema); + this.minimum = minimum; + this.maximum = maximum; + } + + normalize(value, context) { + let r = this.normalizeBase("integer", value, context); + if (r.error) { + return r; + } + value = r.value; + + // Ensure it's between -2**31 and 2**31-1 + if (!Number.isSafeInteger(value)) { + return context.error("Integer is out of range", + "be a valid 32 bit signed integer"); + } + + if (value < this.minimum) { + return context.error(`Integer ${value} is too small (must be at least ${this.minimum})`, + `be at least ${this.minimum}`); + } + if (value > this.maximum) { + return context.error(`Integer ${value} is too big (must be at most ${this.maximum})`, + `be no greater than ${this.maximum}`); + } + + return this.postprocess(r, context); + } + + checkBaseType(baseType) { + return baseType == "integer"; + } +} + +class BooleanType extends Type { + normalize(value, context) { + return this.normalizeBase("boolean", value, context); + } + + checkBaseType(baseType) { + return baseType == "boolean"; + } +} + +class ArrayType extends Type { + static get EXTRA_PROPERTIES() { + return ["items", "minItems", "maxItems", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let items = Schemas.parseSchema(schema.items, path); + + return new this(schema, items, schema.minItems || 0, schema.maxItems || Infinity); + } + + constructor(schema, itemType, minItems, maxItems) { + super(schema); + this.itemType = itemType; + this.minItems = minItems; + this.maxItems = maxItems; + } + + normalize(value, context) { + let v = this.normalizeBase("array", value, context); + if (v.error) { + return v; + } + value = v.value; + + let result = []; + for (let [i, element] of value.entries()) { + element = context.withPath(String(i), () => this.itemType.normalize(element, context)); + if (element.error) { + return element; + } + result.push(element.value); + } + + if (result.length < this.minItems) { + return context.error(`Array requires at least ${this.minItems} items; you have ${result.length}`, + `have at least ${this.minItems} items`); + } + + if (result.length > this.maxItems) { + return context.error(`Array requires at most ${this.maxItems} items; you have ${result.length}`, + `have at most ${this.maxItems} items`); + } + + return this.postprocess({value: result}, context); + } + + checkBaseType(baseType) { + return baseType == "array"; + } +} + +class FunctionType extends Type { + static get EXTRA_PROPERTIES() { + return ["parameters", "async", "returns", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let isAsync = !!schema.async; + let isExpectingCallback = typeof schema.async === "string"; + let parameters = null; + if ("parameters" in schema) { + parameters = []; + for (let param of schema.parameters) { + // Callbacks default to optional for now, because of promise + // handling. + let isCallback = isAsync && param.name == schema.async; + if (isCallback) { + isExpectingCallback = false; + } + + parameters.push({ + type: Schemas.parseSchema(param, path, ["name", "optional", "default"]), + name: param.name, + optional: param.optional == null ? isCallback : param.optional, + default: param.default == undefined ? null : param.default, + }); + } + } + if (isExpectingCallback) { + throw new Error(`Internal error: Expected a callback parameter with name ${schema.async}`); + } + + let hasAsyncCallback = false; + if (isAsync) { + hasAsyncCallback = (parameters && + parameters.length && + parameters[parameters.length - 1].name == schema.async); + + if (schema.returns) { + throw new Error("Internal error: Async functions must not have return values."); + } + if (schema.allowAmbiguousOptionalArguments && !hasAsyncCallback) { + throw new Error("Internal error: Async functions with ambiguous arguments must declare the callback as the last parameter"); + } + } + + return new this(schema, parameters, isAsync, hasAsyncCallback); + } + + constructor(schema, parameters, isAsync, hasAsyncCallback) { + super(schema); + this.parameters = parameters; + this.isAsync = isAsync; + this.hasAsyncCallback = hasAsyncCallback; + } + + normalize(value, context) { + return this.normalizeBase("function", value, context); + } + + checkBaseType(baseType) { + return baseType == "function"; + } +} + +// Represents a "property" defined in a schema namespace with a +// particular value. Essentially this is a constant. +class ValueProperty extends Entry { + constructor(schema, name, value) { + super(schema); + this.name = name; + this.value = value; + } + + inject(path, name, dest, context) { + dest[name] = this.value; + } +} + +// Represents a "property" defined in a schema namespace that is not a +// constant. +class TypeProperty extends Entry { + constructor(schema, namespaceName, name, type, writable) { + super(schema); + this.namespaceName = namespaceName; + this.name = name; + this.type = type; + this.writable = writable; + } + + throwError(context, msg) { + throw context.makeError(`${msg} for ${this.namespaceName}.${this.name}.`); + } + + inject(path, name, dest, context) { + if (this.unsupported) { + return; + } + + let apiImpl = context.getImplementation(path.join("."), name); + + let getStub = () => { + this.checkDeprecated(context); + return apiImpl.getProperty(); + }; + + let desc = { + configurable: false, + enumerable: true, + + get: Cu.exportFunction(getStub, dest), + }; + + if (this.writable) { + let setStub = (value) => { + let normalized = this.type.normalize(value, context); + if (normalized.error) { + this.throwError(context, normalized.error); + } + + apiImpl.setProperty(normalized.value); + }; + + desc.set = Cu.exportFunction(setStub, dest); + } + + Object.defineProperty(dest, name, desc); + } +} + +class SubModuleProperty extends Entry { + // A SubModuleProperty represents a tree of objects and properties + // to expose to an extension. Currently we support only a limited + // form of sub-module properties, where "$ref" points to a + // SubModuleType containing a list of functions and "properties" is + // a list of additional simple properties. + // + // name: Name of the property stuff is being added to. + // namespaceName: Namespace in which the property lives. + // reference: Name of the type defining the functions to add to the property. + // properties: Additional properties to add to the module (unsupported). + constructor(schema, name, namespaceName, reference, properties) { + super(schema); + this.name = name; + this.namespaceName = namespaceName; + this.reference = reference; + this.properties = properties; + } + + inject(path, name, dest, context) { + exportLazyGetter(dest, name, () => { + let obj = Cu.createObjectIn(dest); + + let ns = Schemas.namespaces.get(this.namespaceName); + let type = ns.get(this.reference); + if (!type && this.reference.includes(".")) { + let [namespaceName, ref] = this.reference.split("."); + ns = Schemas.namespaces.get(namespaceName); + type = ns.get(ref); + } + if (!type || !(type instanceof SubModuleType)) { + throw new Error(`Internal error: ${this.namespaceName}.${this.reference} is not a sub-module`); + } + + let functions = type.functions; + for (let fun of functions) { + let subpath = path.concat(name); + let namespace = subpath.join("."); + let allowedContexts = fun.allowedContexts.length ? fun.allowedContexts : ns.defaultContexts; + if (context.shouldInject(namespace, fun.name, allowedContexts)) { + fun.inject(subpath, fun.name, obj, context); + } + } + + // TODO: Inject this.properties. + + return obj; + }); + } +} + +// This class is a base class for FunctionEntrys and Events. It takes +// care of validating parameter lists (i.e., handling of optional +// parameters and parameter type checking). +class CallEntry extends Entry { + constructor(schema, path, name, parameters, allowAmbiguousOptionalArguments) { + super(schema); + this.path = path; + this.name = name; + this.parameters = parameters; + this.allowAmbiguousOptionalArguments = allowAmbiguousOptionalArguments; + } + + throwError(context, msg) { + throw context.makeError(`${msg} for ${this.path.join(".")}.${this.name}.`); + } + + checkParameters(args, context) { + let fixedArgs = []; + + // First we create a new array, fixedArgs, that is the same as + // |args| but with default values in place of omitted optional parameters. + let check = (parameterIndex, argIndex) => { + if (parameterIndex == this.parameters.length) { + if (argIndex == args.length) { + return true; + } + return false; + } + + let parameter = this.parameters[parameterIndex]; + if (parameter.optional) { + // Try skipping it. + fixedArgs[parameterIndex] = parameter.default; + if (check(parameterIndex + 1, argIndex)) { + return true; + } + } + + if (argIndex == args.length) { + return false; + } + + let arg = args[argIndex]; + if (!parameter.type.checkBaseType(getValueBaseType(arg))) { + // For Chrome compatibility, use the default value if null or undefined + // is explicitly passed but is not a valid argument in this position. + if (parameter.optional && (arg === null || arg === undefined)) { + fixedArgs[parameterIndex] = Cu.cloneInto(parameter.default, global); + } else { + return false; + } + } else { + fixedArgs[parameterIndex] = arg; + } + + return check(parameterIndex + 1, argIndex + 1); + }; + + if (this.allowAmbiguousOptionalArguments) { + // When this option is set, it's up to the implementation to + // parse arguments. + // The last argument for asynchronous methods is either a function or null. + // This is specifically done for runtime.sendMessage. + if (this.hasAsyncCallback && typeof(args[args.length - 1]) != "function") { + args.push(null); + } + return args; + } + let success = check(0, 0); + if (!success) { + this.throwError(context, "Incorrect argument types"); + } + + // Now we normalize (and fully type check) all non-omitted arguments. + fixedArgs = fixedArgs.map((arg, parameterIndex) => { + if (arg === null) { + return null; + } + let parameter = this.parameters[parameterIndex]; + let r = parameter.type.normalize(arg, context); + if (r.error) { + this.throwError(context, `Type error for parameter ${parameter.name} (${r.error})`); + } + return r.value; + }); + + return fixedArgs; + } +} + +// Represents a "function" defined in a schema namespace. +class FunctionEntry extends CallEntry { + constructor(schema, path, name, type, unsupported, allowAmbiguousOptionalArguments, returns, permissions) { + super(schema, path, name, type.parameters, allowAmbiguousOptionalArguments); + this.unsupported = unsupported; + this.returns = returns; + this.permissions = permissions; + + this.isAsync = type.isAsync; + this.hasAsyncCallback = type.hasAsyncCallback; + } + + inject(path, name, dest, context) { + if (this.unsupported) { + return; + } + + if (this.permissions && !this.permissions.some(perm => context.hasPermission(perm))) { + return; + } + + exportLazyGetter(dest, name, () => { + let apiImpl = context.getImplementation(path.join("."), name); + + let stub; + if (this.isAsync) { + stub = (...args) => { + this.checkDeprecated(context); + let actuals = this.checkParameters(args, context); + let callback = null; + if (this.hasAsyncCallback) { + callback = actuals.pop(); + } + if (callback === null && context.isChromeCompat) { + // We pass an empty stub function as a default callback for + // the `chrome` API, so promise objects are not returned, + // and lastError values are reported immediately. + callback = () => {}; + } + return apiImpl.callAsyncFunction(actuals, callback); + }; + } else if (!this.returns) { + stub = (...args) => { + this.checkDeprecated(context); + let actuals = this.checkParameters(args, context); + return apiImpl.callFunctionNoReturn(actuals); + }; + } else { + stub = (...args) => { + this.checkDeprecated(context); + let actuals = this.checkParameters(args, context); + return apiImpl.callFunction(actuals); + }; + } + return Cu.exportFunction(stub, dest); + }); + } +} + +// Represents an "event" defined in a schema namespace. +class Event extends CallEntry { + constructor(schema, path, name, type, extraParameters, unsupported, permissions) { + super(schema, path, name, extraParameters); + this.type = type; + this.unsupported = unsupported; + this.permissions = permissions; + } + + checkListener(listener, context) { + let r = this.type.normalize(listener, context); + if (r.error) { + this.throwError(context, "Invalid listener"); + } + return r.value; + } + + inject(path, name, dest, context) { + if (this.unsupported) { + return; + } + + if (this.permissions && !this.permissions.some(perm => context.hasPermission(perm))) { + return; + } + + exportLazyGetter(dest, name, () => { + let apiImpl = context.getImplementation(path.join("."), name); + + let addStub = (listener, ...args) => { + listener = this.checkListener(listener, context); + let actuals = this.checkParameters(args, context); + apiImpl.addListener(listener, actuals); + }; + + let removeStub = (listener) => { + listener = this.checkListener(listener, context); + apiImpl.removeListener(listener); + }; + + let hasStub = (listener) => { + listener = this.checkListener(listener, context); + return apiImpl.hasListener(listener); + }; + + let obj = Cu.createObjectIn(dest); + + Cu.exportFunction(addStub, obj, {defineAs: "addListener"}); + Cu.exportFunction(removeStub, obj, {defineAs: "removeListener"}); + Cu.exportFunction(hasStub, obj, {defineAs: "hasListener"}); + + return obj; + }); + } +} + +const TYPES = Object.freeze(Object.assign(Object.create(null), { + any: AnyType, + array: ArrayType, + boolean: BooleanType, + function: FunctionType, + integer: IntegerType, + number: NumberType, + object: ObjectType, + string: StringType, +})); + +this.Schemas = { + initialized: false, + + // Maps a schema URL to the JSON contained in that schema file. This + // is useful for sending the JSON across processes. + schemaJSON: new Map(), + + // Map[ -> Map[ -> Entry]] + // This keeps track of all the schemas that have been loaded so far. + namespaces: new Map(), + + register(namespaceName, symbol, value) { + let ns = this.namespaces.get(namespaceName); + if (!ns) { + ns = new Map(); + ns.name = namespaceName; + ns.permissions = null; + ns.allowedContexts = []; + ns.defaultContexts = []; + this.namespaces.set(namespaceName, ns); + } + ns.set(symbol, value); + }, + + parseSchema(schema, path, extraProperties = []) { + let allowedProperties = new Set(extraProperties); + + if ("choices" in schema) { + return ChoiceType.parseSchema(schema, path, allowedProperties); + } else if ("$ref" in schema) { + return RefType.parseSchema(schema, path, allowedProperties); + } + + if (!("type" in schema)) { + throw new Error(`Unexpected value for type: ${JSON.stringify(schema)}`); + } + + allowedProperties.add("type"); + + let type = TYPES[schema.type]; + if (!type) { + throw new Error(`Unexpected type ${schema.type}`); + } + return type.parseSchema(schema, path, allowedProperties); + }, + + parseFunction(path, fun) { + let f = new FunctionEntry(fun, path, fun.name, + this.parseSchema(fun, path, + ["name", "unsupported", "returns", + "permissions", + "allowAmbiguousOptionalArguments"]), + fun.unsupported || false, + fun.allowAmbiguousOptionalArguments || false, + fun.returns || null, + fun.permissions || null); + return f; + }, + + loadType(namespaceName, type) { + if ("$extend" in type) { + this.extendType(namespaceName, type); + } else { + this.register(namespaceName, type.id, this.parseSchema(type, [namespaceName], ["id"])); + } + }, + + extendType(namespaceName, type) { + let ns = Schemas.namespaces.get(namespaceName); + let targetType = ns && ns.get(type.$extend); + + // Only allow extending object and choices types for now. + if (targetType instanceof ObjectType) { + type.type = "object"; + } else if (!targetType) { + throw new Error(`Internal error: Attempt to extend a nonexistant type ${type.$extend}`); + } else if (!(targetType instanceof ChoiceType)) { + throw new Error(`Internal error: Attempt to extend a non-extensible type ${type.$extend}`); + } + + let parsed = this.parseSchema(type, [namespaceName], ["$extend"]); + if (parsed.constructor !== targetType.constructor) { + throw new Error(`Internal error: Bad attempt to extend ${type.$extend}`); + } + + targetType.extend(parsed); + }, + + loadProperty(namespaceName, name, prop) { + if ("$ref" in prop) { + if (!prop.unsupported) { + this.register(namespaceName, name, new SubModuleProperty(prop, name, namespaceName, prop.$ref, + prop.properties || {})); + } + } else if ("value" in prop) { + this.register(namespaceName, name, new ValueProperty(prop, name, prop.value)); + } else { + // We ignore the "optional" attribute on properties since we + // don't inject anything here anyway. + let type = this.parseSchema(prop, [namespaceName], ["optional", "writable"]); + this.register(namespaceName, name, new TypeProperty(prop, namespaceName, name, type, prop.writable || false)); + } + }, + + loadFunction(namespaceName, fun) { + let f = this.parseFunction([namespaceName], fun); + this.register(namespaceName, fun.name, f); + }, + + loadEvent(namespaceName, event) { + let extras = event.extraParameters || []; + extras = extras.map(param => { + return { + type: this.parseSchema(param, [namespaceName], ["name", "optional", "default"]), + name: param.name, + optional: param.optional || false, + default: param.default == undefined ? null : param.default, + }; + }); + + // We ignore these properties for now. + /* eslint-disable no-unused-vars */ + let returns = event.returns; + let filters = event.filters; + /* eslint-enable no-unused-vars */ + + let type = this.parseSchema(event, [namespaceName], + ["name", "unsupported", "permissions", + "extraParameters", "returns", "filters"]); + + let e = new Event(event, [namespaceName], event.name, type, extras, + event.unsupported || false, + event.permissions || null); + this.register(namespaceName, event.name, e); + }, + + init() { + if (this.initialized) { + return; + } + this.initialized = true; + + if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { + let data = Services.cpmm.initialProcessData; + let schemas = data["Extension:Schemas"]; + if (schemas) { + this.schemaJSON = schemas; + } + Services.cpmm.addMessageListener("Schema:Add", this); + } + + this.flushSchemas(); + }, + + receiveMessage(msg) { + switch (msg.name) { + case "Schema:Add": + this.schemaJSON.set(msg.data.url, msg.data.schema); + this.flushSchemas(); + break; + + case "Schema:Delete": + this.schemaJSON.delete(msg.data.url); + this.flushSchemas(); + break; + } + }, + + flushSchemas() { + XPCOMUtils.defineLazyGetter(this, "namespaces", + () => this.parseSchemas()); + }, + + parseSchemas() { + Object.defineProperty(this, "namespaces", { + enumerable: true, + configurable: true, + value: new Map(), + }); + + for (let json of this.schemaJSON.values()) { + try { + this.loadSchema(json); + } catch (e) { + Cu.reportError(e); + } + } + + return this.namespaces; + }, + + loadSchema(json) { + for (let namespace of json) { + let name = namespace.namespace; + + let types = namespace.types || []; + for (let type of types) { + this.loadType(name, type); + } + + let properties = namespace.properties || {}; + for (let propertyName of Object.keys(properties)) { + this.loadProperty(name, propertyName, properties[propertyName]); + } + + let functions = namespace.functions || []; + for (let fun of functions) { + this.loadFunction(name, fun); + } + + let events = namespace.events || []; + for (let event of events) { + this.loadEvent(name, event); + } + + let ns = this.namespaces.get(name); + ns.permissions = namespace.permissions || null; + ns.allowedContexts = namespace.allowedContexts || []; + ns.defaultContexts = namespace.defaultContexts || []; + } + }, + + load(url) { + if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_CONTENT) { + return readJSON(url).then(json => { + this.schemaJSON.set(url, json); + + let data = Services.ppmm.initialProcessData; + data["Extension:Schemas"] = this.schemaJSON; + + Services.ppmm.broadcastAsyncMessage("Schema:Add", {url, schema: json}); + + this.flushSchemas(); + }); + } + }, + + unload(url) { + this.schemaJSON.delete(url); + + let data = Services.ppmm.initialProcessData; + data["Extension:Schemas"] = this.schemaJSON; + + Services.ppmm.broadcastAsyncMessage("Schema:Delete", {url}); + + this.flushSchemas(); + }, + + /** + * Checks whether a given object has the necessary permissions to + * expose the given namespace. + * + * @param {string} namespace + * The top-level namespace to check permissions for. + * @param {object} wrapperFuncs + * Wrapper functions for the given context. + * @param {function} wrapperFuncs.hasPermission + * A function which, when given a string argument, returns true + * if the context has the given permission. + * @returns {boolean} + * True if the context has permission for the given namespace. + */ + checkPermissions(namespace, wrapperFuncs) { + let ns = this.namespaces.get(namespace); + if (ns && ns.permissions) { + return ns.permissions.some(perm => wrapperFuncs.hasPermission(perm)); + } + return true; + }, + + exportLazyGetter, + + /** + * Inject registered extension APIs into `dest`. + * + * @param {object} dest The root namespace for the APIs. + * This object is usually exposed to extensions as "chrome" or "browser". + * @param {object} wrapperFuncs An implementation of the InjectionContext + * interface, which runs the actual functionality of the generated API. + */ + inject(dest, wrapperFuncs) { + let context = new InjectionContext(wrapperFuncs); + + let createNamespace = ns => { + let obj = Cu.createObjectIn(dest); + + for (let [name, entry] of ns) { + let allowedContexts = entry.allowedContexts; + if (!allowedContexts.length) { + allowedContexts = ns.defaultContexts; + } + + if (context.shouldInject(ns.name, name, allowedContexts)) { + entry.inject([ns.name], name, obj, context); + } + } + + // Remove the namespace object if it is empty + if (Object.keys(obj).length) { + return obj; + } + }; + + let createNestedNamespaces = (parent, namespaces) => { + for (let [prop, namespace] of namespaces) { + if (namespace instanceof DeepMap) { + exportLazyGetter(parent, prop, () => { + let obj = Cu.createObjectIn(parent); + createNestedNamespaces(obj, namespace); + return obj; + }); + } else { + exportLazyGetter(parent, prop, + () => createNamespace(namespace)); + } + } + }; + + let nestedNamespaces = new DeepMap(); + for (let ns of this.namespaces.values()) { + if (ns.permissions && !ns.permissions.some(perm => context.hasPermission(perm))) { + continue; + } + + if (!wrapperFuncs.shouldInject(ns.name, null, ns.allowedContexts)) { + continue; + } + + if (ns.name.includes(".")) { + let path = ns.name.split("."); + let leafName = path.pop(); + + let parent = nestedNamespaces.getPath(...path); + + parent.set(leafName, ns); + } else { + exportLazyGetter(dest, ns.name, + () => createNamespace(ns)); + } + } + + createNestedNamespaces(dest, nestedNamespaces); + }, + + /** + * Normalize `obj` according to the loaded schema for `typeName`. + * + * @param {object} obj The object to normalize against the schema. + * @param {string} typeName The name in the format namespace.propertyname + * @param {object} context An implementation of Context. Any validation errors + * are reported to the given context. + * @returns {object} The normalized object. + */ + normalize(obj, typeName, context) { + let [namespaceName, prop] = typeName.split("."); + let ns = this.namespaces.get(namespaceName); + let type = ns.get(prop); + + return type.normalize(obj, new Context(context)); + }, +}; diff --git a/toolkit/components/webextensions/ext-alarms.js b/toolkit/components/webextensions/ext-alarms.js new file mode 100644 index 000000000..2171e7dba --- /dev/null +++ b/toolkit/components/webextensions/ext-alarms.js @@ -0,0 +1,155 @@ +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +var { + EventManager, +} = ExtensionUtils; + +// WeakMap[Extension -> Map[name -> Alarm]] +var alarmsMap = new WeakMap(); + +// WeakMap[Extension -> Set[callback]] +var alarmCallbacksMap = new WeakMap(); + +// Manages an alarm created by the extension (alarms API). +function Alarm(extension, name, alarmInfo) { + this.extension = extension; + this.name = name; + this.when = alarmInfo.when; + this.delayInMinutes = alarmInfo.delayInMinutes; + this.periodInMinutes = alarmInfo.periodInMinutes; + this.canceled = false; + + let delay, scheduledTime; + if (this.when) { + scheduledTime = this.when; + delay = this.when - Date.now(); + } else { + if (!this.delayInMinutes) { + this.delayInMinutes = this.periodInMinutes; + } + delay = this.delayInMinutes * 60 * 1000; + scheduledTime = Date.now() + delay; + } + + this.scheduledTime = scheduledTime; + + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT); + this.timer = timer; +} + +Alarm.prototype = { + clear() { + this.timer.cancel(); + alarmsMap.get(this.extension).delete(this.name); + this.canceled = true; + }, + + observe(subject, topic, data) { + if (this.canceled) { + return; + } + + for (let callback of alarmCallbacksMap.get(this.extension)) { + callback(this); + } + + if (!this.periodInMinutes) { + this.clear(); + return; + } + + let delay = this.periodInMinutes * 60 * 1000; + this.scheduledTime = Date.now() + delay; + this.timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT); + }, + + get data() { + return { + name: this.name, + scheduledTime: this.scheduledTime, + periodInMinutes: this.periodInMinutes, + }; + }, +}; + +/* eslint-disable mozilla/balanced-listeners */ +extensions.on("startup", (type, extension) => { + alarmsMap.set(extension, new Map()); + alarmCallbacksMap.set(extension, new Set()); +}); + +extensions.on("shutdown", (type, extension) => { + if (alarmsMap.has(extension)) { + for (let alarm of alarmsMap.get(extension).values()) { + alarm.clear(); + } + alarmsMap.delete(extension); + alarmCallbacksMap.delete(extension); + } +}); +/* eslint-enable mozilla/balanced-listeners */ + +extensions.registerSchemaAPI("alarms", "addon_parent", context => { + let {extension} = context; + return { + alarms: { + create: function(name, alarmInfo) { + name = name || ""; + let alarms = alarmsMap.get(extension); + if (alarms.has(name)) { + alarms.get(name).clear(); + } + let alarm = new Alarm(extension, name, alarmInfo); + alarms.set(alarm.name, alarm); + }, + + get: function(name) { + name = name || ""; + let alarms = alarmsMap.get(extension); + if (alarms.has(name)) { + return Promise.resolve(alarms.get(name).data); + } + return Promise.resolve(); + }, + + getAll: function() { + let result = Array.from(alarmsMap.get(extension).values(), alarm => alarm.data); + return Promise.resolve(result); + }, + + clear: function(name) { + name = name || ""; + let alarms = alarmsMap.get(extension); + if (alarms.has(name)) { + alarms.get(name).clear(); + return Promise.resolve(true); + } + return Promise.resolve(false); + }, + + clearAll: function() { + let cleared = false; + for (let alarm of alarmsMap.get(extension).values()) { + alarm.clear(); + cleared = true; + } + return Promise.resolve(cleared); + }, + + onAlarm: new EventManager(context, "alarms.onAlarm", fire => { + let callback = alarm => { + fire(alarm.data); + }; + + alarmCallbacksMap.get(extension).add(callback); + return () => { + alarmCallbacksMap.get(extension).delete(callback); + }; + }).api(), + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-backgroundPage.js b/toolkit/components/webextensions/ext-backgroundPage.js new file mode 100644 index 000000000..fce6100ca --- /dev/null +++ b/toolkit/components/webextensions/ext-backgroundPage.js @@ -0,0 +1,147 @@ +"use strict"; + +var {interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +const { + promiseDocumentLoaded, + promiseObserved, +} = ExtensionUtils; + +const XUL_URL = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + encodeURI( + ` + `); + +// WeakMap[Extension -> BackgroundPage] +var backgroundPagesMap = new WeakMap(); + +// Responsible for the background_page section of the manifest. +function BackgroundPage(options, extension) { + this.extension = extension; + this.page = options.page || null; + this.isGenerated = !!options.scripts; + this.windowlessBrowser = null; + this.webNav = null; +} + +BackgroundPage.prototype = { + build: Task.async(function* () { + let windowlessBrowser = Services.appShell.createWindowlessBrowser(true); + this.windowlessBrowser = windowlessBrowser; + + let url; + if (this.page) { + url = this.extension.baseURI.resolve(this.page); + } else if (this.isGenerated) { + url = this.extension.baseURI.resolve("_generated_background_page.html"); + } + + if (!this.extension.isExtensionURL(url)) { + this.extension.manifestError("Background page must be a file within the extension"); + url = this.extension.baseURI.resolve("_blank.html"); + } + + let system = Services.scriptSecurityManager.getSystemPrincipal(); + + // The windowless browser is a thin wrapper around a docShell that keeps + // its related resources alive. It implements nsIWebNavigation and + // forwards its methods to the underlying docShell, but cannot act as a + // docShell itself. Calling `getInterface(nsIDocShell)` gives us the + // underlying docShell, and `QueryInterface(nsIWebNavigation)` gives us + // access to the webNav methods that are already available on the + // windowless browser, but contrary to appearances, they are not the same + // object. + let chromeShell = windowlessBrowser.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIWebNavigation); + + if (PrivateBrowsingUtils.permanentPrivateBrowsing) { + let attrs = chromeShell.getOriginAttributes(); + attrs.privateBrowsingId = 1; + chromeShell.setOriginAttributes(attrs); + } + + chromeShell.useGlobalHistory = false; + chromeShell.createAboutBlankContentViewer(system); + chromeShell.loadURI(XUL_URL, 0, null, null, null); + + + yield promiseObserved("chrome-document-global-created", + win => win.document == chromeShell.document); + + let chromeDoc = yield promiseDocumentLoaded(chromeShell.document); + + let browser = chromeDoc.createElement("browser"); + browser.setAttribute("type", "content"); + browser.setAttribute("disableglobalhistory", "true"); + chromeDoc.documentElement.appendChild(browser); + + extensions.emit("extension-browser-inserted", browser); + browser.messageManager.sendAsyncMessage("Extension:InitExtensionView", { + viewType: "background", + url, + }); + + yield new Promise(resolve => { + browser.messageManager.addMessageListener("Extension:ExtensionViewLoaded", function onLoad() { + browser.messageManager.removeMessageListener("Extension:ExtensionViewLoaded", onLoad); + resolve(); + }); + }); + + // TODO(robwu): This is not webext-oop compatible. + this.webNav = browser.docShell.QueryInterface(Ci.nsIWebNavigation); + let window = this.webNav.document.defaultView; + + + // Set the add-on's main debugger global, for use in the debugger + // console. + if (this.extension.addonData.instanceID) { + AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID) + .then(addon => addon.setDebugGlobal(window)); + } + + this.extension.emit("startup"); + }), + + shutdown() { + if (this.extension.addonData.instanceID) { + AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID) + .then(addon => addon.setDebugGlobal(null)); + } + + // Navigate away from the background page to invalidate any + // setTimeouts or other callbacks. + if (this.webNav) { + this.webNav.loadURI("about:blank", 0, null, null, null); + this.webNav = null; + } + + this.windowlessBrowser.loadURI("about:blank", 0, null, null, null); + this.windowlessBrowser.close(); + this.windowlessBrowser = null; + }, +}; + +/* eslint-disable mozilla/balanced-listeners */ +extensions.on("manifest_background", (type, directive, extension, manifest) => { + let bgPage = new BackgroundPage(manifest.background, extension); + backgroundPagesMap.set(extension, bgPage); + return bgPage.build(); +}); + +extensions.on("shutdown", (type, extension) => { + if (backgroundPagesMap.has(extension)) { + backgroundPagesMap.get(extension).shutdown(); + backgroundPagesMap.delete(extension); + } +}); +/* eslint-enable mozilla/balanced-listeners */ diff --git a/toolkit/components/webextensions/ext-browser-content.js b/toolkit/components/webextensions/ext-browser-content.js new file mode 100644 index 000000000..e14ca50d6 --- /dev/null +++ b/toolkit/components/webextensions/ext-browser-content.js @@ -0,0 +1,217 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout", + "resource://gre/modules/Timer.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "require", + "resource://devtools/shared/Loader.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "setTimeout", + "resource://gre/modules/Timer.jsm"); + +XPCOMUtils.defineLazyGetter(this, "colorUtils", () => { + return require("devtools/shared/css/color").colorUtils; +}); + +const { + stylesheetMap, +} = ExtensionUtils; + +/* globals addMessageListener, content, docShell, sendAsyncMessage */ + +// Minimum time between two resizes. +const RESIZE_TIMEOUT = 100; + +const BrowserListener = { + init({allowScriptsToClose, fixedWidth, maxHeight, maxWidth, stylesheets}) { + this.fixedWidth = fixedWidth; + this.stylesheets = stylesheets || []; + + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + + this.oldBackground = null; + + if (allowScriptsToClose) { + content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .allowScriptsToClose(); + } + + addEventListener("DOMWindowCreated", this, true); + addEventListener("load", this, true); + addEventListener("DOMContentLoaded", this, true); + addEventListener("DOMWindowClose", this, true); + addEventListener("MozScrolledAreaChanged", this, true); + }, + + destroy() { + removeEventListener("DOMWindowCreated", this, true); + removeEventListener("load", this, true); + removeEventListener("DOMContentLoaded", this, true); + removeEventListener("DOMWindowClose", this, true); + removeEventListener("MozScrolledAreaChanged", this, true); + }, + + receiveMessage({name, data}) { + if (name === "Extension:InitBrowser") { + this.init(data); + } + }, + + handleEvent(event) { + switch (event.type) { + case "DOMWindowCreated": + if (event.target === content.document) { + let winUtils = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + for (let url of this.stylesheets) { + winUtils.addSheet(stylesheetMap.get(url), winUtils.AGENT_SHEET); + } + } + break; + + case "DOMWindowClose": + if (event.target === content) { + event.preventDefault(); + + sendAsyncMessage("Extension:DOMWindowClose"); + } + break; + + case "DOMContentLoaded": + if (event.target === content.document) { + sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href}); + this.handleDOMChange(true); + } + break; + + case "load": + if (event.target.contentWindow === content) { + // For about:addons inline , we currently receive a load + // event on the element, but no load or DOMContentLoaded + // events from the content window. + sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href}); + } else if (event.target !== content.document) { + break; + } + + // We use a capturing listener, so we get this event earlier than any + // load listeners in the content page. Resizing after a timeout ensures + // that we calculate the size after the entire event cycle has completed + // (unless someone spins the event loop, anyway), and hopefully after + // the content has made any modifications. + Promise.resolve().then(() => { + this.handleDOMChange(true); + }); + + // Mutation observer to make sure the panel shrinks when the content does. + new content.MutationObserver(this.handleDOMChange.bind(this)).observe( + content.document.documentElement, { + attributes: true, + characterData: true, + childList: true, + subtree: true, + }); + break; + + case "MozScrolledAreaChanged": + this.handleDOMChange(); + break; + } + }, + + // Resizes the browser to match the preferred size of the content (debounced). + handleDOMChange(ignoreThrottling = false) { + if (ignoreThrottling && this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + this.resizeTimeout = null; + } + + if (this.resizeTimeout == null) { + this.resizeTimeout = setTimeout(() => { + try { + if (content) { + this._handleDOMChange("delayed"); + } + } finally { + this.resizeTimeout = null; + } + }, RESIZE_TIMEOUT); + + this._handleDOMChange(); + } + }, + + _handleDOMChange(detail) { + let doc = content.document; + + let body = doc.body; + if (!body || doc.compatMode === "BackCompat") { + // In quirks mode, the root element is used as the scroll frame, and the + // body lies about its scroll geometry, and returns the values for the + // root instead. + body = doc.documentElement; + } + + + let result; + if (this.fixedWidth) { + // If we're in a fixed-width area (namely a slide-in subview of the main + // menu panel), we need to calculate the view height based on the + // preferred height of the content document's root scrollable element at the + // current width, rather than the complete preferred dimensions of the + // content window. + + // Compensate for any offsets (margin, padding, ...) between the scroll + // area of the body and the outer height of the document. + let getHeight = elem => elem.getBoundingClientRect(elem).height; + let bodyPadding = getHeight(doc.documentElement) - getHeight(body); + + let height = Math.ceil(body.scrollHeight + bodyPadding); + + result = {height, detail}; + } else { + let background = doc.defaultView.getComputedStyle(body).backgroundColor; + let bgColor = colorUtils.colorToRGBA(background); + if (bgColor.a !== 1) { + // Ignore non-opaque backgrounds. + background = null; + } + + if (background !== this.oldBackground) { + sendAsyncMessage("Extension:BrowserBackgroundChanged", {background}); + } + this.oldBackground = background; + + + // Adjust the size of the browser based on its content's preferred size. + let {contentViewer} = docShell; + let ratio = content.devicePixelRatio; + + let w = {}, h = {}; + contentViewer.getContentSizeConstrained(this.maxWidth * ratio, + this.maxHeight * ratio, + w, h); + + let width = Math.ceil(w.value / ratio); + let height = Math.ceil(h.value / ratio); + + result = {width, height, detail}; + } + + sendAsyncMessage("Extension:BrowserResized", result); + }, +}; + +addMessageListener("Extension:InitBrowser", BrowserListener); diff --git a/toolkit/components/webextensions/ext-c-backgroundPage.js b/toolkit/components/webextensions/ext-c-backgroundPage.js new file mode 100644 index 000000000..b5074dd9a --- /dev/null +++ b/toolkit/components/webextensions/ext-c-backgroundPage.js @@ -0,0 +1,45 @@ +"use strict"; + +global.initializeBackgroundPage = (contentWindow) => { + // Override the `alert()` method inside background windows; + // we alias it to console.log(). + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394 + let alertDisplayedWarning = false; + let alertOverwrite = text => { + if (!alertDisplayedWarning) { + require("devtools/client/framework/devtools-browser"); + + let hudservice = require("devtools/client/webconsole/hudservice"); + hudservice.openBrowserConsoleOrFocus(); + + contentWindow.console.warn("alert() is not supported in background windows; please use console.log instead."); + + alertDisplayedWarning = true; + } + + contentWindow.console.log(text); + }; + Cu.exportFunction(alertOverwrite, contentWindow, {defineAs: "alert"}); +}; + +extensions.registerSchemaAPI("extension", "addon_child", context => { + function getBackgroundPage() { + for (let view of context.extension.views) { + if (view.viewType == "background" && context.principal.subsumes(view.principal)) { + return view.contentWindow; + } + } + return null; + } + return { + extension: { + getBackgroundPage, + }, + + runtime: { + getBackgroundPage() { + return context.cloneScope.Promise.resolve(getBackgroundPage()); + }, + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-c-extension.js b/toolkit/components/webextensions/ext-c-extension.js new file mode 100644 index 000000000..669309bea --- /dev/null +++ b/toolkit/components/webextensions/ext-c-extension.js @@ -0,0 +1,57 @@ +"use strict"; + +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); + +function extensionApiFactory(context) { + return { + extension: { + getURL(url) { + return context.extension.baseURI.resolve(url); + }, + + get lastError() { + return context.lastError; + }, + + get inIncognitoContext() { + return context.incognito; + }, + }, + }; +} + +extensions.registerSchemaAPI("extension", "addon_child", extensionApiFactory); +extensions.registerSchemaAPI("extension", "content_child", extensionApiFactory); +extensions.registerSchemaAPI("extension", "addon_child", context => { + return { + extension: { + getViews: function(fetchProperties) { + let result = Cu.cloneInto([], context.cloneScope); + + for (let view of context.extension.views) { + if (!view.active) { + continue; + } + if (!context.principal.subsumes(view.principal)) { + continue; + } + + if (fetchProperties !== null) { + if (fetchProperties.type !== null && view.viewType != fetchProperties.type) { + continue; + } + + if (fetchProperties.windowId !== null && view.windowId != fetchProperties.windowId) { + continue; + } + } + + result.push(view.contentWindow); + } + + return result; + }, + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-c-runtime.js b/toolkit/components/webextensions/ext-c-runtime.js new file mode 100644 index 000000000..8adca60ca --- /dev/null +++ b/toolkit/components/webextensions/ext-c-runtime.js @@ -0,0 +1,93 @@ +"use strict"; + +function runtimeApiFactory(context) { + let {extension} = context; + + return { + runtime: { + onConnect: context.messenger.onConnect("runtime.onConnect"), + + onMessage: context.messenger.onMessage("runtime.onMessage"), + + connect: function(extensionId, connectInfo) { + let name = connectInfo !== null && connectInfo.name || ""; + extensionId = extensionId || extension.id; + let recipient = {extensionId}; + + return context.messenger.connect(context.messageManager, name, recipient); + }, + + sendMessage: function(...args) { + let options; // eslint-disable-line no-unused-vars + let extensionId, message, responseCallback; + if (typeof args[args.length - 1] == "function") { + responseCallback = args.pop(); + } + if (!args.length) { + return Promise.reject({message: "runtime.sendMessage's message argument is missing"}); + } else if (args.length == 1) { + message = args[0]; + } else if (args.length == 2) { + if (typeof args[0] == "string" && args[0]) { + [extensionId, message] = args; + } else { + [message, options] = args; + } + } else if (args.length == 3) { + [extensionId, message, options] = args; + } else if (args.length == 4 && !responseCallback) { + return Promise.reject({message: "runtime.sendMessage's last argument is not a function"}); + } else { + return Promise.reject({message: "runtime.sendMessage received too many arguments"}); + } + + if (extensionId != null && typeof extensionId != "string") { + return Promise.reject({message: "runtime.sendMessage's extensionId argument is invalid"}); + } + if (options != null && typeof options != "object") { + return Promise.reject({message: "runtime.sendMessage's options argument is invalid"}); + } + // TODO(robwu): Validate option keys and values when we support it. + + extensionId = extensionId || extension.id; + let recipient = {extensionId}; + + return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback); + }, + + connectNative(application) { + let recipient = { + childId: context.childManager.id, + toNativeApp: application, + }; + + return context.messenger.connectNative(context.messageManager, "", recipient); + }, + + sendNativeMessage(application, message) { + let recipient = { + childId: context.childManager.id, + toNativeApp: application, + }; + return context.messenger.sendNativeMessage(context.messageManager, message, recipient); + }, + + get lastError() { + return context.lastError; + }, + + getManifest() { + return Cu.cloneInto(extension.manifest, context.cloneScope); + }, + + id: extension.id, + + getURL: function(url) { + return extension.baseURI.resolve(url); + }, + }, + }; +} + +extensions.registerSchemaAPI("runtime", "addon_child", runtimeApiFactory); +extensions.registerSchemaAPI("runtime", "content_child", runtimeApiFactory); diff --git a/toolkit/components/webextensions/ext-c-storage.js b/toolkit/components/webextensions/ext-c-storage.js new file mode 100644 index 000000000..e8d53058f --- /dev/null +++ b/toolkit/components/webextensions/ext-c-storage.js @@ -0,0 +1,62 @@ +"use strict"; + +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage", + "resource://gre/modules/ExtensionStorage.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +function storageApiFactory(context) { + function sanitize(items) { + // The schema validator already takes care of arrays (which are only allowed + // to contain strings). Strings and null are safe values. + if (typeof items != "object" || items === null || Array.isArray(items)) { + return items; + } + // If we got here, then `items` is an object generated by `ObjectType`'s + // `normalize` method from Schemas.jsm. The object returned by `normalize` + // lives in this compartment, while the values live in compartment of + // `context.contentWindow`. The `sanitize` method runs with the principal + // of `context`, so we cannot just use `ExtensionStorage.sanitize` because + // it is not allowed to access properties of `items`. + // So we enumerate all properties and sanitize each value individually. + let sanitized = {}; + for (let [key, value] of Object.entries(items)) { + sanitized[key] = ExtensionStorage.sanitize(value, context); + } + return sanitized; + } + return { + storage: { + local: { + get: function(keys) { + keys = sanitize(keys); + return context.childManager.callParentAsyncFunction("storage.local.get", [ + keys, + ]); + }, + set: function(items) { + items = sanitize(items); + return context.childManager.callParentAsyncFunction("storage.local.set", [ + items, + ]); + }, + }, + + sync: { + get: function(keys) { + keys = sanitize(keys); + return context.childManager.callParentAsyncFunction("storage.sync.get", [ + keys, + ]); + }, + set: function(items) { + items = sanitize(items); + return context.childManager.callParentAsyncFunction("storage.sync.set", [ + items, + ]); + }, + }, + }, + }; +} +extensions.registerSchemaAPI("storage", "addon_child", storageApiFactory); +extensions.registerSchemaAPI("storage", "content_child", storageApiFactory); diff --git a/toolkit/components/webextensions/ext-c-test.js b/toolkit/components/webextensions/ext-c-test.js new file mode 100644 index 000000000..b0c92f79f --- /dev/null +++ b/toolkit/components/webextensions/ext-c-test.js @@ -0,0 +1,188 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/ExtensionUtils.jsm"); +var { + SingletonEventManager, +} = ExtensionUtils; + +/** + * Checks whether the given error matches the given expectations. + * + * @param {*} error + * The error to check. + * @param {string|RegExp|function|null} expectedError + * The expectation to check against. If this parameter is: + * + * - a string, the error message must exactly equal the string. + * - a regular expression, it must match the error message. + * - a function, it is called with the error object and its + * return value is returned. + * - null, the function always returns true. + * @param {BaseContext} context + * + * @returns {boolean} + * True if the error matches the expected error. + */ +function errorMatches(error, expectedError, context) { + if (expectedError === null) { + return true; + } + + if (typeof expectedError === "function") { + return context.runSafeWithoutClone(expectedError, error); + } + + if (typeof error !== "object" || error == null || + typeof error.message !== "string") { + return false; + } + + if (typeof expectedError === "string") { + return error.message === expectedError; + } + + try { + return expectedError.test(error.message); + } catch (e) { + Cu.reportError(e); + } + + return false; +} + +/** + * Calls .toSource() on the given value, but handles null, undefined, + * and errors. + * + * @param {*} value + * @returns {string} + */ +function toSource(value) { + if (value === null) { + return "null"; + } + if (value === undefined) { + return "undefined"; + } + if (typeof value === "string") { + return JSON.stringify(value); + } + + try { + return String(value.toSource()); + } catch (e) { + return ""; + } +} + +function makeTestAPI(context) { + const {extension} = context; + + function getStack() { + return new context.cloneScope.Error().stack.replace(/^/gm, " "); + } + + function assertTrue(value, msg) { + extension.emit("test-result", Boolean(value), String(msg), getStack()); + } + + return { + test: { + sendMessage(...args) { + extension.emit("test-message", ...args); + }, + + notifyPass(msg) { + extension.emit("test-done", true, msg, getStack()); + }, + + notifyFail(msg) { + extension.emit("test-done", false, msg, getStack()); + }, + + log(msg) { + extension.emit("test-log", true, msg, getStack()); + }, + + fail(msg) { + assertTrue(false, msg); + }, + + succeed(msg) { + assertTrue(true, msg); + }, + + assertTrue(value, msg) { + assertTrue(value, msg); + }, + + assertFalse(value, msg) { + assertTrue(!value, msg); + }, + + assertEq(expected, actual, msg) { + let equal = expected === actual; + + expected = String(expected); + actual = String(actual); + + if (!equal && expected === actual) { + actual += " (different)"; + } + extension.emit("test-eq", equal, String(msg), expected, actual, getStack()); + }, + + assertRejects(promise, expectedError, msg) { + // Wrap in a native promise for consistency. + promise = Promise.resolve(promise); + + if (msg) { + msg = `: ${msg}`; + } + + return promise.then(result => { + assertTrue(false, `Promise resolved, expected rejection${msg}`); + }, error => { + let errorMessage = toSource(error && error.message); + + assertTrue(errorMatches(error, expectedError, context), + `Promise rejected, expecting rejection to match ${toSource(expectedError)}, ` + + `got ${errorMessage}${msg}`); + }); + }, + + assertThrows(func, expectedError, msg) { + if (msg) { + msg = `: ${msg}`; + } + + try { + func(); + + assertTrue(false, `Function did not throw, expected error${msg}`); + } catch (error) { + let errorMessage = toSource(error && error.message); + + assertTrue(errorMatches(error, expectedError, context), + `Function threw, expecting error to match ${toSource(expectedError)}` + + `got ${errorMessage}${msg}`); + } + }, + + onMessage: new SingletonEventManager(context, "test.onMessage", fire => { + let handler = (event, ...args) => { + context.runSafe(fire, ...args); + }; + + extension.on("test-harness-message", handler); + return () => { + extension.off("test-harness-message", handler); + }; + }).api(), + }, + }; +} + +extensions.registerSchemaAPI("test", "addon_child", makeTestAPI); +extensions.registerSchemaAPI("test", "content_child", makeTestAPI); + diff --git a/toolkit/components/webextensions/ext-cookies.js b/toolkit/components/webextensions/ext-cookies.js new file mode 100644 index 000000000..d0a703421 --- /dev/null +++ b/toolkit/components/webextensions/ext-cookies.js @@ -0,0 +1,484 @@ +"use strict"; + +const {interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService", + "resource://gre/modules/ContextualIdentityService.jsm"); + +var { + EventManager, +} = ExtensionUtils; + +var DEFAULT_STORE = "firefox-default"; +var PRIVATE_STORE = "firefox-private"; +var CONTAINER_STORE = "firefox-container-"; + +global.getCookieStoreIdForTab = function(data, tab) { + if (data.incognito) { + return PRIVATE_STORE; + } + + if (tab.userContextId) { + return CONTAINER_STORE + tab.userContextId; + } + + return DEFAULT_STORE; +}; + +global.isPrivateCookieStoreId = function(storeId) { + return storeId == PRIVATE_STORE; +}; + +global.isDefaultCookieStoreId = function(storeId) { + return storeId == DEFAULT_STORE; +}; + +global.isContainerCookieStoreId = function(storeId) { + return storeId !== null && storeId.startsWith(CONTAINER_STORE); +}; + +global.getContainerForCookieStoreId = function(storeId) { + if (!global.isContainerCookieStoreId(storeId)) { + return null; + } + + let containerId = storeId.substring(CONTAINER_STORE.length); + if (ContextualIdentityService.getIdentityFromId(containerId)) { + return parseInt(containerId, 10); + } + + return null; +}; + +global.isValidCookieStoreId = function(storeId) { + return global.isDefaultCookieStoreId(storeId) || + global.isPrivateCookieStoreId(storeId) || + global.isContainerCookieStoreId(storeId); +}; + +function convert({cookie, isPrivate}) { + let result = { + name: cookie.name, + value: cookie.value, + domain: cookie.host, + hostOnly: !cookie.isDomain, + path: cookie.path, + secure: cookie.isSecure, + httpOnly: cookie.isHttpOnly, + session: cookie.isSession, + }; + + if (!cookie.isSession) { + result.expirationDate = cookie.expiry; + } + + if (cookie.originAttributes.userContextId) { + result.storeId = CONTAINER_STORE + cookie.originAttributes.userContextId; + } else if (cookie.originAttributes.privateBrowsingId || isPrivate) { + result.storeId = PRIVATE_STORE; + } else { + result.storeId = DEFAULT_STORE; + } + + return result; +} + +function isSubdomain(otherDomain, baseDomain) { + return otherDomain == baseDomain || otherDomain.endsWith("." + baseDomain); +} + +// Checks that the given extension has permission to set the given cookie for +// the given URI. +function checkSetCookiePermissions(extension, uri, cookie) { + // Permission checks: + // + // - If the extension does not have permissions for the specified + // URL, it cannot set cookies for it. + // + // - If the specified URL could not set the given cookie, neither can + // the extension. + // + // Ideally, we would just have the cookie service make the latter + // determination, but that turns out to be quite complicated. At the + // moment, it requires constructing a cookie string and creating a + // dummy channel, both of which can be problematic. It also triggers + // a whole set of additional permission and preference checks, which + // may or may not be desirable. + // + // So instead, we do a similar set of checks here. Exactly what + // cookies a given URL should be able to set is not well-documented, + // and is not standardized in any standard that anyone actually + // follows. So instead, we follow the rules used by the cookie + // service. + // + // See source/netwerk/cookie/nsCookieService.cpp, in particular + // CheckDomain() and SetCookieInternal(). + + if (uri.scheme != "http" && uri.scheme != "https") { + return false; + } + + if (!extension.whiteListedHosts.matchesIgnoringPath(uri)) { + return false; + } + + if (!cookie.host) { + // If no explicit host is specified, this becomes a host-only cookie. + cookie.host = uri.host; + return true; + } + + // A leading "." is not expected, but is tolerated if it's not the only + // character in the host. If there is one, start by stripping it off. We'll + // add a new one on success. + if (cookie.host.length > 1) { + cookie.host = cookie.host.replace(/^\./, ""); + } + cookie.host = cookie.host.toLowerCase(); + + if (cookie.host != uri.host) { + // Not an exact match, so check for a valid subdomain. + let baseDomain; + try { + baseDomain = Services.eTLD.getBaseDomain(uri); + } catch (e) { + if (e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS || + e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + // The cookie service uses these to determine whether the domain + // requires an exact match. We already know we don't have an exact + // match, so return false. In all other cases, re-raise the error. + return false; + } + throw e; + } + + // The cookie domain must be a subdomain of the base domain. This prevents + // us from setting cookies for domains like ".co.uk". + // The domain of the requesting URL must likewise be a subdomain of the + // cookie domain. This prevents us from setting cookies for entirely + // unrelated domains. + if (!isSubdomain(cookie.host, baseDomain) || + !isSubdomain(uri.host, cookie.host)) { + return false; + } + + // RFC2109 suggests that we may only add cookies for sub-domains 1-level + // below us, but enforcing that would break the web, so we don't. + } + + // An explicit domain was passed, so add a leading "." to make this a + // domain cookie. + cookie.host = "." + cookie.host; + + // We don't do any significant checking of path permissions. RFC2109 + // suggests we only allow sites to add cookies for sub-paths, similar to + // same origin policy enforcement, but no-one implements this. + + return true; +} + +function* query(detailsIn, props, context) { + // Different callers want to filter on different properties. |props| + // tells us which ones they're interested in. + let details = {}; + props.forEach(property => { + if (detailsIn[property] !== null) { + details[property] = detailsIn[property]; + } + }); + + if ("domain" in details) { + details.domain = details.domain.toLowerCase().replace(/^\./, ""); + } + + let userContextId = 0; + let isPrivate = context.incognito; + if (details.storeId) { + if (!global.isValidCookieStoreId(details.storeId)) { + return; + } + + if (global.isDefaultCookieStoreId(details.storeId)) { + isPrivate = false; + } else if (global.isPrivateCookieStoreId(details.storeId)) { + isPrivate = true; + } else if (global.isContainerCookieStoreId(details.storeId)) { + isPrivate = false; + userContextId = global.getContainerForCookieStoreId(details.storeId); + if (!userContextId) { + return; + } + } + } + + let storeId = DEFAULT_STORE; + if (isPrivate) { + storeId = PRIVATE_STORE; + } else if ("storeId" in details) { + storeId = details.storeId; + } + + // We can use getCookiesFromHost for faster searching. + let enumerator; + let uri; + if ("url" in details) { + try { + uri = NetUtil.newURI(details.url).QueryInterface(Ci.nsIURL); + Services.cookies.usePrivateMode(isPrivate, () => { + enumerator = Services.cookies.getCookiesFromHost(uri.host, {userContextId}); + }); + } catch (ex) { + // This often happens for about: URLs + return; + } + } else if ("domain" in details) { + Services.cookies.usePrivateMode(isPrivate, () => { + enumerator = Services.cookies.getCookiesFromHost(details.domain, {userContextId}); + }); + } else { + Services.cookies.usePrivateMode(isPrivate, () => { + enumerator = Services.cookies.enumerator; + }); + } + + // Based on nsCookieService::GetCookieStringInternal + function matches(cookie) { + function domainMatches(host) { + return cookie.rawHost == host || (cookie.isDomain && host.endsWith(cookie.host)); + } + + function pathMatches(path) { + let cookiePath = cookie.path.replace(/\/$/, ""); + + if (!path.startsWith(cookiePath)) { + return false; + } + + // path == cookiePath, but without the redundant string compare. + if (path.length == cookiePath.length) { + return true; + } + + // URL path is a substring of the cookie path, so it matches if, and + // only if, the next character is a path delimiter. + let pathDelimiters = ["/", "?", "#", ";"]; + return pathDelimiters.includes(path[cookiePath.length]); + } + + // "Restricts the retrieved cookies to those that would match the given URL." + if (uri) { + if (!domainMatches(uri.host)) { + return false; + } + + if (cookie.isSecure && uri.scheme != "https") { + return false; + } + + if (!pathMatches(uri.path)) { + return false; + } + } + + if ("name" in details && details.name != cookie.name) { + return false; + } + + if (userContextId != cookie.originAttributes.userContextId) { + return false; + } + + // "Restricts the retrieved cookies to those whose domains match or are subdomains of this one." + if ("domain" in details && !isSubdomain(cookie.rawHost, details.domain)) { + return false; + } + + // "Restricts the retrieved cookies to those whose path exactly matches this string."" + if ("path" in details && details.path != cookie.path) { + return false; + } + + if ("secure" in details && details.secure != cookie.isSecure) { + return false; + } + + if ("session" in details && details.session != cookie.isSession) { + return false; + } + + // Check that the extension has permissions for this host. + if (!context.extension.whiteListedHosts.matchesCookie(cookie)) { + return false; + } + + return true; + } + + while (enumerator.hasMoreElements()) { + let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); + if (matches(cookie)) { + yield {cookie, isPrivate, storeId}; + } + } +} + +extensions.registerSchemaAPI("cookies", "addon_parent", context => { + let {extension} = context; + let self = { + cookies: { + get: function(details) { + // FIXME: We don't sort by length of path and creation time. + for (let cookie of query(details, ["url", "name", "storeId"], context)) { + return Promise.resolve(convert(cookie)); + } + + // Found no match. + return Promise.resolve(null); + }, + + getAll: function(details) { + let allowed = ["url", "name", "domain", "path", "secure", "session", "storeId"]; + let result = Array.from(query(details, allowed, context), convert); + + return Promise.resolve(result); + }, + + set: function(details) { + let uri = NetUtil.newURI(details.url).QueryInterface(Ci.nsIURL); + + let path; + if (details.path !== null) { + path = details.path; + } else { + // This interface essentially emulates the behavior of the + // Set-Cookie header. In the case of an omitted path, the cookie + // service uses the directory path of the requesting URL, ignoring + // any filename or query parameters. + path = uri.directory; + } + + let name = details.name !== null ? details.name : ""; + let value = details.value !== null ? details.value : ""; + let secure = details.secure !== null ? details.secure : false; + let httpOnly = details.httpOnly !== null ? details.httpOnly : false; + let isSession = details.expirationDate === null; + let expiry = isSession ? Number.MAX_SAFE_INTEGER : details.expirationDate; + let isPrivate = context.incognito; + let userContextId = 0; + if (global.isDefaultCookieStoreId(details.storeId)) { + isPrivate = false; + } else if (global.isPrivateCookieStoreId(details.storeId)) { + isPrivate = true; + } else if (global.isContainerCookieStoreId(details.storeId)) { + let containerId = global.getContainerForCookieStoreId(details.storeId); + if (containerId === null) { + return Promise.reject({message: `Illegal storeId: ${details.storeId}`}); + } + isPrivate = false; + userContextId = containerId; + } else if (details.storeId !== null) { + return Promise.reject({message: "Unknown storeId"}); + } + + let cookieAttrs = {host: details.domain, path: path, isSecure: secure}; + if (!checkSetCookiePermissions(extension, uri, cookieAttrs)) { + return Promise.reject({message: `Permission denied to set cookie ${JSON.stringify(details)}`}); + } + + // The permission check may have modified the domain, so use + // the new value instead. + Services.cookies.usePrivateMode(isPrivate, () => { + Services.cookies.add(cookieAttrs.host, path, name, value, + secure, httpOnly, isSession, expiry, {userContextId}); + }); + + return self.cookies.get(details); + }, + + remove: function(details) { + for (let {cookie, isPrivate, storeId} of query(details, ["url", "name", "storeId"], context)) { + Services.cookies.usePrivateMode(isPrivate, () => { + Services.cookies.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes); + }); + + // Todo: could there be multiple per subdomain? + return Promise.resolve({ + url: details.url, + name: details.name, + storeId, + }); + } + + return Promise.resolve(null); + }, + + getAllCookieStores: function() { + let data = {}; + for (let window of WindowListManager.browserWindows()) { + let tabs = TabManager.for(extension).getTabs(window); + for (let tab of tabs) { + if (!(tab.cookieStoreId in data)) { + data[tab.cookieStoreId] = []; + } + data[tab.cookieStoreId].push(tab); + } + } + + let result = []; + for (let key in data) { + result.push({id: key, tabIds: data[key], incognito: key == PRIVATE_STORE}); + } + return Promise.resolve(result); + }, + + onChanged: new EventManager(context, "cookies.onChanged", fire => { + let observer = (subject, topic, data) => { + let notify = (removed, cookie, cause) => { + cookie.QueryInterface(Ci.nsICookie2); + + if (extension.whiteListedHosts.matchesCookie(cookie)) { + fire({removed, cookie: convert({cookie, isPrivate: topic == "private-cookie-changed"}), cause}); + } + }; + + // We do our best effort here to map the incompatible states. + switch (data) { + case "deleted": + notify(true, subject, "explicit"); + break; + case "added": + notify(false, subject, "explicit"); + break; + case "changed": + notify(true, subject, "overwrite"); + notify(false, subject, "explicit"); + break; + case "batch-deleted": + subject.QueryInterface(Ci.nsIArray); + for (let i = 0; i < subject.length; i++) { + let cookie = subject.queryElementAt(i, Ci.nsICookie2); + if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) { + notify(true, cookie, "expired"); + } else { + notify(true, cookie, "evicted"); + } + } + break; + } + }; + + Services.obs.addObserver(observer, "cookie-changed", false); + Services.obs.addObserver(observer, "private-cookie-changed", false); + return () => { + Services.obs.removeObserver(observer, "cookie-changed"); + Services.obs.removeObserver(observer, "private-cookie-changed"); + }; + }).api(), + }, + }; + + return self; +}); diff --git a/toolkit/components/webextensions/ext-downloads.js b/toolkit/components/webextensions/ext-downloads.js new file mode 100644 index 000000000..132814ae4 --- /dev/null +++ b/toolkit/components/webextensions/ext-downloads.js @@ -0,0 +1,799 @@ +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths", + "resource://gre/modules/DownloadPaths.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter", + "resource://devtools/shared/event-emitter.js"); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +const { + ignoreEvent, + normalizeTime, + runSafeSync, + SingletonEventManager, + PlatformInfo, +} = ExtensionUtils; + +const DOWNLOAD_ITEM_FIELDS = ["id", "url", "referrer", "filename", "incognito", + "danger", "mime", "startTime", "endTime", + "estimatedEndTime", "state", + "paused", "canResume", "error", + "bytesReceived", "totalBytes", + "fileSize", "exists", + "byExtensionId", "byExtensionName"]; + +// Fields that we generate onChanged events for. +const DOWNLOAD_ITEM_CHANGE_FIELDS = ["endTime", "state", "paused", "canResume", + "error", "exists"]; + +// From https://fetch.spec.whatwg.org/#forbidden-header-name +const FORBIDDEN_HEADERS = ["ACCEPT-CHARSET", "ACCEPT-ENCODING", + "ACCESS-CONTROL-REQUEST-HEADERS", "ACCESS-CONTROL-REQUEST-METHOD", + "CONNECTION", "CONTENT-LENGTH", "COOKIE", "COOKIE2", "DATE", "DNT", + "EXPECT", "HOST", "KEEP-ALIVE", "ORIGIN", "REFERER", "TE", "TRAILER", + "TRANSFER-ENCODING", "UPGRADE", "VIA"]; + +const FORBIDDEN_PREFIXES = /^PROXY-|^SEC-/i; + +class DownloadItem { + constructor(id, download, extension) { + this.id = id; + this.download = download; + this.extension = extension; + this.prechange = {}; + } + + get url() { return this.download.source.url; } + get referrer() { return this.download.source.referrer; } + get filename() { return this.download.target.path; } + get incognito() { return this.download.source.isPrivate; } + get danger() { return "safe"; } // TODO + get mime() { return this.download.contentType; } + get startTime() { return this.download.startTime; } + get endTime() { return null; } // TODO + get estimatedEndTime() { return null; } // TODO + get state() { + if (this.download.succeeded) { + return "complete"; + } + if (this.download.canceled) { + return "interrupted"; + } + return "in_progress"; + } + get paused() { + return this.download.canceled && this.download.hasPartialData && !this.download.error; + } + get canResume() { + return (this.download.stopped || this.download.canceled) && + this.download.hasPartialData && !this.download.error; + } + get error() { + if (!this.download.stopped || this.download.succeeded) { + return null; + } + // TODO store this instead of calculating it + + if (this.download.error) { + if (this.download.error.becauseSourceFailed) { + return "NETWORK_FAILED"; // TODO + } + if (this.download.error.becauseTargetFailed) { + return "FILE_FAILED"; // TODO + } + return "CRASH"; + } + return "USER_CANCELED"; + } + get bytesReceived() { + return this.download.currentBytes; + } + get totalBytes() { + return this.download.hasProgress ? this.download.totalBytes : -1; + } + get fileSize() { + // todo: this is supposed to be post-compression + return this.download.succeeded ? this.download.target.size : -1; + } + get exists() { return this.download.target.exists; } + get byExtensionId() { return this.extension ? this.extension.id : undefined; } + get byExtensionName() { return this.extension ? this.extension.name : undefined; } + + /** + * Create a cloneable version of this object by pulling all the + * fields into simple properties (instead of getters). + * + * @returns {object} A DownloadItem with flat properties, + * suitable for cloning. + */ + serialize() { + let obj = {}; + for (let field of DOWNLOAD_ITEM_FIELDS) { + obj[field] = this[field]; + } + if (obj.startTime) { + obj.startTime = obj.startTime.toISOString(); + } + return obj; + } + + // When a change event fires, handlers can look at how an individual + // field changed by comparing item.fieldname with item.prechange.fieldname. + // After all handlers have been invoked, this gets called to store the + // current values of all fields ahead of the next event. + _change() { + for (let field of DOWNLOAD_ITEM_CHANGE_FIELDS) { + this.prechange[field] = this[field]; + } + } +} + + +// DownloadMap maps back and forth betwen the numeric identifiers used in +// the downloads WebExtension API and a Download object from the Downloads jsm. +// todo: make id and extension info persistent (bug 1247794) +const DownloadMap = { + currentId: 0, + loadPromise: null, + + // Maps numeric id -> DownloadItem + byId: new Map(), + + // Maps Download object -> DownloadItem + byDownload: new WeakMap(), + + lazyInit() { + if (this.loadPromise == null) { + EventEmitter.decorate(this); + this.loadPromise = Downloads.getList(Downloads.ALL).then(list => { + let self = this; + return list.addView({ + onDownloadAdded(download) { + const item = self.newFromDownload(download, null); + self.emit("create", item); + }, + + onDownloadRemoved(download) { + const item = self.byDownload.get(download); + if (item != null) { + self.emit("erase", item); + self.byDownload.delete(download); + self.byId.delete(item.id); + } + }, + + onDownloadChanged(download) { + const item = self.byDownload.get(download); + if (item == null) { + Cu.reportError("Got onDownloadChanged for unknown download object"); + } else { + // We get the first one of these when the download is started. + // In this case, don't emit anything, just initialize prechange. + if (Object.keys(item.prechange).length > 0) { + self.emit("change", item); + } + item._change(); + } + }, + }).then(() => list.getAll()) + .then(downloads => { + downloads.forEach(download => { + this.newFromDownload(download, null); + }); + }) + .then(() => list); + }); + } + return this.loadPromise; + }, + + getDownloadList() { + return this.lazyInit(); + }, + + getAll() { + return this.lazyInit().then(() => this.byId.values()); + }, + + fromId(id) { + const download = this.byId.get(id); + if (!download) { + throw new Error(`Invalid download id ${id}`); + } + return download; + }, + + newFromDownload(download, extension) { + if (this.byDownload.has(download)) { + return this.byDownload.get(download); + } + + const id = ++this.currentId; + let item = new DownloadItem(id, download, extension); + this.byId.set(id, item); + this.byDownload.set(download, item); + return item; + }, + + erase(item) { + // This will need to get more complicated for bug 1255507 but for now we + // only work with downloads in the DownloadList from getAll() + return this.getDownloadList().then(list => { + list.remove(item.download); + }); + }, +}; + +// Create a callable function that filters a DownloadItem based on a +// query object of the type passed to search() or erase(). +function downloadQuery(query) { + let queryTerms = []; + let queryNegativeTerms = []; + if (query.query != null) { + for (let term of query.query) { + if (term[0] == "-") { + queryNegativeTerms.push(term.slice(1).toLowerCase()); + } else { + queryTerms.push(term.toLowerCase()); + } + } + } + + function normalizeDownloadTime(arg, before) { + if (arg == null) { + return before ? Number.MAX_VALUE : 0; + } + return normalizeTime(arg).getTime(); + } + + const startedBefore = normalizeDownloadTime(query.startedBefore, true); + const startedAfter = normalizeDownloadTime(query.startedAfter, false); + // const endedBefore = normalizeDownloadTime(query.endedBefore, true); + // const endedAfter = normalizeDownloadTime(query.endedAfter, false); + + const totalBytesGreater = query.totalBytesGreater || 0; + const totalBytesLess = (query.totalBytesLess != null) + ? query.totalBytesLess : Number.MAX_VALUE; + + // Handle options for which we can have a regular expression and/or + // an explicit value to match. + function makeMatch(regex, value, field) { + if (value == null && regex == null) { + return input => true; + } + + let re; + try { + re = new RegExp(regex || "", "i"); + } catch (err) { + throw new Error(`Invalid ${field}Regex: ${err.message}`); + } + if (value == null) { + return input => re.test(input); + } + + value = value.toLowerCase(); + if (re.test(value)) { + return input => (value == input); + } + return input => false; + } + + const matchFilename = makeMatch(query.filenameRegex, query.filename, "filename"); + const matchUrl = makeMatch(query.urlRegex, query.url, "url"); + + return function(item) { + const url = item.url.toLowerCase(); + const filename = item.filename.toLowerCase(); + + if (!queryTerms.every(term => url.includes(term) || filename.includes(term))) { + return false; + } + + if (queryNegativeTerms.some(term => url.includes(term) || filename.includes(term))) { + return false; + } + + if (!matchFilename(filename) || !matchUrl(url)) { + return false; + } + + if (!item.startTime) { + if (query.startedBefore != null || query.startedAfter != null) { + return false; + } + } else if (item.startTime > startedBefore || item.startTime < startedAfter) { + return false; + } + + // todo endedBefore, endedAfter + + if (item.totalBytes == -1) { + if (query.totalBytesGreater != null || query.totalBytesLess != null) { + return false; + } + } else if (item.totalBytes <= totalBytesGreater || item.totalBytes >= totalBytesLess) { + return false; + } + + // todo: include danger + const SIMPLE_ITEMS = ["id", "mime", "startTime", "endTime", "state", + "paused", "error", + "bytesReceived", "totalBytes", "fileSize", "exists"]; + for (let field of SIMPLE_ITEMS) { + if (query[field] != null && item[field] != query[field]) { + return false; + } + } + + return true; + }; +} + +function queryHelper(query) { + let matchFn; + try { + matchFn = downloadQuery(query); + } catch (err) { + return Promise.reject({message: err.message}); + } + + let compareFn; + if (query.orderBy != null) { + const fields = query.orderBy.map(field => field[0] == "-" + ? {reverse: true, name: field.slice(1)} + : {reverse: false, name: field}); + + for (let field of fields) { + if (!DOWNLOAD_ITEM_FIELDS.includes(field.name)) { + return Promise.reject({message: `Invalid orderBy field ${field.name}`}); + } + } + + compareFn = (dl1, dl2) => { + for (let field of fields) { + const val1 = dl1[field.name]; + const val2 = dl2[field.name]; + + if (val1 < val2) { + return field.reverse ? 1 : -1; + } else if (val1 > val2) { + return field.reverse ? -1 : 1; + } + } + return 0; + }; + } + + return DownloadMap.getAll().then(downloads => { + if (compareFn) { + downloads = Array.from(downloads); + downloads.sort(compareFn); + } + let results = []; + for (let download of downloads) { + if (query.limit && results.length >= query.limit) { + break; + } + if (matchFn(download)) { + results.push(download); + } + } + return results; + }); +} + +extensions.registerSchemaAPI("downloads", "addon_parent", context => { + let {extension} = context; + return { + downloads: { + download(options) { + let {filename} = options; + if (filename && PlatformInfo.os === "win") { + // cross platform javascript code uses "/" + filename = filename.replace(/\//g, "\\"); + } + + if (filename != null) { + if (filename.length == 0) { + return Promise.reject({message: "filename must not be empty"}); + } + + let path = OS.Path.split(filename); + if (path.absolute) { + return Promise.reject({message: "filename must not be an absolute path"}); + } + + if (path.components.some(component => component == "..")) { + return Promise.reject({message: "filename must not contain back-references (..)"}); + } + } + + if (options.conflictAction == "prompt") { + // TODO + return Promise.reject({message: "conflictAction prompt not yet implemented"}); + } + + if (options.headers) { + for (let {name} of options.headers) { + if (FORBIDDEN_HEADERS.includes(name.toUpperCase()) || name.match(FORBIDDEN_PREFIXES)) { + return Promise.reject({message: "Forbidden request header name"}); + } + } + } + + // Handle method, headers and body options. + function adjustChannel(channel) { + if (channel instanceof Ci.nsIHttpChannel) { + const method = options.method || "GET"; + channel.requestMethod = method; + + if (options.headers) { + for (let {name, value} of options.headers) { + channel.setRequestHeader(name, value, false); + } + } + + if (options.body != null) { + const stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.setData(options.body, options.body.length); + + channel.QueryInterface(Ci.nsIUploadChannel2); + channel.explicitSetUploadStream(stream, null, -1, method, false); + } + } + return Promise.resolve(); + } + + function createTarget(downloadsDir) { + let target; + if (filename) { + target = OS.Path.join(downloadsDir, filename); + } else { + let uri = NetUtil.newURI(options.url); + + let remote = "download"; + if (uri instanceof Ci.nsIURL) { + remote = uri.fileName; + } + target = OS.Path.join(downloadsDir, remote); + } + + // Create any needed subdirectories if required by filename. + const dir = OS.Path.dirname(target); + return OS.File.makeDir(dir, {from: downloadsDir}).then(() => { + return OS.File.exists(target); + }).then(exists => { + // This has a race, something else could come along and create + // the file between this test and them time the download code + // creates the target file. But we can't easily fix it without + // modifying DownloadCore so we live with it for now. + if (exists) { + switch (options.conflictAction) { + case "uniquify": + default: + target = DownloadPaths.createNiceUniqueFile(new FileUtils.File(target)).path; + break; + + case "overwrite": + break; + } + } + }).then(() => { + if (!options.saveAs) { + return Promise.resolve(target); + } + + // Setup the file picker Save As dialog. + const picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + const window = Services.wm.getMostRecentWindow("navigator:browser"); + picker.init(window, null, Ci.nsIFilePicker.modeSave); + picker.displayDirectory = new FileUtils.File(dir); + picker.appendFilters(Ci.nsIFilePicker.filterAll); + picker.defaultString = OS.Path.basename(target); + + // Open the dialog and resolve/reject with the result. + return new Promise((resolve, reject) => { + picker.open(result => { + if (result === Ci.nsIFilePicker.returnCancel) { + reject({message: "Download canceled by the user"}); + } else { + resolve(picker.file.path); + } + }); + }); + }); + } + + let download; + return Downloads.getPreferredDownloadsDirectory() + .then(downloadsDir => createTarget(downloadsDir)) + .then(target => { + const source = { + url: options.url, + }; + + if (options.method || options.headers || options.body) { + source.adjustChannel = adjustChannel; + } + + return Downloads.createDownload({ + source, + target: { + path: target, + partFilePath: target + ".part", + }, + }); + }).then(dl => { + download = dl; + return DownloadMap.getDownloadList(); + }).then(list => { + list.add(download); + + // This is necessary to make pause/resume work. + download.tryToKeepPartialData = true; + download.start(); + + const item = DownloadMap.newFromDownload(download, extension); + return item.id; + }); + }, + + removeFile(id) { + return DownloadMap.lazyInit().then(() => { + let item; + try { + item = DownloadMap.fromId(id); + } catch (err) { + return Promise.reject({message: `Invalid download id ${id}`}); + } + if (item.state !== "complete") { + return Promise.reject({message: `Cannot remove incomplete download id ${id}`}); + } + return OS.File.remove(item.filename, {ignoreAbsent: false}).catch((err) => { + return Promise.reject({message: `Could not remove download id ${item.id} because the file doesn't exist`}); + }); + }); + }, + + search(query) { + return queryHelper(query) + .then(items => items.map(item => item.serialize())); + }, + + pause(id) { + return DownloadMap.lazyInit().then(() => { + let item; + try { + item = DownloadMap.fromId(id); + } catch (err) { + return Promise.reject({message: `Invalid download id ${id}`}); + } + if (item.state != "in_progress") { + return Promise.reject({message: `Download ${id} cannot be paused since it is in state ${item.state}`}); + } + + return item.download.cancel(); + }); + }, + + resume(id) { + return DownloadMap.lazyInit().then(() => { + let item; + try { + item = DownloadMap.fromId(id); + } catch (err) { + return Promise.reject({message: `Invalid download id ${id}`}); + } + if (!item.canResume) { + return Promise.reject({message: `Download ${id} cannot be resumed`}); + } + + return item.download.start(); + }); + }, + + cancel(id) { + return DownloadMap.lazyInit().then(() => { + let item; + try { + item = DownloadMap.fromId(id); + } catch (err) { + return Promise.reject({message: `Invalid download id ${id}`}); + } + if (item.download.succeeded) { + return Promise.reject({message: `Download ${id} is already complete`}); + } + return item.download.finalize(true); + }); + }, + + showDefaultFolder() { + Downloads.getPreferredDownloadsDirectory().then(dir => { + let dirobj = new FileUtils.File(dir); + if (dirobj.isDirectory()) { + dirobj.launch(); + } else { + throw new Error(`Download directory ${dirobj.path} is not actually a directory`); + } + }).catch(Cu.reportError); + }, + + erase(query) { + return queryHelper(query).then(items => { + let results = []; + let promises = []; + for (let item of items) { + promises.push(DownloadMap.erase(item)); + results.push(item.id); + } + return Promise.all(promises).then(() => results); + }); + }, + + open(downloadId) { + return DownloadMap.lazyInit().then(() => { + let download = DownloadMap.fromId(downloadId).download; + if (download.succeeded) { + return download.launch(); + } + return Promise.reject({message: "Download has not completed."}); + }).catch((error) => { + return Promise.reject({message: error.message}); + }); + }, + + show(downloadId) { + return DownloadMap.lazyInit().then(() => { + let download = DownloadMap.fromId(downloadId); + return download.download.showContainingDirectory(); + }).then(() => { + return true; + }).catch(error => { + return Promise.reject({message: error.message}); + }); + }, + + getFileIcon(downloadId, options) { + return DownloadMap.lazyInit().then(() => { + let size = options && options.size ? options.size : 32; + let download = DownloadMap.fromId(downloadId).download; + let pathPrefix = ""; + let path; + + if (download.succeeded) { + let file = FileUtils.File(download.target.path); + path = Services.io.newFileURI(file).spec; + } else { + path = OS.Path.basename(download.target.path); + pathPrefix = "//"; + } + + return new Promise((resolve, reject) => { + let chromeWebNav = Services.appShell.createWindowlessBrowser(true); + chromeWebNav + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .createAboutBlankContentViewer(Services.scriptSecurityManager.getSystemPrincipal()); + + let img = chromeWebNav.document.createElement("img"); + img.width = size; + img.height = size; + + let handleLoad; + let handleError; + const cleanup = () => { + img.removeEventListener("load", handleLoad); + img.removeEventListener("error", handleError); + chromeWebNav.close(); + chromeWebNav = null; + }; + + handleLoad = () => { + let canvas = chromeWebNav.document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + let context = canvas.getContext("2d"); + context.drawImage(img, 0, 0, size, size); + let dataURL = canvas.toDataURL("image/png"); + cleanup(); + resolve(dataURL); + }; + + handleError = (error) => { + Cu.reportError(error); + cleanup(); + reject(new Error("An unexpected error occurred")); + }; + + img.addEventListener("load", handleLoad); + img.addEventListener("error", handleError); + img.src = `moz-icon:${pathPrefix}${path}?size=${size}`; + }); + }).catch((error) => { + return Promise.reject({message: error.message}); + }); + }, + + // When we do setShelfEnabled(), check for additional "downloads.shelf" permission. + // i.e.: + // setShelfEnabled(enabled) { + // if (!extension.hasPermission("downloads.shelf")) { + // throw new context.cloneScope.Error("Permission denied because 'downloads.shelf' permission is missing."); + // } + // ... + // } + + onChanged: new SingletonEventManager(context, "downloads.onChanged", fire => { + const handler = (what, item) => { + let changes = {}; + const noundef = val => (val === undefined) ? null : val; + DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => { + if (item[fld] != item.prechange[fld]) { + changes[fld] = { + previous: noundef(item.prechange[fld]), + current: noundef(item[fld]), + }; + } + }); + if (Object.keys(changes).length > 0) { + changes.id = item.id; + runSafeSync(context, fire, changes); + } + }; + + let registerPromise = DownloadMap.getDownloadList().then(() => { + DownloadMap.on("change", handler); + }); + return () => { + registerPromise.then(() => { + DownloadMap.off("change", handler); + }); + }; + }).api(), + + onCreated: new SingletonEventManager(context, "downloads.onCreated", fire => { + const handler = (what, item) => { + runSafeSync(context, fire, item.serialize()); + }; + let registerPromise = DownloadMap.getDownloadList().then(() => { + DownloadMap.on("create", handler); + }); + return () => { + registerPromise.then(() => { + DownloadMap.off("create", handler); + }); + }; + }).api(), + + onErased: new SingletonEventManager(context, "downloads.onErased", fire => { + const handler = (what, item) => { + runSafeSync(context, fire, item.id); + }; + let registerPromise = DownloadMap.getDownloadList().then(() => { + DownloadMap.on("erase", handler); + }); + return () => { + registerPromise.then(() => { + DownloadMap.off("erase", handler); + }); + }; + }).api(), + + onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"), + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-extension.js b/toolkit/components/webextensions/ext-extension.js new file mode 100644 index 000000000..c4bdc8b63 --- /dev/null +++ b/toolkit/components/webextensions/ext-extension.js @@ -0,0 +1,20 @@ +"use strict"; + +extensions.registerSchemaAPI("extension", "addon_parent", context => { + return { + extension: { + get lastError() { + return context.lastError; + }, + + isAllowedIncognitoAccess() { + return Promise.resolve(true); + }, + + isAllowedFileSchemeAccess() { + return Promise.resolve(false); + }, + }, + }; +}); + diff --git a/toolkit/components/webextensions/ext-i18n.js b/toolkit/components/webextensions/ext-i18n.js new file mode 100644 index 000000000..bb4bde4bd --- /dev/null +++ b/toolkit/components/webextensions/ext-i18n.js @@ -0,0 +1,34 @@ +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +var { + detectLanguage, +} = ExtensionUtils; + +function i18nApiFactory(context) { + let {extension} = context; + return { + i18n: { + getMessage: function(messageName, substitutions) { + return extension.localizeMessage(messageName, substitutions, {cloneScope: context.cloneScope}); + }, + + getAcceptLanguages: function() { + let result = extension.localeData.acceptLanguages; + return Promise.resolve(result); + }, + + getUILanguage: function() { + return extension.localeData.uiLocale; + }, + + detectLanguage: function(text) { + return detectLanguage(text); + }, + }, + }; +} +extensions.registerSchemaAPI("i18n", "addon_child", i18nApiFactory); +extensions.registerSchemaAPI("i18n", "content_child", i18nApiFactory); diff --git a/toolkit/components/webextensions/ext-idle.js b/toolkit/components/webextensions/ext-idle.js new file mode 100644 index 000000000..c5be4b600 --- /dev/null +++ b/toolkit/components/webextensions/ext-idle.js @@ -0,0 +1,94 @@ +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter", + "resource://devtools/shared/event-emitter.js"); +XPCOMUtils.defineLazyServiceGetter(this, "idleService", + "@mozilla.org/widget/idleservice;1", + "nsIIdleService"); +const { + SingletonEventManager, +} = ExtensionUtils; + +// WeakMap[Extension -> Object] +var observersMap = new WeakMap(); + +function getObserverInfo(extension, context) { + let observerInfo = observersMap.get(extension); + if (!observerInfo) { + observerInfo = { + observer: null, + detectionInterval: 60, + }; + observersMap.set(extension, observerInfo); + context.callOnClose({ + close: () => { + let {observer, detectionInterval} = observersMap.get(extension); + if (observer) { + idleService.removeIdleObserver(observer, detectionInterval); + } + observersMap.delete(extension); + }, + }); + } + return observerInfo; +} + +function getObserver(extension, context) { + let observerInfo = getObserverInfo(extension, context); + let {observer, detectionInterval} = observerInfo; + if (!observer) { + observer = { + observe: function(subject, topic, data) { + if (topic == "idle" || topic == "active") { + this.emit("stateChanged", topic); + } + }, + }; + EventEmitter.decorate(observer); + idleService.addIdleObserver(observer, detectionInterval); + observerInfo.observer = observer; + observerInfo.detectionInterval = detectionInterval; + } + return observer; +} + +function setDetectionInterval(extension, context, newInterval) { + let observerInfo = getObserverInfo(extension, context); + let {observer, detectionInterval} = observerInfo; + if (observer) { + idleService.removeIdleObserver(observer, detectionInterval); + idleService.addIdleObserver(observer, newInterval); + } + observerInfo.detectionInterval = newInterval; +} + +extensions.registerSchemaAPI("idle", "addon_parent", context => { + let {extension} = context; + return { + idle: { + queryState: function(detectionIntervalInSeconds) { + if (idleService.idleTime < detectionIntervalInSeconds * 1000) { + return Promise.resolve("active"); + } + return Promise.resolve("idle"); + }, + setDetectionInterval: function(detectionIntervalInSeconds) { + setDetectionInterval(extension, context, detectionIntervalInSeconds); + }, + onStateChanged: new SingletonEventManager(context, "idle.onStateChanged", fire => { + let listener = (event, data) => { + context.runSafe(fire, data); + }; + + getObserver(extension, context).on("stateChanged", listener); + return () => { + getObserver(extension, context).off("stateChanged", listener); + }; + }).api(), + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-management.js b/toolkit/components/webextensions/ext-management.js new file mode 100644 index 000000000..59a7959d7 --- /dev/null +++ b/toolkit/components/webextensions/ext-management.js @@ -0,0 +1,109 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +XPCOMUtils.defineLazyGetter(this, "strBundle", function() { + const stringSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService); + return stringSvc.createBundle("chrome://global/locale/extensions.properties"); +}); +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "promptService", + "@mozilla.org/embedcomp/prompt-service;1", + "nsIPromptService"); + +function _(key, ...args) { + if (args.length) { + return strBundle.formatStringFromName(key, args, args.length); + } + return strBundle.GetStringFromName(key); +} + +function installType(addon) { + if (addon.temporarilyInstalled) { + return "development"; + } else if (addon.foreignInstall) { + return "sideload"; + } else if (addon.isSystem) { + return "other"; + } + return "normal"; +} + +extensions.registerSchemaAPI("management", "addon_parent", context => { + let {extension} = context; + return { + management: { + getSelf: function() { + return new Promise((resolve, reject) => AddonManager.getAddonByID(extension.id, addon => { + try { + let m = extension.manifest; + let extInfo = { + id: extension.id, + name: addon.name, + shortName: m.short_name || "", + description: addon.description || "", + version: addon.version, + mayDisable: !!(addon.permissions & AddonManager.PERM_CAN_DISABLE), + enabled: addon.isActive, + optionsUrl: addon.optionsURL || "", + permissions: Array.from(extension.permissions).filter(perm => { + return !extension.whiteListedHosts.pat.includes(perm); + }), + hostPermissions: extension.whiteListedHosts.pat, + installType: installType(addon), + }; + if (addon.homepageURL) { + extInfo.homepageUrl = addon.homepageURL; + } + if (addon.updateURL) { + extInfo.updateUrl = addon.updateURL; + } + if (m.icons) { + extInfo.icons = Object.keys(m.icons).map(key => { + return {size: Number(key), url: m.icons[key]}; + }); + } + + resolve(extInfo); + } catch (err) { + reject(err); + } + })); + }, + + uninstallSelf: function(options) { + return new Promise((resolve, reject) => { + if (options && options.showConfirmDialog) { + let message = _("uninstall.confirmation.message", extension.name); + if (options.dialogMessage) { + message = `${options.dialogMessage}\n${message}`; + } + let title = _("uninstall.confirmation.title", extension.name); + let buttonFlags = promptService.BUTTON_POS_0 * promptService.BUTTON_TITLE_IS_STRING + + promptService.BUTTON_POS_1 * promptService.BUTTON_TITLE_IS_STRING; + let button0Title = _("uninstall.confirmation.button-0.label"); + let button1Title = _("uninstall.confirmation.button-1.label"); + let response = promptService.confirmEx(null, title, message, buttonFlags, button0Title, button1Title, null, null, {value: 0}); + if (response == 1) { + return reject({message: "User cancelled uninstall of extension"}); + } + } + AddonManager.getAddonByID(extension.id, addon => { + let canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL); + if (!canUninstall) { + return reject({message: "The add-on cannot be uninstalled"}); + } + try { + addon.uninstall(); + } catch (err) { + return reject(err); + } + }); + }); + }, + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-notifications.js b/toolkit/components/webextensions/ext-notifications.js new file mode 100644 index 000000000..1df96a2ac --- /dev/null +++ b/toolkit/components/webextensions/ext-notifications.js @@ -0,0 +1,161 @@ +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter", + "resource://devtools/shared/event-emitter.js"); + +var { + EventManager, + ignoreEvent, +} = ExtensionUtils; + +// WeakMap[Extension -> Map[id -> Notification]] +var notificationsMap = new WeakMap(); + +// Manages a notification popup (notifications API) created by the extension. +function Notification(extension, id, options) { + this.extension = extension; + this.id = id; + this.options = options; + + let imageURL; + if (options.iconUrl) { + imageURL = this.extension.baseURI.resolve(options.iconUrl); + } + + try { + let svc = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); + svc.showAlertNotification(imageURL, + options.title, + options.message, + true, // textClickable + this.id, + this, + this.id); + } catch (e) { + // This will fail if alerts aren't available on the system. + } +} + +Notification.prototype = { + clear() { + try { + let svc = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); + svc.closeAlert(this.id); + } catch (e) { + // This will fail if the OS doesn't support this function. + } + notificationsMap.get(this.extension).delete(this.id); + }, + + observe(subject, topic, data) { + let notifications = notificationsMap.get(this.extension); + + let emitAndDelete = event => { + notifications.emit(event, data); + notifications.delete(this.id); + }; + + // Don't try to emit events if the extension has been unloaded + if (!notifications) { + return; + } + + if (topic === "alertclickcallback") { + emitAndDelete("clicked"); + } + if (topic === "alertfinished") { + emitAndDelete("closed"); + } + }, +}; + +/* eslint-disable mozilla/balanced-listeners */ +extensions.on("startup", (type, extension) => { + let map = new Map(); + EventEmitter.decorate(map); + notificationsMap.set(extension, map); +}); + +extensions.on("shutdown", (type, extension) => { + if (notificationsMap.has(extension)) { + for (let notification of notificationsMap.get(extension).values()) { + notification.clear(); + } + notificationsMap.delete(extension); + } +}); +/* eslint-enable mozilla/balanced-listeners */ + +var nextId = 0; + +extensions.registerSchemaAPI("notifications", "addon_parent", context => { + let {extension} = context; + return { + notifications: { + create: function(notificationId, options) { + if (!notificationId) { + notificationId = String(nextId++); + } + + let notifications = notificationsMap.get(extension); + if (notifications.has(notificationId)) { + notifications.get(notificationId).clear(); + } + + // FIXME: Lots of options still aren't supported, especially + // buttons. + let notification = new Notification(extension, notificationId, options); + notificationsMap.get(extension).set(notificationId, notification); + + return Promise.resolve(notificationId); + }, + + clear: function(notificationId) { + let notifications = notificationsMap.get(extension); + if (notifications.has(notificationId)) { + notifications.get(notificationId).clear(); + return Promise.resolve(true); + } + return Promise.resolve(false); + }, + + getAll: function() { + let result = {}; + notificationsMap.get(extension).forEach((value, key) => { + result[key] = value.options; + }); + return Promise.resolve(result); + }, + + onClosed: new EventManager(context, "notifications.onClosed", fire => { + let listener = (event, notificationId) => { + // FIXME: Support the byUser argument. + fire(notificationId, true); + }; + + notificationsMap.get(extension).on("closed", listener); + return () => { + notificationsMap.get(extension).off("closed", listener); + }; + }).api(), + + onClicked: new EventManager(context, "notifications.onClicked", fire => { + let listener = (event, notificationId) => { + fire(notificationId, true); + }; + + notificationsMap.get(extension).on("clicked", listener); + return () => { + notificationsMap.get(extension).off("clicked", listener); + }; + }).api(), + + // Intend to implement this later: https://bugzilla.mozilla.org/show_bug.cgi?id=1190681 + onButtonClicked: ignoreEvent(context, "notifications.onButtonClicked"), + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-runtime.js b/toolkit/components/webextensions/ext-runtime.js new file mode 100644 index 000000000..aed3ffd4b --- /dev/null +++ b/toolkit/components/webextensions/ext-runtime.js @@ -0,0 +1,134 @@ +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Extension", + "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement", + "resource://gre/modules/ExtensionManagement.jsm"); + +var { + SingletonEventManager, +} = ExtensionUtils; + +extensions.registerSchemaAPI("runtime", "addon_parent", context => { + let {extension} = context; + return { + runtime: { + onStartup: new SingletonEventManager(context, "runtime.onStartup", fire => { + if (context.incognito) { + // This event should not fire if we are operating in a private profile. + return () => {}; + } + let listener = () => { + if (extension.startupReason === "APP_STARTUP") { + fire(); + } + }; + extension.on("startup", listener); + return () => { + extension.off("startup", listener); + }; + }).api(), + + onInstalled: new SingletonEventManager(context, "runtime.onInstalled", fire => { + let listener = () => { + switch (extension.startupReason) { + case "APP_STARTUP": + if (Extension.browserUpdated) { + fire({reason: "browser_update"}); + } + break; + case "ADDON_INSTALL": + fire({reason: "install"}); + break; + case "ADDON_UPGRADE": + fire({reason: "update"}); + break; + } + }; + extension.on("startup", listener); + return () => { + extension.off("startup", listener); + }; + }).api(), + + onUpdateAvailable: new SingletonEventManager(context, "runtime.onUpdateAvailable", fire => { + let instanceID = extension.addonData.instanceID; + AddonManager.addUpgradeListener(instanceID, upgrade => { + extension.upgrade = upgrade; + let details = { + version: upgrade.version, + }; + context.runSafe(fire, details); + }); + return () => { + AddonManager.removeUpgradeListener(instanceID); + }; + }).api(), + + reload: () => { + if (extension.upgrade) { + // If there is a pending update, install it now. + extension.upgrade.install(); + } else { + // Otherwise, reload the current extension. + AddonManager.getAddonByID(extension.id, addon => { + addon.reload(); + }); + } + }, + + get lastError() { + // TODO(robwu): Figure out how to make sure that errors in the parent + // process are propagated to the child process. + // lastError should not be accessed from the parent. + return context.lastError; + }, + + getBrowserInfo: function() { + const {name, vendor, version, appBuildID} = Services.appinfo; + const info = {name, vendor, version, buildID: appBuildID}; + return Promise.resolve(info); + }, + + getPlatformInfo: function() { + return Promise.resolve(ExtensionUtils.PlatformInfo); + }, + + openOptionsPage: function() { + if (!extension.manifest.options_ui) { + return Promise.reject({message: "No `options_ui` declared"}); + } + + return openOptionsPage(extension).then(() => {}); + }, + + setUninstallURL: function(url) { + if (url.length == 0) { + return Promise.resolve(); + } + + let uri; + try { + uri = NetUtil.newURI(url); + } catch (e) { + return Promise.reject({message: `Invalid URL: ${JSON.stringify(url)}`}); + } + + if (uri.scheme != "http" && uri.scheme != "https") { + return Promise.reject({message: "url must have the scheme http or https"}); + } + + extension.uninstallURL = url; + return Promise.resolve(); + }, + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-storage.js b/toolkit/components/webextensions/ext-storage.js new file mode 100644 index 000000000..b1e22c46c --- /dev/null +++ b/toolkit/components/webextensions/ext-storage.js @@ -0,0 +1,46 @@ +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage", + "resource://gre/modules/ExtensionStorage.jsm"); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +var { + EventManager, +} = ExtensionUtils; + +function storageApiFactory(context) { + let {extension} = context; + return { + storage: { + local: { + get: function(spec) { + return ExtensionStorage.get(extension.id, spec); + }, + set: function(items) { + return ExtensionStorage.set(extension.id, items, context); + }, + remove: function(keys) { + return ExtensionStorage.remove(extension.id, keys); + }, + clear: function() { + return ExtensionStorage.clear(extension.id); + }, + }, + + onChanged: new EventManager(context, "storage.onChanged", fire => { + let listenerLocal = changes => { + fire(changes, "local"); + }; + + ExtensionStorage.addOnChangedListener(extension.id, listenerLocal); + return () => { + ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal); + }; + }).api(), + }, + }; +} +extensions.registerSchemaAPI("storage", "addon_parent", storageApiFactory); +extensions.registerSchemaAPI("storage", "content_parent", storageApiFactory); diff --git a/toolkit/components/webextensions/ext-topSites.js b/toolkit/components/webextensions/ext-topSites.js new file mode 100644 index 000000000..a66ac85d9 --- /dev/null +++ b/toolkit/components/webextensions/ext-topSites.js @@ -0,0 +1,24 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils", + "resource://gre/modules/NewTabUtils.jsm"); + +extensions.registerSchemaAPI("topSites", "addon_parent", context => { + return { + topSites: { + get: function() { + let urls = NewTabUtils.links.getLinks() + .filter(link => !!link) + .map(link => { + return { + url: link.url, + title: link.title, + }; + }); + return Promise.resolve(urls); + }, + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-webNavigation.js b/toolkit/components/webextensions/ext-webNavigation.js new file mode 100644 index 000000000..904f3a4a7 --- /dev/null +++ b/toolkit/components/webextensions/ext-webNavigation.js @@ -0,0 +1,192 @@ +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement", + "resource://gre/modules/ExtensionManagement.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "MatchURLFilters", + "resource://gre/modules/MatchPattern.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation", + "resource://gre/modules/WebNavigation.jsm"); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +var { + SingletonEventManager, + ignoreEvent, +} = ExtensionUtils; + +const defaultTransitionTypes = { + topFrame: "link", + subFrame: "auto_subframe", +}; + +const frameTransitions = { + anyFrame: { + qualifiers: ["server_redirect", "client_redirect", "forward_back"], + }, + topFrame: { + types: ["reload", "form_submit"], + }, +}; + +const tabTransitions = { + topFrame: { + qualifiers: ["from_address_bar"], + types: ["auto_bookmark", "typed", "keyword", "generated", "link"], + }, + subFrame: { + types: ["manual_subframe"], + }, +}; + +function isTopLevelFrame({frameId, parentFrameId}) { + return frameId == 0 && parentFrameId == -1; +} + +function fillTransitionProperties(eventName, src, dst) { + if (eventName == "onCommitted" || eventName == "onHistoryStateUpdated") { + let frameTransitionData = src.frameTransitionData || {}; + let tabTransitionData = src.tabTransitionData || {}; + + let transitionType, transitionQualifiers = []; + + // Fill transition properties for any frame. + for (let qualifier of frameTransitions.anyFrame.qualifiers) { + if (frameTransitionData[qualifier]) { + transitionQualifiers.push(qualifier); + } + } + + if (isTopLevelFrame(dst)) { + for (let type of frameTransitions.topFrame.types) { + if (frameTransitionData[type]) { + transitionType = type; + } + } + + for (let qualifier of tabTransitions.topFrame.qualifiers) { + if (tabTransitionData[qualifier]) { + transitionQualifiers.push(qualifier); + } + } + + for (let type of tabTransitions.topFrame.types) { + if (tabTransitionData[type]) { + transitionType = type; + } + } + + // If transitionType is not defined, defaults it to "link". + if (!transitionType) { + transitionType = defaultTransitionTypes.topFrame; + } + } else { + // If it is sub-frame, transitionType defaults it to "auto_subframe", + // "manual_subframe" is set only in case of a recent user interaction. + transitionType = tabTransitionData.link ? + "manual_subframe" : defaultTransitionTypes.subFrame; + } + + // Fill the transition properties in the webNavigation event object. + dst.transitionType = transitionType; + dst.transitionQualifiers = transitionQualifiers; + } +} + +// Similar to WebRequestEventManager but for WebNavigation. +function WebNavigationEventManager(context, eventName) { + let name = `webNavigation.${eventName}`; + let register = (callback, urlFilters) => { + // Don't create a MatchURLFilters instance if the listener does not include any filter. + let filters = urlFilters ? + new MatchURLFilters(urlFilters.url) : null; + + let listener = data => { + if (!data.browser) { + return; + } + + let data2 = { + url: data.url, + timeStamp: Date.now(), + frameId: ExtensionManagement.getFrameId(data.windowId), + parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId), + }; + + if (eventName == "onErrorOccurred") { + data2.error = data.error; + } + + // Fills in tabId typically. + extensions.emit("fill-browser-data", data.browser, data2); + if (data2.tabId < 0) { + return; + } + + fillTransitionProperties(eventName, data, data2); + + context.runSafe(callback, data2); + }; + + WebNavigation[eventName].addListener(listener, filters); + return () => { + WebNavigation[eventName].removeListener(listener); + }; + }; + + return SingletonEventManager.call(this, context, name, register); +} + +WebNavigationEventManager.prototype = Object.create(SingletonEventManager.prototype); + +function convertGetFrameResult(tabId, data) { + return { + errorOccurred: data.errorOccurred, + url: data.url, + tabId, + frameId: ExtensionManagement.getFrameId(data.windowId), + parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId), + }; +} + +extensions.registerSchemaAPI("webNavigation", "addon_parent", context => { + return { + webNavigation: { + onTabReplaced: ignoreEvent(context, "webNavigation.onTabReplaced"), + onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(), + onCommitted: new WebNavigationEventManager(context, "onCommitted").api(), + onDOMContentLoaded: new WebNavigationEventManager(context, "onDOMContentLoaded").api(), + onCompleted: new WebNavigationEventManager(context, "onCompleted").api(), + onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(), + onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(), + onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(), + onCreatedNavigationTarget: ignoreEvent(context, "webNavigation.onCreatedNavigationTarget"), + getAllFrames(details) { + let tab = TabManager.getTab(details.tabId, context); + + let {innerWindowID, messageManager} = tab.linkedBrowser; + let recipient = {innerWindowID}; + + return context.sendMessage(messageManager, "WebNavigation:GetAllFrames", {}, {recipient}) + .then((results) => results.map(convertGetFrameResult.bind(null, details.tabId))); + }, + getFrame(details) { + let tab = TabManager.getTab(details.tabId, context); + + let recipient = { + innerWindowID: tab.linkedBrowser.innerWindowID, + }; + + let mm = tab.linkedBrowser.messageManager; + return context.sendMessage(mm, "WebNavigation:GetFrame", {options: details}, {recipient}) + .then((result) => { + return result ? + convertGetFrameResult(details.tabId, result) : + Promise.reject({message: `No frame found with frameId: ${details.frameId}`}); + }); + }, + }, + }; +}); diff --git a/toolkit/components/webextensions/ext-webRequest.js b/toolkit/components/webextensions/ext-webRequest.js new file mode 100644 index 000000000..f92330131 --- /dev/null +++ b/toolkit/components/webextensions/ext-webRequest.js @@ -0,0 +1,115 @@ +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern", + "resource://gre/modules/MatchPattern.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "WebRequest", + "resource://gre/modules/WebRequest.jsm"); + +Cu.import("resource://gre/modules/ExtensionManagement.jsm"); +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +var { + SingletonEventManager, +} = ExtensionUtils; + +// EventManager-like class specifically for WebRequest. Inherits from +// SingletonEventManager. Takes care of converting |details| parameter +// when invoking listeners. +function WebRequestEventManager(context, eventName) { + let name = `webRequest.${eventName}`; + let register = (callback, filter, info) => { + let listener = data => { + // Prevent listening in on requests originating from system principal to + // prevent tinkering with OCSP, app and addon updates, etc. + if (data.isSystemPrincipal) { + return; + } + + let data2 = { + requestId: data.requestId, + url: data.url, + originUrl: data.originUrl, + method: data.method, + type: data.type, + timeStamp: Date.now(), + frameId: data.type == "main_frame" ? 0 : ExtensionManagement.getFrameId(data.windowId), + parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId), + }; + + const maybeCached = ["onResponseStarted", "onBeforeRedirect", "onCompleted", "onErrorOccurred"]; + if (maybeCached.includes(eventName)) { + data2.fromCache = !!data.fromCache; + } + + if ("ip" in data) { + data2.ip = data.ip; + } + + extensions.emit("fill-browser-data", data.browser, data2); + + let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl", + "requestBody"]; + for (let opt of optional) { + if (opt in data) { + data2[opt] = data[opt]; + } + } + + return context.runSafe(callback, data2); + }; + + let filter2 = {}; + filter2.urls = new MatchPattern(filter.urls); + if (filter.types) { + filter2.types = filter.types; + } + if (filter.tabId) { + filter2.tabId = filter.tabId; + } + if (filter.windowId) { + filter2.windowId = filter.windowId; + } + + let info2 = []; + if (info) { + for (let desc of info) { + if (desc == "blocking" && !context.extension.hasPermission("webRequestBlocking")) { + Cu.reportError("Using webRequest.addListener with the blocking option " + + "requires the 'webRequestBlocking' permission."); + } else { + info2.push(desc); + } + } + } + + WebRequest[eventName].addListener(listener, filter2, info2); + return () => { + WebRequest[eventName].removeListener(listener); + }; + }; + + return SingletonEventManager.call(this, context, name, register); +} + +WebRequestEventManager.prototype = Object.create(SingletonEventManager.prototype); + +extensions.registerSchemaAPI("webRequest", "addon_parent", context => { + return { + webRequest: { + onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(), + onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(), + onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(), + onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(), + onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(), + onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(), + onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(), + onCompleted: new WebRequestEventManager(context, "onCompleted").api(), + handlerBehaviorChanged: function() { + // TODO: Flush all caches. + }, + }, + }; +}); diff --git a/toolkit/components/webextensions/extensions-toolkit.manifest b/toolkit/components/webextensions/extensions-toolkit.manifest new file mode 100644 index 000000000..4ec65a984 --- /dev/null +++ b/toolkit/components/webextensions/extensions-toolkit.manifest @@ -0,0 +1,49 @@ +# scripts +category webextension-scripts alarms chrome://extensions/content/ext-alarms.js +category webextension-scripts backgroundPage chrome://extensions/content/ext-backgroundPage.js +category webextension-scripts cookies chrome://extensions/content/ext-cookies.js +category webextension-scripts downloads chrome://extensions/content/ext-downloads.js +category webextension-scripts management chrome://extensions/content/ext-management.js +category webextension-scripts notifications chrome://extensions/content/ext-notifications.js +category webextension-scripts i18n chrome://extensions/content/ext-i18n.js +category webextension-scripts idle chrome://extensions/content/ext-idle.js +category webextension-scripts webRequest chrome://extensions/content/ext-webRequest.js +category webextension-scripts webNavigation chrome://extensions/content/ext-webNavigation.js +category webextension-scripts runtime chrome://extensions/content/ext-runtime.js +category webextension-scripts extension chrome://extensions/content/ext-extension.js +category webextension-scripts storage chrome://extensions/content/ext-storage.js +category webextension-scripts topSites chrome://extensions/content/ext-topSites.js + +# scripts specific for content process. +category webextension-scripts-content extension chrome://extensions/content/ext-c-extension.js +category webextension-scripts-content i18n chrome://extensions/content/ext-i18n.js +category webextension-scripts-content runtime chrome://extensions/content/ext-c-runtime.js +category webextension-scripts-content test chrome://extensions/content/ext-c-test.js +category webextension-scripts-content storage chrome://extensions/content/ext-c-storage.js + +# scripts that must run in the same process as addon code. +category webextension-scripts-addon backgroundPage chrome://extensions/content/ext-c-backgroundPage.js +category webextension-scripts-addon extension chrome://extensions/content/ext-c-extension.js +category webextension-scripts-addon i18n chrome://extensions/content/ext-i18n.js +category webextension-scripts-addon runtime chrome://extensions/content/ext-c-runtime.js +category webextension-scripts-addon test chrome://extensions/content/ext-c-test.js +category webextension-scripts-addon storage chrome://extensions/content/ext-c-storage.js + +# schemas +category webextension-schemas alarms chrome://extensions/content/schemas/alarms.json +category webextension-schemas cookies chrome://extensions/content/schemas/cookies.json +category webextension-schemas downloads chrome://extensions/content/schemas/downloads.json +category webextension-schemas events chrome://extensions/content/schemas/events.json +category webextension-schemas extension chrome://extensions/content/schemas/extension.json +category webextension-schemas extension_types chrome://extensions/content/schemas/extension_types.json +category webextension-schemas i18n chrome://extensions/content/schemas/i18n.json +category webextension-schemas idle chrome://extensions/content/schemas/idle.json +category webextension-schemas management chrome://extensions/content/schemas/management.json +category webextension-schemas native_host_manifest chrome://extensions/content/schemas/native_host_manifest.json +category webextension-schemas notifications chrome://extensions/content/schemas/notifications.json +category webextension-schemas runtime chrome://extensions/content/schemas/runtime.json +category webextension-schemas storage chrome://extensions/content/schemas/storage.json +category webextension-schemas test chrome://extensions/content/schemas/test.json +category webextension-schemas top_sites chrome://extensions/content/schemas/top_sites.json +category webextension-schemas web_navigation chrome://extensions/content/schemas/web_navigation.json +category webextension-schemas web_request chrome://extensions/content/schemas/web_request.json diff --git a/toolkit/components/webextensions/jar.mn b/toolkit/components/webextensions/jar.mn new file mode 100644 index 000000000..6d343e1b7 --- /dev/null +++ b/toolkit/components/webextensions/jar.mn @@ -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/. + +toolkit.jar: +% content extensions %content/extensions/ + content/extensions/ext-alarms.js + content/extensions/ext-backgroundPage.js + content/extensions/ext-browser-content.js + content/extensions/ext-cookies.js + content/extensions/ext-downloads.js + content/extensions/ext-management.js + content/extensions/ext-notifications.js + content/extensions/ext-i18n.js + content/extensions/ext-idle.js + content/extensions/ext-webRequest.js + content/extensions/ext-webNavigation.js + content/extensions/ext-runtime.js + content/extensions/ext-extension.js + content/extensions/ext-storage.js + content/extensions/ext-topSites.js + content/extensions/ext-c-backgroundPage.js + content/extensions/ext-c-extension.js + content/extensions/ext-c-runtime.js + content/extensions/ext-c-storage.js + content/extensions/ext-c-test.js diff --git a/toolkit/components/webextensions/moz.build b/toolkit/components/webextensions/moz.build new file mode 100644 index 000000000..f32f526f9 --- /dev/null +++ b/toolkit/components/webextensions/moz.build @@ -0,0 +1,41 @@ +# -*- 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/. + +EXTRA_JS_MODULES += [ + 'Extension.jsm', + 'ExtensionAPI.jsm', + 'ExtensionChild.jsm', + 'ExtensionCommon.jsm', + 'ExtensionContent.jsm', + 'ExtensionManagement.jsm', + 'ExtensionParent.jsm', + 'ExtensionStorage.jsm', + 'ExtensionUtils.jsm', + 'LegacyExtensionsUtils.jsm', + 'MessageChannel.jsm', + 'NativeMessaging.jsm', + 'Schemas.jsm', +] + +EXTRA_COMPONENTS += [ + 'extensions-toolkit.manifest', +] + +TESTING_JS_MODULES += [ + 'ExtensionTestCommon.jsm', + 'ExtensionXPCShellUtils.jsm', +] + +DIRS += ['schemas'] + +JAR_MANIFESTS += ['jar.mn'] + +MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini'] +MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini'] +XPCSHELL_TESTS_MANIFESTS += [ + 'test/xpcshell/native_messaging.ini', + 'test/xpcshell/xpcshell.ini', +] diff --git a/toolkit/components/webextensions/schemas/LICENSE b/toolkit/components/webextensions/schemas/LICENSE new file mode 100644 index 000000000..9314092fd --- /dev/null +++ b/toolkit/components/webextensions/schemas/LICENSE @@ -0,0 +1,27 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/toolkit/components/webextensions/schemas/alarms.json b/toolkit/components/webextensions/schemas/alarms.json new file mode 100644 index 000000000..2a72a2842 --- /dev/null +++ b/toolkit/components/webextensions/schemas/alarms.json @@ -0,0 +1,145 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "alarms", + "permissions": ["alarms"], + "types": [ + { + "id": "Alarm", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of this alarm." + }, + "scheduledTime": { + "type": "number", + "description": "Time when the alarm is scheduled to fire, in milliseconds past the epoch." + }, + "periodInMinutes": { + "type": "number", + "optional": true, + "description": "When present, signals that the alarm triggers periodically after so many minutes." + } + } + } + ], + "functions": [ + { + "name": "create", + "type": "function", + "description": "Creates an alarm. After the delay is expired, the onAlarm event is fired. If there is another alarm with the same name (or no name if none is specified), it will be cancelled and replaced by this alarm.", + "parameters": [ + { + "type": "string", + "name": "name", + "optional": true, + "description": "Optional name to identify this alarm. Defaults to the empty string." + }, + { + "type": "object", + "name": "alarmInfo", + "description": "Details about the alarm. The alarm first fires either at 'when' milliseconds past the epoch (if 'when' is provided), after 'delayInMinutes' minutes from the current time (if 'delayInMinutes' is provided instead), or after 'periodInMinutes' minutes from the current time (if only 'periodInMinutes' is provided). Users should never provide both 'when' and 'delayInMinutes'. If 'periodInMinutes' is provided, then the alarm recurs repeatedly after that many minutes.", + "properties": { + "when": {"type": "number", "optional": true, + "description": "Time when the alarm is scheduled to first fire, in milliseconds past the epoch."}, + "delayInMinutes": {"type": "number", "optional": true, + "description": "Number of minutes from the current time after which the alarm should first fire."}, + "periodInMinutes": {"type": "number", "optional": true, + "description": "Number of minutes after which the alarm should recur repeatedly."} + } + } + ] + }, + { + "name": "get", + "type": "function", + "description": "Retrieves details about the specified alarm.", + "async": "callback", + "parameters": [ + { + "type": "string", + "name": "name", + "optional": true, + "description": "The name of the alarm to get. Defaults to the empty string." + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { "name": "alarm", "$ref": "Alarm" } + ] + } + ] + }, + { + "name": "getAll", + "type": "function", + "description": "Gets an array of all the alarms.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { "name": "alarms", "type": "array", "items": { "$ref": "Alarm" } } + ] + } + ] + }, + { + "name": "clear", + "type": "function", + "description": "Clears the alarm with the given name.", + "async": "callback", + "parameters": [ + { + "type": "string", + "name": "name", + "optional": true, + "description": "The name of the alarm to clear. Defaults to the empty string." + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { "name": "wasCleared", "type": "boolean", "description": "Whether an alarm of the given name was found to clear." } + ] + } + ] + }, + { + "name": "clearAll", + "type": "function", + "description": "Clears all alarms.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { "name": "wasCleared", "type": "boolean", "description": "Whether any alarm was found to clear." } + ] + } + ] + } + ], + "events": [ + { + "name": "onAlarm", + "type": "function", + "description": "Fired when an alarm has expired. Useful for transient background pages.", + "parameters": [ + { + "name": "name", + "$ref": "Alarm", + "description": "The alarm that has expired." + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/cookies.json b/toolkit/components/webextensions/schemas/cookies.json new file mode 100644 index 000000000..a7de6eb42 --- /dev/null +++ b/toolkit/components/webextensions/schemas/cookies.json @@ -0,0 +1,224 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "Permission", + "choices": [{ + "type": "string", + "enum": [ + "cookies" + ] + }] + } + ] + }, + { + "namespace": "cookies", + "description": "Use the browser.cookies API to query and modify cookies, and to be notified when they change.", + "permissions": ["cookies"], + "types": [ + { + "id": "Cookie", + "type": "object", + "description": "Represents information about an HTTP cookie.", + "properties": { + "name": {"type": "string", "description": "The name of the cookie."}, + "value": {"type": "string", "description": "The value of the cookie."}, + "domain": {"type": "string", "description": "The domain of the cookie (e.g. \"www.google.com\", \"example.com\")."}, + "hostOnly": {"type": "boolean", "description": "True if the cookie is a host-only cookie (i.e. a request's host must exactly match the domain of the cookie)."}, + "path": {"type": "string", "description": "The path of the cookie."}, + "secure": {"type": "boolean", "description": "True if the cookie is marked as Secure (i.e. its scope is limited to secure channels, typically HTTPS)."}, + "httpOnly": {"type": "boolean", "description": "True if the cookie is marked as HttpOnly (i.e. the cookie is inaccessible to client-side scripts)."}, + "session": {"type": "boolean", "description": "True if the cookie is a session cookie, as opposed to a persistent cookie with an expiration date."}, + "expirationDate": {"type": "number", "optional": true, "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies."}, + "storeId": {"type": "string", "description": "The ID of the cookie store containing this cookie, as provided in getAllCookieStores()."} + } + }, + { + "id": "CookieStore", + "type": "object", + "description": "Represents a cookie store in the browser. An incognito mode window, for instance, uses a separate cookie store from a non-incognito window.", + "properties": { + "id": {"type": "string", "description": "The unique identifier for the cookie store."}, + "tabIds": {"type": "array", "items": {"type": "integer"}, "description": "Identifiers of all the browser tabs that share this cookie store."} + } + }, + { + "id": "OnChangedCause", + "type": "string", + "enum": ["evicted", "expired", "explicit", "expired_overwrite", "overwrite"], + "description": "The underlying reason behind the cookie's change. If a cookie was inserted, or removed via an explicit call to $(ref:cookies.remove), \"cause\" will be \"explicit\". If a cookie was automatically removed due to expiry, \"cause\" will be \"expired\". If a cookie was removed due to being overwritten with an already-expired expiration date, \"cause\" will be set to \"expired_overwrite\". If a cookie was automatically removed due to garbage collection, \"cause\" will be \"evicted\". If a cookie was automatically removed due to a \"set\" call that overwrote it, \"cause\" will be \"overwrite\". Plan your response accordingly." + } + ], + "functions": [ + { + "name": "get", + "type": "function", + "description": "Retrieves information about a single cookie. If more than one cookie of the same name exists for the given URL, the one with the longest path will be returned. For cookies with the same path length, the cookie with the earliest creation time will be returned.", + "async": "callback", + "parameters": [ + { + "type": "object", + "name": "details", + "description": "Details to identify the cookie being retrieved.", + "properties": { + "url": {"type": "string", "description": "The URL with which the cookie to retrieve is associated. This argument may be a full URL, in which case any data following the URL path (e.g. the query string) is simply ignored. If host permissions for this URL are not specified in the manifest file, the API call will fail."}, + "name": {"type": "string", "description": "The name of the cookie to retrieve."}, + "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store in which to look for the cookie. By default, the current execution context's cookie store will be used."} + } + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "cookie", "$ref": "Cookie", "optional": true, "description": "Contains details about the cookie. This parameter is null if no such cookie was found." + } + ] + } + ] + }, + { + "name": "getAll", + "type": "function", + "description": "Retrieves all cookies from a single cookie store that match the given information. The cookies returned will be sorted, with those with the longest path first. If multiple cookies have the same path length, those with the earliest creation time will be first.", + "async": "callback", + "parameters": [ + { + "type": "object", + "name": "details", + "description": "Information to filter the cookies being retrieved.", + "properties": { + "url": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those that would match the given URL."}, + "name": {"type": "string", "optional": true, "description": "Filters the cookies by name."}, + "domain": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."}, + "path": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those whose path exactly matches this string."}, + "secure": {"type": "boolean", "optional": true, "description": "Filters the cookies by their Secure property."}, + "session": {"type": "boolean", "optional": true, "description": "Filters out session vs. persistent cookies."}, + "storeId": {"type": "string", "optional": true, "description": "The cookie store to retrieve cookies from. If omitted, the current execution context's cookie store will be used."} + } + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "cookies", "type": "array", "items": {"$ref": "Cookie"}, "description": "All the existing, unexpired cookies that match the given cookie info." + } + ] + } + ] + }, + { + "name": "set", + "type": "function", + "description": "Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist.", + "async": "callback", + "parameters": [ + { + "type": "object", + "name": "details", + "description": "Details about the cookie being set.", + "properties": { + "url": {"type": "string", "description": "The request-URI to associate with the setting of the cookie. This value can affect the default domain and path values of the created cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."}, + "name": {"type": "string", "optional": true, "description": "The name of the cookie. Empty by default if omitted."}, + "value": {"type": "string", "optional": true, "description": "The value of the cookie. Empty by default if omitted."}, + "domain": {"type": "string", "optional": true, "description": "The domain of the cookie. If omitted, the cookie becomes a host-only cookie."}, + "path": {"type": "string", "optional": true, "description": "The path of the cookie. Defaults to the path portion of the url parameter."}, + "secure": {"type": "boolean", "optional": true, "description": "Whether the cookie should be marked as Secure. Defaults to false."}, + "httpOnly": {"type": "boolean", "optional": true, "description": "Whether the cookie should be marked as HttpOnly. Defaults to false."}, + "expirationDate": {"type": "number", "optional": true, "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie."}, + "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store in which to set the cookie. By default, the cookie is set in the current execution context's cookie store."} + } + }, + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [ + { + "name": "cookie", "$ref": "Cookie", "optional": true, "description": "Contains details about the cookie that's been set. If setting failed for any reason, this will be \"null\", and $(ref:runtime.lastError) will be set." + } + ] + } + ] + }, + { + "name": "remove", + "type": "function", + "description": "Deletes a cookie by name.", + "async": "callback", + "parameters": [ + { + "type": "object", + "name": "details", + "description": "Information to identify the cookie to remove.", + "properties": { + "url": {"type": "string", "description": "The URL associated with the cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."}, + "name": {"type": "string", "description": "The name of the cookie to remove."}, + "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store to look in for the cookie. If unspecified, the cookie is looked for by default in the current execution context's cookie store."} + } + }, + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [ + { + "name": "details", + "type": "object", + "description": "Contains details about the cookie that's been removed. If removal failed for any reason, this will be \"null\", and $(ref:runtime.lastError) will be set.", + "optional": true, + "properties": { + "url": {"type": "string", "description": "The URL associated with the cookie that's been removed."}, + "name": {"type": "string", "description": "The name of the cookie that's been removed."}, + "storeId": {"type": "string", "description": "The ID of the cookie store from which the cookie was removed."} + } + } + ] + } + ] + }, + { + "name": "getAllCookieStores", + "type": "function", + "description": "Lists all existing cookie stores.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "cookieStores", "type": "array", "items": {"$ref": "CookieStore"}, "description": "All the existing cookie stores." + } + ] + } + ] + } + ], + "events": [ + { + "name": "onChanged", + "type": "function", + "description": "Fired when a cookie is set or removed. As a special case, note that updating a cookie's properties is implemented as a two step process: the cookie to be updated is first removed entirely, generating a notification with \"cause\" of \"overwrite\" . Afterwards, a new cookie is written with the updated values, generating a second notification with \"cause\" \"explicit\".", + "parameters": [ + { + "type": "object", + "name": "changeInfo", + "properties": { + "removed": {"type": "boolean", "description": "True if a cookie was removed."}, + "cookie": {"$ref": "Cookie", "description": "Information about the cookie that was set or removed."}, + "cause": {"$ref": "OnChangedCause", "description": "The underlying reason behind the cookie's change."} + } + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/downloads.json b/toolkit/components/webextensions/schemas/downloads.json new file mode 100644 index 000000000..dcd43e4e1 --- /dev/null +++ b/toolkit/components/webextensions/schemas/downloads.json @@ -0,0 +1,793 @@ +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "Permission", + "choices": [{ + "type": "string", + "enum": [ + "downloads", + "downloads.open", + "downloads.shelf" + ] + }] + } + ] + }, + { + "namespace": "downloads", + "permissions": ["downloads"], + "types": [ + { + "id": "FilenameConflictAction", + "type": "string", + "enum": [ + "uniquify", + "overwrite", + "prompt" + ] + }, + { + "id": "InterruptReason", + "type": "string", + "enum": [ + "FILE_FAILED", + "FILE_ACCESS_DENIED", + "FILE_NO_SPACE", + "FILE_NAME_TOO_LONG", + "FILE_TOO_LARGE", + "FILE_VIRUS_INFECTED", + "FILE_TRANSIENT_ERROR", + "FILE_BLOCKED", + "FILE_SECURITY_CHECK_FAILED", + "FILE_TOO_SHORT", + "NETWORK_FAILED", + "NETWORK_TIMEOUT", + "NETWORK_DISCONNECTED", + "NETWORK_SERVER_DOWN", + "NETWORK_INVALID_REQUEST", + "SERVER_FAILED", + "SERVER_NO_RANGE", + "SERVER_BAD_CONTENT", + "SERVER_UNAUTHORIZED", + "SERVER_CERT_PROBLEM", + "SERVER_FORBIDDEN", + "USER_CANCELED", + "USER_SHUTDOWN", + "CRASH" + ] + }, + { + "id": "DangerType", + "type": "string", + "enum": [ + "file", + "url", + "content", + "uncommon", + "host", + "unwanted", + "safe", + "accepted" + ], + "description": "
file
The download's filename is suspicious.
url
The download's URL is known to be malicious.
content
The downloaded file is known to be malicious.
uncommon
The download's URL is not commonly downloaded and could be dangerous.
safe
The download presents no known danger to the user's computer.
These string constants will never change, however the set of DangerTypes may change." + }, + { + "id": "State", + "type": "string", + "enum": [ + "in_progress", + "interrupted", + "complete" + ], + "description": "
in_progress
The download is currently receiving data from the server.
interrupted
An error broke the connection with the file host.
complete
The download completed successfully.
These string constants will never change, however the set of States may change." + }, + { + "id": "DownloadItem", + "type": "object", + "properties": { + "id": { + "description": "An identifier that is persistent across browser sessions.", + "type": "integer" + }, + "url": { + "description": "Absolute URL.", + "type": "string" + }, + "referrer": { + "type": "string" + }, + "filename": { + "description": "Absolute local path.", + "type": "string" + }, + "incognito": { + "description": "False if this download is recorded in the history, true if it is not recorded.", + "type": "boolean" + }, + "danger": { + "$ref": "DangerType", + "description": "Indication of whether this download is thought to be safe or known to be suspicious." + }, + "mime": { + "description": "The file's MIME type.", + "type": "string" + }, + "startTime": { + "description": "Number of milliseconds between the unix epoch and when this download began.", + "type": "string" + }, + "endTime": { + "description": "Number of milliseconds between the unix epoch and when this download ended.", + "optional": true, + "type": "string" + }, + "estimatedEndTime": { + "type": "string", + "optional": true + }, + "state": { + "$ref": "State", + "description": "Indicates whether the download is progressing, interrupted, or complete." + }, + "paused": { + "description": "True if the download has stopped reading data from the host, but kept the connection open.", + "type": "boolean" + }, + "canResume": { + "type": "boolean" + }, + "error": { + "description": "Number indicating why a download was interrupted.", + "optional": true, + "$ref": "InterruptReason" + }, + "bytesReceived": { + "description": "Number of bytes received so far from the host, without considering file compression.", + "type": "number" + }, + "totalBytes": { + "description": "Number of bytes in the whole file, without considering file compression, or -1 if unknown.", + "type": "number" + }, + "fileSize": { + "description": "Number of bytes in the whole file post-decompression, or -1 if unknown.", + "type": "number" + }, + "exists": { + "type": "boolean" + }, + "byExtensionId": { + "type": "string", + "optional": true + }, + "byExtensionName": { + "type": "string", + "optional": true + } + } + }, + { + "id": "StringDelta", + "type": "object", + "properties": { + "current": { + "optional": true, + "type": "string" + }, + "previous": { + "optional": true, + "type": "string" + } + } + }, + { + "id": "DoubleDelta", + "type": "object", + "properties": { + "current": { + "optional": true, + "type": "number" + }, + "previous": { + "optional": true, + "type": "number" + } + } + }, + { + "id": "BooleanDelta", + "type": "object", + "properties": { + "current": { + "optional": true, + "type": "boolean" + }, + "previous": { + "optional": true, + "type": "boolean" + } + } + }, + { + "id": "DownloadTime", + "description": "A time specified as a Date object, a number or string representing milliseconds since the epoch, or an ISO 8601 string", + "choices": [ + { + "type": "string", + "pattern": "^[1-9]\\d*$" + }, + { + "$ref": "extensionTypes.Date" + } + ] + }, + { + "id": "DownloadQuery", + "description": "Parameters that combine to specify a predicate that can be used to select a set of downloads. Used for example in search() and erase()", + "type": "object", + "properties": { + "query": { + "description": "This array of search terms limits results to DownloadItems whose filename or url contain all of the search terms that do not begin with a dash '-' and none of the search terms that do begin with a dash.", + "optional": true, + "type": "array", + "items": { "type": "string" } + }, + "startedBefore": { + "description": "Limits results to downloads that started before the given ms since the epoch.", + "optional": true, + "$ref": "DownloadTime" + }, + "startedAfter": { + "description": "Limits results to downloads that started after the given ms since the epoch.", + "optional": true, + "$ref": "DownloadTime" + }, + "endedBefore": { + "description": "Limits results to downloads that ended before the given ms since the epoch.", + "optional": true, + "$ref": "DownloadTime" + }, + "endedAfter": { + "description": "Limits results to downloads that ended after the given ms since the epoch.", + "optional": true, + "$ref": "DownloadTime" + }, + "totalBytesGreater": { + "description": "Limits results to downloads whose totalBytes is greater than the given integer.", + "optional": true, + "type": "number" + }, + "totalBytesLess": { + "description": "Limits results to downloads whose totalBytes is less than the given integer.", + "optional": true, + "type": "number" + }, + "filenameRegex": { + "description": "Limits results to DownloadItems whose filename matches the given regular expression.", + "optional": true, + "type": "string" + }, + "urlRegex": { + "description": "Limits results to DownloadItems whose url matches the given regular expression.", + "optional": true, + "type": "string" + }, + "limit": { + "description": "Setting this integer limits the number of results. Otherwise, all matching DownloadItems will be returned.", + "optional": true, + "type": "integer" + }, + "orderBy": { + "description": "Setting elements of this array to DownloadItem properties in order to sort the search results. For example, setting orderBy='startTime' sorts the DownloadItems by their start time in ascending order. To specify descending order, prefix orderBy with a hyphen: '-startTime'.", + "optional": true, + "type": "array", + "items": { "type": "string" } + }, + "id": { + "type": "integer", + "optional": true + }, + "url": { + "description": "Absolute URL.", + "optional": true, + "type": "string" + }, + "filename": { + "description": "Absolute local path.", + "optional": true, + "type": "string" + }, + "danger": { + "$ref": "DangerType", + "description": "Indication of whether this download is thought to be safe or known to be suspicious.", + "optional": true + }, + "mime": { + "description": "The file's MIME type.", + "optional": true, + "type": "string" + }, + "startTime": { + "optional": true, + "type": "string" + }, + "endTime": { + "optional": true, + "type": "string" + }, + "state": { + "$ref": "State", + "description": "Indicates whether the download is progressing, interrupted, or complete.", + "optional": true + }, + "paused": { + "description": "True if the download has stopped reading data from the host, but kept the connection open.", + "optional": true, + "type": "boolean" + }, + "error": { + "description": "Why a download was interrupted.", + "optional": true, + "$ref": "InterruptReason" + }, + "bytesReceived": { + "description": "Number of bytes received so far from the host, without considering file compression.", + "optional": true, + "type": "number" + }, + "totalBytes": { + "description": "Number of bytes in the whole file, without considering file compression, or -1 if unknown.", + "optional": true, + "type": "number" + }, + "fileSize": { + "description": "Number of bytes in the whole file post-decompression, or -1 if unknown.", + "optional": true, + "type": "number" + }, + "exists": { + "type": "boolean", + "optional": true + } + } + } + ], + "functions": [ + { + "name": "download", + "type": "function", + "async": "callback", + "description": "Download a URL. If the URL uses the HTTP[S] protocol, then the request will include all cookies currently set for its hostname. If both filename and saveAs are specified, then the Save As dialog will be displayed, pre-populated with the specified filename. If the download started successfully, callback will be called with the new DownloadItem's downloadId. If there was an error starting the download, then callback will be called with downloadId=undefined and chrome.extension.lastError will contain a descriptive string. The error strings are not guaranteed to remain backwards compatible between releases. You must not parse it.", + "parameters": [ + { + "description": "What to download and how.", + "name": "options", + "type": "object", + "properties": { + "url": { + "description": "The URL to download.", + "type": "string", + "format": "url" + }, + "filename": { + "description": "A file path relative to the Downloads directory to contain the downloaded file.", + "optional": true, + "type": "string" + }, + "conflictAction": { + "$ref": "FilenameConflictAction", + "optional": true + }, + "saveAs": { + "description": "Use a file-chooser to allow the user to select a filename.", + "optional": true, + "type": "boolean" + }, + "method": { + "description": "The HTTP method to use if the URL uses the HTTP[S] protocol.", + "enum": [ + "GET", + "POST" + ], + "optional": true, + "type": "string" + }, + "headers": { + "optional": true, + "type": "array", + "description": "Extra HTTP headers to send with the request if the URL uses the HTTP[s] protocol. Each header is represented as a dictionary containing the keys name and either value or binaryValue, restricted to those allowed by XMLHttpRequest.", + "items": { + "type": "object", + "properties": { + "name": { + "description": "Name of the HTTP header.", + "type": "string" + }, + "value": { + "description": "Value of the HTTP header.", + "type": "string" + } + } + } + }, + "body": { + "description": "Post body.", + "optional": true, + "type": "string" + } + } + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ + { + "name": "downloadId", + "type": "integer" + } + ] + } + ] + }, + { + "name": "search", + "type": "function", + "async": "callback", + "description": "Find DownloadItems. Set query to the empty object to get all DownloadItems. To get a specific DownloadItem, set only the id field.", + "parameters": [ + { + "name": "query", + "$ref": "DownloadQuery" + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "items": { + "$ref": "DownloadItem" + }, + "name": "results", + "type": "array" + } + ] + } + ] + }, + { + "name": "pause", + "type": "function", + "async": "callback", + "description": "Pause the download. If the request was successful the download is in a paused state. Otherwise chrome.extension.lastError contains an error message. The request will fail if the download is not active.", + "parameters": [ + { + "description": "The id of the download to pause.", + "name": "downloadId", + "type": "integer" + }, + { + "name": "callback", + "optional": true, + "parameters": [], + "type": "function" + } + ] + }, + { + "name": "resume", + "type": "function", + "async": "callback", + "description": "Resume a paused download. If the request was successful the download is in progress and unpaused. Otherwise chrome.extension.lastError contains an error message. The request will fail if the download is not active.", + "parameters": [ + { + "description": "The id of the download to resume.", + "name": "downloadId", + "type": "integer" + }, + { + "name": "callback", + "optional": true, + "parameters": [], + "type": "function" + } + ] + }, + { + "name": "cancel", + "type": "function", + "async": "callback", + "description": "Cancel a download. When callback is run, the download is cancelled, completed, interrupted or doesn't exist anymore.", + "parameters": [ + { + "description": "The id of the download to cancel.", + "name": "downloadId", + "type": "integer" + }, + { + "name": "callback", + "optional": true, + "parameters": [], + "type": "function" + } + ] + }, + { + "name": "getFileIcon", + "type": "function", + "async": "callback", + "description": "Retrieve an icon for the specified download. For new downloads, file icons are available after the onCreated event has been received. The image returned by this function while a download is in progress may be different from the image returned after the download is complete. Icon retrieval is done by querying the underlying operating system or toolkit depending on the platform. The icon that is returned will therefore depend on a number of factors including state of the download, platform, registered file types and visual theme. If a file icon cannot be determined, chrome.extension.lastError will contain an error message.", + "parameters": [ + { + "description": "The identifier for the download.", + "name": "downloadId", + "type": "integer" + }, + { + "name": "options", + "optional": true, + "properties": { + "size": { + "description": "The size of the icon. The returned icon will be square with dimensions size * size pixels. The default size for the icon is 32x32 pixels.", + "optional": true, + "minimum": 1, + "maximum": 127, + "type": "integer" + } + }, + "type": "object" + }, + { + "name": "callback", + "parameters": [ + { + "name": "iconURL", + "optional": true, + "type": "string" + } + ], + "type": "function" + } + ] + }, + { + "name": "open", + "type": "function", + "async": "callback", + "description": "Open the downloaded file.", + "permissions": ["downloads.open"], + "parameters": [ + { + "name": "downloadId", + "type": "integer" + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [] + } + ] + }, + { + "name": "show", + "type": "function", + "description": "Show the downloaded file in its folder in a file manager.", + "async": "callback", + "parameters": [ + { + "name": "downloadId", + "type": "integer" + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ + { + "name": "success", + "type": "boolean" + } + ] + } + ] + }, + { + "name": "showDefaultFolder", + "type": "function", + "parameters": [] + }, + { + "name": "erase", + "type": "function", + "async": "callback", + "description": "Erase matching DownloadItems from history", + "parameters": [ + { + "name": "query", + "$ref": "DownloadQuery" + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ + { + "items": { + "type": "integer" + }, + "name": "erasedIds", + "type": "array" + } + ] + } + ] + }, + { + "name": "removeFile", + "async": "callback", + "type": "function", + "parameters": [ + { + "name": "downloadId", + "type": "integer" + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ ] + } + ] + }, + { + "description": "Prompt the user to either accept or cancel a dangerous download. acceptDanger() does not automatically accept dangerous downloads.", + "name": "acceptDanger", + "unsupported": true, + "parameters": [ + { + "name": "downloadId", + "type": "integer" + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ ] + } + ], + "type": "function" + }, + { + "description": "Initiate dragging the file to another application.", + "name": "drag", + "unsupported": true, + "parameters": [ + { + "name": "downloadId", + "type": "integer" + } + ], + "type": "function" + }, + { + "name": "setShelfEnabled", + "type": "function", + "unsupported": true, + "parameters": [ + { + "name": "enabled", + "type": "boolean" + } + ] + } + ], + "events": [ + { + "description": "This event fires with the DownloadItem object when a download begins.", + "name": "onCreated", + "parameters": [ + { + "$ref": "DownloadItem", + "name": "downloadItem" + } + ], + "type": "function" + }, + { + "description": "Fires with the downloadId when a download is erased from history.", + "name": "onErased", + "parameters": [ + { + "name": "downloadId", + "description": "The id of the DownloadItem that was erased.", + "type": "integer" + } + ], + "type": "function" + }, + { + "name": "onChanged", + "description": "When any of a DownloadItem's properties except bytesReceived changes, this event fires with the downloadId and an object containing the properties that changed.", + "parameters": [ + { + "name": "downloadDelta", + "type": "object", + "properties": { + "id": { + "description": "The id of the DownloadItem that changed.", + "type": "integer" + }, + "url": { + "description": "Describes a change in a DownloadItem's url.", + "optional": true, + "$ref": "StringDelta" + }, + "filename": { + "description": "Describes a change in a DownloadItem's filename.", + "optional": true, + "$ref": "StringDelta" + }, + "danger": { + "description": "Describes a change in a DownloadItem's danger.", + "optional": true, + "$ref": "StringDelta" + }, + "mime": { + "description": "Describes a change in a DownloadItem's mime.", + "optional": true, + "$ref": "StringDelta" + }, + "startTime": { + "description": "Describes a change in a DownloadItem's startTime.", + "optional": true, + "$ref": "StringDelta" + }, + "endTime": { + "description": "Describes a change in a DownloadItem's endTime.", + "optional": true, + "$ref": "StringDelta" + }, + "state": { + "description": "Describes a change in a DownloadItem's state.", + "optional": true, + "$ref": "StringDelta" + }, + "canResume": { + "optional": true, + "$ref": "BooleanDelta" + }, + "paused": { + "description": "Describes a change in a DownloadItem's paused.", + "optional": true, + "$ref": "BooleanDelta" + }, + "error": { + "description": "Describes a change in a DownloadItem's error.", + "optional": true, + "$ref": "StringDelta" + }, + "totalBytes": { + "description": "Describes a change in a DownloadItem's totalBytes.", + "optional": true, + "$ref": "DoubleDelta" + }, + "fileSize": { + "description": "Describes a change in a DownloadItem's fileSize.", + "optional": true, + "$ref": "DoubleDelta" + }, + "exists": { + "optional": true, + "$ref": "BooleanDelta" + } + } + } + ], + "type": "function" + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/events.json b/toolkit/components/webextensions/schemas/events.json new file mode 100644 index 000000000..ea3cbb5d2 --- /dev/null +++ b/toolkit/components/webextensions/schemas/events.json @@ -0,0 +1,322 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "events", + "description": "The chrome.events namespace contains common types used by APIs dispatching events to notify you when something interesting happens.", + "types": [ + { + "id": "Rule", + "type": "object", + "description": "Description of a declarative rule for handling events.", + "properties": { + "id": { + "type": "string", + "optional": true, + "description": "Optional identifier that allows referencing this rule." + }, + "tags": { + "type": "array", + "items": {"type": "string"}, + "optional": true, + "description": "Tags can be used to annotate rules and perform operations on sets of rules." + }, + "conditions": { + "type": "array", + "items": {"type": "any"}, + "description": "List of conditions that can trigger the actions." + }, + "actions": { + "type": "array", + "items": {"type": "any"}, + "description": "List of actions that are triggered if one of the condtions is fulfilled." + }, + "priority": { + "type": "integer", + "optional": true, + "description": "Optional priority of this rule. Defaults to 100." + } + } + }, + { + "id": "Event", + "type": "object", + "description": "An object which allows the addition and removal of listeners for a Chrome event.", + "functions": [ + { + "name": "addListener", + "type": "function", + "description": "Registers an event listener callback to an event.", + "parameters": [ + { + "name": "callback", + "type": "function", + "description": "Called when an event occurs. The parameters of this function depend on the type of event." + } + ] + }, + { + "name": "removeListener", + "type": "function", + "description": "Deregisters an event listener callback from an event.", + "parameters": [ + { + "name": "callback", + "type": "function", + "description": "Listener that shall be unregistered." + } + ] + }, + { + "name": "hasListener", + "type": "function", + "parameters": [ + { + "name": "callback", + "type": "function", + "description": "Listener whose registration status shall be tested." + } + ], + "returns": { + "type": "boolean", + "description": "True if callback is registered to the event." + } + }, + { + "name": "hasListeners", + "type": "function", + "parameters": [], + "returns": { + "type": "boolean", + "description": "True if any event listeners are registered to the event." + } + }, + { + "name": "addRules", + "unsupported": true, + "type": "function", + "description": "Registers rules to handle events.", + "parameters": [ + { + "name": "eventName", + "type": "string", + "description": "Name of the event this function affects." + }, + { + "name": "webViewInstanceId", + "type": "integer", + "description": "If provided, this is an integer that uniquely identfies the associated with this function call." + }, + { + "name": "rules", + "type": "array", + "items": {"$ref": "Rule"}, + "description": "Rules to be registered. These do not replace previously registered rules." + }, + { + "name": "callback", + "optional": true, + "type": "function", + "parameters": [ + { + "name": "rules", + "type": "array", + "items": {"$ref": "Rule"}, + "description": "Rules that were registered, the optional parameters are filled with values." + } + ], + "description": "Called with registered rules." + } + ] + }, + { + "name": "getRules", + "unsupported": true, + "type": "function", + "description": "Returns currently registered rules.", + "parameters": [ + { + "name": "eventName", + "type": "string", + "description": "Name of the event this function affects." + }, + { + "name": "webViewInstanceId", + "type": "integer", + "description": "If provided, this is an integer that uniquely identfies the associated with this function call." + }, + { + "name": "ruleIdentifiers", + "optional": true, + "type": "array", + "items": {"type": "string"}, + "description": "If an array is passed, only rules with identifiers contained in this array are returned." + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "rules", + "type": "array", + "items": {"$ref": "Rule"}, + "description": "Rules that were registered, the optional parameters are filled with values." + } + ], + "description": "Called with registered rules." + } + ] + }, + { + "name": "removeRules", + "unsupported": true, + "type": "function", + "description": "Unregisters currently registered rules.", + "parameters": [ + { + "name": "eventName", + "type": "string", + "description": "Name of the event this function affects." + }, + { + "name": "webViewInstanceId", + "type": "integer", + "description": "If provided, this is an integer that uniquely identfies the associated with this function call." + }, + { + "name": "ruleIdentifiers", + "optional": true, + "type": "array", + "items": {"type": "string"}, + "description": "If an array is passed, only rules with identifiers contained in this array are unregistered." + }, + { + "name": "callback", + "optional": true, + "type": "function", + "parameters": [], + "description": "Called when rules were unregistered." + } + ] + } + ] + }, + { + "id": "UrlFilter", + "type": "object", + "description": "Filters URLs for various criteria. See event filtering. All criteria are case sensitive.", + "properties": { + "hostContains": { + "type": "string", + "description": "Matches if the host name of the URL contains a specified string. To test whether a host name component has a prefix 'foo', use hostContains: '.foo'. This matches 'www.foobar.com' and 'foo.com', because an implicit dot is added at the beginning of the host name. Similarly, hostContains can be used to match against component suffix ('foo.') and to exactly match against components ('.foo.'). Suffix- and exact-matching for the last components need to be done separately using hostSuffix, because no implicit dot is added at the end of the host name.", + "optional": true + }, + "hostEquals": { + "type": "string", + "description": "Matches if the host name of the URL is equal to a specified string.", + "optional": true + }, + "hostPrefix": { + "type": "string", + "description": "Matches if the host name of the URL starts with a specified string.", + "optional": true + }, + "hostSuffix": { + "type": "string", + "description": "Matches if the host name of the URL ends with a specified string.", + "optional": true + }, + "pathContains": { + "type": "string", + "description": "Matches if the path segment of the URL contains a specified string.", + "optional": true + }, + "pathEquals": { + "type": "string", + "description": "Matches if the path segment of the URL is equal to a specified string.", + "optional": true + }, + "pathPrefix": { + "type": "string", + "description": "Matches if the path segment of the URL starts with a specified string.", + "optional": true + }, + "pathSuffix": { + "type": "string", + "description": "Matches if the path segment of the URL ends with a specified string.", + "optional": true + }, + "queryContains": { + "type": "string", + "description": "Matches if the query segment of the URL contains a specified string.", + "optional": true + }, + "queryEquals": { + "type": "string", + "description": "Matches if the query segment of the URL is equal to a specified string.", + "optional": true + }, + "queryPrefix": { + "type": "string", + "description": "Matches if the query segment of the URL starts with a specified string.", + "optional": true + }, + "querySuffix": { + "type": "string", + "description": "Matches if the query segment of the URL ends with a specified string.", + "optional": true + }, + "urlContains": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) contains a specified string. Port numbers are stripped from the URL if they match the default port number.", + "optional": true + }, + "urlEquals": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) is equal to a specified string. Port numbers are stripped from the URL if they match the default port number.", + "optional": true + }, + "urlMatches": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the RE2 syntax.", + "optional": true + }, + "originAndPathMatches": { + "type": "string", + "description": "Matches if the URL without query segment and fragment identifier matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the RE2 syntax.", + "optional": true + }, + "urlPrefix": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) starts with a specified string. Port numbers are stripped from the URL if they match the default port number.", + "optional": true + }, + "urlSuffix": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) ends with a specified string. Port numbers are stripped from the URL if they match the default port number.", + "optional": true + }, + "schemes": { + "type": "array", + "description": "Matches if the scheme of the URL is equal to any of the schemes specified in the array.", + "optional": true, + "items": { "type": "string" } + }, + "ports": { + "type": "array", + "description": "Matches if the port of the URL is contained in any of the specified port lists. For example [80, 443, [1000, 1200]] matches all requests on port 80, 443 and in the range 1000-1200.", + "optional": true, + "items": { + "choices": [ + {"type": "integer", "description": "A specific port."}, + {"type": "array", "minItems": 2, "maxItems": 2, "items": {"type": "integer"}, "description": "A pair of integers identiying the start and end (both inclusive) of a port range."} + ] + } + } + } + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/experiments.json b/toolkit/components/webextensions/schemas/experiments.json new file mode 100644 index 000000000..c687173a9 --- /dev/null +++ b/toolkit/components/webextensions/schemas/experiments.json @@ -0,0 +1,16 @@ +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "Permission", + "choices": [ + { + "type": "string", + "pattern": "^experiments(\\.\\w+)+$" + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/extension.json b/toolkit/components/webextensions/schemas/extension.json new file mode 100644 index 000000000..5a1b6c935 --- /dev/null +++ b/toolkit/components/webextensions/schemas/extension.json @@ -0,0 +1,178 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "extension", + "allowedContexts": ["content"], + "description": "The browser.extension API has utilities that can be used by any extension page. It includes support for exchanging messages between an extension and its content scripts or between extensions, as described in detail in $(topic:messaging)[Message Passing].", + "properties": { + "lastError": { + "type": "object", + "optional": true, + "allowedContexts": ["content"], + "description": "Set for the lifetime of a callback if an ansychronous extension api has resulted in an error. If no error has occured lastError will be undefined.", + "properties": { + "message": { "type": "string", "description": "Description of the error that has taken place." } + }, + "additionalProperties": { + "type": "any" + } + }, + "inIncognitoContext": { + "type": "boolean", + "optional": true, + "allowedContexts": ["content"], + "description": "True for content scripts running inside incognito tabs, and for extension pages running inside an incognito process. The latter only applies to extensions with 'split' incognito_behavior." + } + }, + "types": [ + { + "id": "ViewType", + "type": "string", + "enum": ["tab", "notification", "popup"], + "description": "The type of extension view." + } + ], + "functions": [ + { + "name": "getURL", + "type": "function", + "allowedContexts": ["content"], + "description": "Converts a relative path within an extension install directory to a fully-qualified URL.", + "parameters": [ + { + "type": "string", + "name": "path", + "description": "A path to a resource within an extension expressed relative to its install directory." + } + ], + "returns": { + "type": "string", + "description": "The fully-qualified URL to the resource." + } + }, + { + "name": "getViews", + "type": "function", + "description": "Returns an array of the JavaScript 'window' objects for each of the pages running inside the current extension.", + "parameters": [ + { + "type": "object", + "name": "fetchProperties", + "optional": true, + "properties": { + "type": { + "$ref": "ViewType", + "optional": true, + "description": "The type of view to get. If omitted, returns all views (including background pages and tabs). Valid values: 'tab', 'notification', 'popup'." + }, + "windowId": { + "type": "integer", + "optional": true, + "description": "The window to restrict the search to. If omitted, returns all views." + } + } + } + ], + "returns": { + "type": "array", + "description": "Array of global objects", + "items": { + "name": "viewGlobals", + "type": "object", + "isInstanceOf": "Window", + "additionalProperties": { "type": "any" } + } + } + }, + { + "name": "getBackgroundPage", + "type": "function", + "description": "Returns the JavaScript 'window' object for the background page running inside the current extension. Returns null if the extension has no background page.", + "parameters": [], + "returns": { + "type": "object", + "optional": true, + "name": "backgroundPageGlobal", + "isInstanceOf": "Window", + "additionalProperties": { "type": "any" } + } + }, + { + "name": "isAllowedIncognitoAccess", + "type": "function", + "description": "Retrieves the state of the extension's access to Incognito-mode (as determined by the user-controlled 'Allowed in Incognito' checkbox.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "isAllowedAccess", + "type": "boolean", + "description": "True if the extension has access to Incognito mode, false otherwise." + } + ] + } + ] + }, + { + "name": "isAllowedFileSchemeAccess", + "type": "function", + "description": "Retrieves the state of the extension's access to the 'file://' scheme (as determined by the user-controlled 'Allow access to File URLs' checkbox.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "isAllowedAccess", + "type": "boolean", + "description": "True if the extension can access the 'file://' scheme, false otherwise." + } + ] + } + ] + }, + { + "name": "setUpdateUrlData", + "unsupported": true, + "type": "function", + "description": "Sets the value of the ap CGI parameter used in the extension's update URL. This value is ignored for extensions that are hosted in the browser vendor's store.", + "parameters": [ + {"type": "string", "name": "data", "maxLength": 1024} + ] + } + ], + "events": [ + { + "name": "onRequest", + "unsupported": true, + "deprecated": "Please use $(ref:runtime.onMessage).", + "type": "function", + "description": "Fired when a request is sent from either an extension process or a content script.", + "parameters": [ + {"name": "request", "type": "any", "optional": true, "description": "The request sent by the calling script."}, + {"name": "sender", "$ref": "runtime.MessageSender" }, + {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object, or undefined if there is no response. If you have more than one onRequest listener in the same document, then only one may send a response." } + ] + }, + { + "name": "onRequestExternal", + "unsupported": true, + "deprecated": "Please use $(ref:runtime.onMessageExternal).", + "type": "function", + "description": "Fired when a request is sent from another extension.", + "parameters": [ + {"name": "request", "type": "any", "optional": true, "description": "The request sent by the calling script."}, + {"name": "sender", "$ref": "runtime.MessageSender" }, + {"name": "sendResponse", "type": "function", "description": "Function to call when you have a response. The argument should be any JSON-ifiable object, or undefined if there is no response." } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/extension_types.json b/toolkit/components/webextensions/schemas/extension_types.json new file mode 100644 index 000000000..1a88e4e60 --- /dev/null +++ b/toolkit/components/webextensions/schemas/extension_types.json @@ -0,0 +1,83 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "extensionTypes", + "description": "The browser.extensionTypes API contains type declarations for WebExtensions.", + "types": [ + { + "id": "ImageFormat", + "type": "string", + "enum": ["jpeg", "png"], + "description": "The format of an image." + }, + { + "id": "ImageDetails", + "type": "object", + "description": "Details about the format and quality of an image.", + "properties": { + "format": { + "$ref": "ImageFormat", + "optional": true, + "description": "The format of the resulting image. Default is \"jpeg\"." + }, + "quality": { + "type": "integer", + "optional": true, + "minimum": 0, + "maximum": 100, + "description": "When format is \"jpeg\", controls the quality of the resulting image. This value is ignored for PNG images. As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease." + } + } + }, + { + "id": "RunAt", + "type": "string", + "enum": ["document_start", "document_end", "document_idle"], + "description": "The soonest that the JavaScript or CSS will be injected into the tab." + }, + { + "id": "InjectDetails", + "type": "object", + "description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time.", + "properties": { + "code": {"type": "string", "optional": true, "description": "JavaScript or CSS code to inject.

Warning:
Be careful using the code parameter. Incorrect use of it may open your extension to cross site scripting attacks."}, + "file": {"type": "string", "optional": true, "description": "JavaScript or CSS file to inject."}, + "allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is true, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's false and is only injected into the top frame."}, + "matchAboutBlank": {"type": "boolean", "optional": true, "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is false."}, + "frameId": { + "type": "integer", + "minimum": 0, + "optional": true, + "description": "The ID of the frame to inject the script into. This may not be used in combination with allFrames." + }, + "runAt": { + "$ref": "RunAt", + "optional": true, + "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"." + } + } + }, + { + "id": "Date", + "choices": [ + { + "type": "string", + "format": "date" + }, + { + "type": "integer", + "minimum": 0 + }, + { + "type": "object", + "isInstanceOf": "Date", + "additionalProperties": { "type": "any" } + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/i18n.json b/toolkit/components/webextensions/schemas/i18n.json new file mode 100644 index 000000000..12dc45dfc --- /dev/null +++ b/toolkit/components/webextensions/schemas/i18n.json @@ -0,0 +1,132 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "WebExtensionManifest", + "properties": { + "default_locale": { + "type": "string", + "optional": "true" + } + } + } + ] + }, + { + "namespace": "i18n", + "allowedContexts": ["content"], + "defaultContexts": ["content"], + "description": "Use the browser.i18n infrastructure to implement internationalization across your whole app or extension.", + "types": [ + { + "id": "LanguageCode", + "type": "string", + "description": "An ISO language code such as en or fr. For a complete list of languages supported by this method, see kLanguageInfoTable. For an unknown language, und will be returned, which means that [percentage] of the text is unknown to CLD" + } + ], + "functions": [ + { + "name": "getAcceptLanguages", + "type": "function", + "description": "Gets the accept-languages of the browser. This is different from the locale used by the browser; to get the locale, use $(ref:i18n.getUILanguage).", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + {"name": "languages", "type": "array", "items": {"$ref": "LanguageCode"}, "description": "Array of LanguageCode"} + ] + } + ] + }, + { + "name": "getMessage", + "type": "function", + "description": "Gets the localized string for the specified message. If the message is missing, this method returns an empty string (''). If the format of the getMessage() call is wrong — for example, messageName is not a string or the substitutions array has more than 9 elements — this method returns undefined.", + "parameters": [ + { + "type": "string", + "name": "messageName", + "description": "The name of the message, as specified in the $(topic:i18n-messages)[messages.json] file." + }, + { + "type": "any", + "name": "substitutions", + "optional": true, + "description": "Substitution strings, if the message requires any." + } + ], + "returns": { + "type": "string", + "description": "Message localized for current locale." + } + }, + { + "name": "getUILanguage", + "type": "function", + "description": "Gets the browser UI language of the browser. This is different from $(ref:i18n.getAcceptLanguages) which returns the preferred user languages.", + "parameters": [], + "returns": { + "type": "string", + "description": "The browser UI language code such as en-US or fr-FR." + } + }, + { + "name": "detectLanguage", + "type": "function", + "description": "Detects the language of the provided text using CLD.", + "async": "callback", + "parameters": [ + { + "type": "string", + "name": "text", + "description": "User input string to be translated." + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "type": "object", + "name": "result", + "description": "LanguageDetectionResult object that holds detected langugae reliability and array of DetectedLanguage", + "properties": { + "isReliable": { "type": "boolean", "description": "CLD detected language reliability" }, + "languages": + { + "type": "array", + "description": "array of detectedLanguage", + "items": + { + "type": "object", + "description": "DetectedLanguage object that holds detected ISO language code and its percentage in the input string", + "properties": + { + "language": + { + "$ref": "LanguageCode" + }, + "percentage": + { + "type": "integer", + "description": "The percentage of the detected language" + } + } + } + } + } + } + ] + } + ] + } + ], + "events": [] + } +] diff --git a/toolkit/components/webextensions/schemas/idle.json b/toolkit/components/webextensions/schemas/idle.json new file mode 100644 index 000000000..e0b3b951e --- /dev/null +++ b/toolkit/components/webextensions/schemas/idle.json @@ -0,0 +1,70 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "idle", + "description": "Use the browser.idle API to detect when the machine's idle state changes.", + "permissions": ["idle"], + "types": [ + { + "id": "IdleState", + "type": "string", + "enum": ["active", "idle"] + } + ], + "functions": [ + { + "name": "queryState", + "type": "function", + "description": "Returns \"idle\" if the user has not generated any input for a specified number of seconds, or \"active\" otherwise.", + "async": "callback", + "parameters": [ + { + "name": "detectionIntervalInSeconds", + "type": "integer", + "minimum": 15, + "description": "The system is considered idle if detectionIntervalInSeconds seconds have elapsed since the last user input detected." + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "newState", + "$ref": "IdleState" + } + ] + } + ] + }, + { + "name": "setDetectionInterval", + "type": "function", + "description": "Sets the interval, in seconds, used to determine when the system is in an idle state for onStateChanged events. The default interval is 60 seconds.", + "parameters": [ + { + "name": "intervalInSeconds", + "type": "integer", + "minimum": 15, + "description": "Threshold, in seconds, used to determine when the system is in an idle state." + } + ] + } + ], + "events": [ + { + "name": "onStateChanged", + "type": "function", + "description": "Fired when the system changes to an active or idle state. The event fires with \"idle\" if the the user has not generated any input for a specified number of seconds, and \"active\" when the user generates input on an idle system.", + "parameters": [ + { + "name": "newState", + "$ref": "IdleState" + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/jar.mn b/toolkit/components/webextensions/schemas/jar.mn new file mode 100644 index 000000000..0bdf35b0d --- /dev/null +++ b/toolkit/components/webextensions/schemas/jar.mn @@ -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/. + +toolkit.jar: +% content extensions %content/extensions/ + content/extensions/schemas/alarms.json + content/extensions/schemas/cookies.json + content/extensions/schemas/downloads.json + content/extensions/schemas/events.json + content/extensions/schemas/experiments.json + content/extensions/schemas/extension.json + content/extensions/schemas/extension_types.json + content/extensions/schemas/i18n.json + content/extensions/schemas/idle.json + content/extensions/schemas/management.json + content/extensions/schemas/manifest.json + content/extensions/schemas/native_host_manifest.json + content/extensions/schemas/notifications.json + content/extensions/schemas/runtime.json + content/extensions/schemas/storage.json + content/extensions/schemas/test.json + content/extensions/schemas/top_sites.json + content/extensions/schemas/web_navigation.json + content/extensions/schemas/web_request.json diff --git a/toolkit/components/webextensions/schemas/management.json b/toolkit/components/webextensions/schemas/management.json new file mode 100644 index 000000000..413ff1d0d --- /dev/null +++ b/toolkit/components/webextensions/schemas/management.json @@ -0,0 +1,250 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "Permission", + "choices": [{ + "type": "string", + "enum": [ + "management" + ] + }] + } + ] + }, + { + "namespace":"management", + "description": "The browser.management API provides ways to manage the list of extensions that are installed and running.", + "types": [ + { + "id": "IconInfo", + "description": "Information about an icon belonging to an extension.", + "type": "object", + "properties": { + "size": { + "type": "integer", + "description": "A number representing the width and height of the icon. Likely values include (but are not limited to) 128, 48, 24, and 16." + }, + "url": { + "type": "string", + "description": "The URL for this icon image. To display a grayscale version of the icon (to indicate that an extension is disabled, for example), append ?grayscale=true to the URL." + } + } + }, + { + "id": "ExtensionDisabledReason", + "description": "A reason the item is disabled.", + "type": "string", + "enum": ["unknown", "permissions_increase"] + }, + { + "id": "ExtensionType", + "description": "The type of this extension. Will always be 'extension'.", + "type": "string", + "enum": ["extension"] + }, + { + "id": "ExtensionInstallType", + "description": "How the extension was installed. One of
development: The extension was loaded unpacked in developer mode,
normal: The extension was installed normally via an .xpi file,
sideload: The extension was installed by other software on the machine,
other: The extension was installed by other means.", + "type": "string", + "enum": ["development", "normal", "sideload", "other"] + }, + { + "id": "ExtensionInfo", + "description": "Information about an installed extension.", + "type": "object", + "properties": { + "id": { + "description": "The extension's unique identifier.", + "type": "string" + }, + "name": { + "description": "The name of this extension.", + "type": "string" + }, + "shortName": { + "description": "A short version of the name of this extension.", + "type": "string" + }, + "description": { + "description": "The description of this extension.", + "type": "string" + }, + "version": { + "description": "The version of this extension.", + "type": "string" + }, + "versionName": { + "description": "The version name of this extension if the manifest specified one.", + "type": "string", + "optional": true + }, + "mayDisable": { + "description": "Whether this extension can be disabled or uninstalled by the user.", + "type": "boolean" + }, + "enabled": { + "description": "Whether it is currently enabled or disabled.", + "type": "boolean" + }, + "disabledReason": { + "description": "A reason the item is disabled.", + "$ref": "ExtensionDisabledReason", + "optional": true + }, + "type": { + "description": "The type of this extension. Will always return 'extension'.", + "$ref": "ExtensionType" + }, + "homepageUrl": { + "description": "The URL of the homepage of this extension.", + "type": "string", + "optional": true + }, + "updateUrl": { + "description": "The update URL of this extension.", + "type": "string", + "optional": true + }, + "optionsUrl": { + "description": "The url for the item's options page, if it has one.", + "type": "string" + }, + "icons": { + "description": "A list of icon information. Note that this just reflects what was declared in the manifest, and the actual image at that url may be larger or smaller than what was declared, so you might consider using explicit width and height attributes on img tags referencing these images. See the manifest documentation on icons for more details.", + "type": "array", + "optional": true, + "items": { + "$ref": "IconInfo" + } + }, + "permissions": { + "description": "Returns a list of API based permissions.", + "type": "array", + "items" : { + "type": "string" + } + }, + "hostPermissions": { + "description": "Returns a list of host based permissions.", + "type": "array", + "items" : { + "type": "string" + } + }, + "installType": { + "description": "How the extension was installed.", + "$ref": "ExtensionInstallType" + } + } + } + ], + "functions": [ + { + "name": "getAll", + "type": "function", + "permissions": ["management"], + "unsupported": true, + "description": "Returns a list of information about installed extensions.", + "async": "callback", + "parameters": [ + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ + { + "type": "array", + "name": "result", + "items": { + "$ref": "ExtensionInfo" + } + } + ] + } + ] + }, + { + "name": "get", + "type": "function", + "permissions": ["management"], + "unsupported": true, + "description": "Returns information about the installed extension that has the given ID.", + "async": "callback", + "parameters": [ + { + "name": "id", + "$ref": "manifest.ExtensionID", + "description": "The ID from an item of $(ref:management.ExtensionInfo)." + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ + { + "name": "result", + "$ref": "ExtensionInfo" + } + ] + } + ] + }, + { + "name": "getSelf", + "type": "function", + "description": "Returns information about the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [ + { + "name": "result", + "$ref": "ExtensionInfo" + } + ] + } + ] + }, + { + "name": "uninstallSelf", + "type": "function", + "description": "Uninstalls the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.", + "async": "callback", + "parameters": [ + { + "type": "object", + "name": "options", + "optional": true, + "properties": { + "showConfirmDialog": { + "type": "boolean", + "optional": true, + "description": "Whether or not a confirm-uninstall dialog should prompt the user. Defaults to false." + }, + "dialogMessage": { + "type": "string", + "optional": true, + "description": "The message to display to a user when being asked to confirm removal of the extension." + } + } + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [] + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/manifest.json b/toolkit/components/webextensions/schemas/manifest.json new file mode 100644 index 000000000..09e6b56fb --- /dev/null +++ b/toolkit/components/webextensions/schemas/manifest.json @@ -0,0 +1,377 @@ +[ + { + "namespace": "manifest", + "types": [ + { + "id": "WebExtensionManifest", + "type": "object", + "description": "Represents a WebExtension manifest.json file", + "properties": { + "manifest_version": { + "type": "integer", + "minimum": 2, + "maximum": 2 + }, + + "minimum_chrome_version":{ + "type": "string", + "optional": true + }, + + "applications": { + "type": "object", + "optional": true, + "properties": { + "gecko": { + "$ref": "FirefoxSpecificProperties", + "optional": true + } + } + }, + + "browser_specific_settings": { + "type": "object", + "optional": true, + "properties": { + "gecko": { + "$ref": "FirefoxSpecificProperties", + "optional": true + } + } + }, + + "name": { + "type": "string", + "optional": false, + "preprocess": "localize" + }, + + "short_name": { + "type": "string", + "optional": true, + "preprocess": "localize" + }, + + "description": { + "type": "string", + "optional": true, + "preprocess": "localize" + }, + + "author": { + "type": "string", + "optional": true, + "preprocess": "localize", + "onError": "warn" + }, + + "version": { + "type": "string", + "optional": false + }, + + "homepage_url": { + "type": "string", + "format": "url", + "optional": true, + "preprocess": "localize" + }, + + "icons": { + "type": "object", + "optional": true, + "patternProperties": { + "^[1-9]\\d*$": { "type": "string" } + } + }, + + "incognito": { + "type": "string", + "enum": ["spanning"], + "optional": true, + "onError": "warn" + }, + + "background": { + "choices": [ + { + "type": "object", + "properties": { + "page": { "$ref": "ExtensionURL" }, + "persistent": { + "optional": true, + "$ref": "PersistentBackgroundProperty" + } + }, + "additionalProperties": { "$ref": "UnrecognizedProperty" } + }, + { + "type": "object", + "properties": { + "scripts": { + "type": "array", + "items": { "$ref": "ExtensionURL" } + }, + "persistent": { + "optional": true, + "$ref": "PersistentBackgroundProperty" + } + }, + "additionalProperties": { "$ref": "UnrecognizedProperty" } + } + ], + "optional": true + }, + + "options_ui": { + "type": "object", + + "optional": true, + + "properties": { + "page": { "$ref": "ExtensionURL" }, + "browser_style": { + "type": "boolean", + "optional": true + }, + "chrome_style": { + "type": "boolean", + "optional": true + }, + "open_in_tab": { + "type": "boolean", + "optional": true + } + }, + + "additionalProperties": { + "type": "any", + "deprecated": "An unexpected property was found in the WebExtension manifest" + } + }, + + "content_scripts": { + "type": "array", + "optional": true, + "items": { "$ref": "ContentScript" } + }, + + "content_security_policy": { + "type": "string", + "optional": true, + "format": "contentSecurityPolicy", + "onError": "warn" + }, + + "permissions": { + "type": "array", + "items": { + "choices": [ + { "$ref": "Permission" }, + { + "type": "string", + "deprecated": "Unknown permission ${value}" + } + ] + }, + "optional": true + }, + + "web_accessible_resources": { + "type": "array", + "items": { "type": "string" }, + "optional": true + }, + + "developer": { + "type": "object", + "optional": true, + "properties": { + "name": { + "type": "string", + "optional": true, + "preprocess": "localize" + }, + "url": { + "type": "string", + "optional": true, + "preprocess": "localize" + } + } + } + + }, + + "additionalProperties": { "$ref": "UnrecognizedProperty" } + }, + { + "id": "Permission", + "choices": [ + { + "type": "string", + "enum": [ + "alarms", + "clipboardWrite", + "idle", + "notifications", + "storage" + ] + }, + { "$ref": "MatchPattern" } + ] + }, + { + "id": "ExtensionURL", + "type": "string", + "format": "strictRelativeUrl" + }, + { + "id": "ExtensionID", + "choices": [ + { + "type": "string", + "pattern": "(?i)^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}$" + }, + { + "type": "string", + "pattern": "(?i)^[a-z0-9-._]*@[a-z0-9-._]+$" + } + ] + }, + { + "id": "FirefoxSpecificProperties", + "type": "object", + "properties": { + "id": { + "$ref": "ExtensionID", + "optional": true + }, + + "update_url": { + "type": "string", + "format": "url", + "optional": true + }, + + "strict_min_version": { + "type": "string", + "optional": true + }, + + "strict_max_version": { + "type": "string", + "optional": true + } + } + }, + { + "id": "MatchPattern", + "choices": [ + { + "type": "string", + "enum": [""] + }, + { + "type": "string", + "pattern": "^(https?|file|ftp|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+)/.*$" + }, + { + "type": "string", + "pattern": "^file:///.*$" + } + ] + }, + { + "id": "ContentScript", + "type": "object", + "description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time. Based on InjectDetails, but using underscore rather than camel case naming conventions.", + "additionalProperties": { "$ref": "UnrecognizedProperty" }, + "properties": { + "matches": { + "type": "array", + "optional": false, + "minItems": 1, + "items": { "$ref": "MatchPattern" } + }, + "exclude_matches": { + "type": "array", + "optional": true, + "minItems": 1, + "items": { "$ref": "MatchPattern" } + }, + "include_globs": { + "type": "array", + "optional": true, + "items": { "type": "string" } + }, + "exclude_globs": { + "type": "array", + "optional": true, + "items": { "type": "string" } + }, + "css": { + "type": "array", + "optional": true, + "description": "The list of CSS files to inject", + "items": { "$ref": "ExtensionURL" } + }, + "js": { + "type": "array", + "optional": true, + "description": "The list of CSS files to inject", + "items": { "$ref": "ExtensionURL" } + }, + "all_frames": {"type": "boolean", "optional": true, "description": "If allFrames is true, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's false and is only injected into the top frame."}, + "match_about_blank": {"type": "boolean", "optional": true, "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is false."}, + "run_at": { + "$ref": "extensionTypes.RunAt", + "optional": true, + "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"." + } + } + }, + { + "id": "IconPath", + "choices": [ + { + "type": "object", + "patternProperties": { + "^[1-9]\\d*$": { "$ref": "ExtensionURL" } + }, + "additionalProperties": false + }, + { "$ref": "ExtensionURL" } + ] + }, + { + "id": "IconImageData", + "choices": [ + { + "type": "object", + "patternProperties": { + "^[1-9]\\d*$": { "$ref": "ImageData" } + }, + "additionalProperties": false + }, + { "$ref": "ImageData" } + ] + }, + { + "id": "ImageData", + "type": "object", + "isInstanceOf": "ImageData", + "postprocess": "convertImageDataToURL" + }, + { + "id": "UnrecognizedProperty", + "type": "any", + "deprecated": "An unexpected property was found in the WebExtension manifest." + }, + { + "id": "PersistentBackgroundProperty", + "type": "boolean", + "deprecated": "Event pages are not currently supported. This will run as a persistent background page." + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/moz.build b/toolkit/components/webextensions/schemas/moz.build new file mode 100644 index 000000000..aac3a838c --- /dev/null +++ b/toolkit/components/webextensions/schemas/moz.build @@ -0,0 +1,7 @@ +# -*- 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/. + +JAR_MANIFESTS += ['jar.mn'] diff --git a/toolkit/components/webextensions/schemas/native_host_manifest.json b/toolkit/components/webextensions/schemas/native_host_manifest.json new file mode 100644 index 000000000..4ad2ea7f1 --- /dev/null +++ b/toolkit/components/webextensions/schemas/native_host_manifest.json @@ -0,0 +1,37 @@ +[ + { + "namespace": "manifest", + "types": [ + { + "id": "NativeHostManifest", + "type": "object", + "description": "Represents a native host manifest file", + "properties": { + "name": { + "type": "string", + "pattern": "^\\w+(\\.\\w+)*$" + }, + "description": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "stdio" + ] + }, + "allowed_extensions": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "manifest.ExtensionID" + } + } + } + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/notifications.json b/toolkit/components/webextensions/schemas/notifications.json new file mode 100644 index 000000000..12878e8c8 --- /dev/null +++ b/toolkit/components/webextensions/schemas/notifications.json @@ -0,0 +1,416 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "notifications", + "permissions": ["notifications"], + "types": [ + { + "id": "TemplateType", + "type": "string", + "enum": [ + "basic", + "image", + "list", + "progress" + ] + }, + { + "id": "PermissionLevel", + "type": "string", + "enum": [ + "granted", + "denied" + ] + }, + { + "id": "NotificationItem", + "type": "object", + "properties": { + "title": { + "description": "Title of one item of a list notification.", + "type": "string" + }, + "message": { + "description": "Additional details about this item.", + "type": "string" + } + } + }, + { + "id": "CreateNotificationOptions", + "type": "object", + "properties": { + "type": { + "description": "Which type of notification to display.", + "$ref": "TemplateType" + }, + "iconUrl": { + "optional": true, + "description": "A URL to the sender's avatar, app icon, or a thumbnail for image notifications.", + "type": "string" + }, + "appIconMaskUrl": { + "optional": true, + "description": "A URL to the app icon mask.", + "type": "string" + }, + "title": { + "description": "Title of the notification (e.g. sender name for email).", + "type": "string" + }, + "message": { + "description": "Main notification content.", + "type": "string" + }, + "contextMessage": { + "optional": true, + "description": "Alternate notification content with a lower-weight font.", + "type": "string" + }, + "priority": { + "optional": true, + "description": "Priority ranges from -2 to 2. -2 is lowest priority. 2 is highest. Zero is default.", + "type": "integer", + "minimum": -2, + "maximum": 2 + }, + "eventTime": { + "optional": true, + "description": "A timestamp associated with the notification, in milliseconds past the epoch.", + "type": "number" + }, + "buttons": { + "unsupported": true, + "optional": true, + "description": "Text and icons for up to two notification action buttons.", + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "iconUrl": { + "optional": true, + "type": "string" + } + } + } + }, + "imageUrl": { + "optional": true, + "description": "A URL to the image thumbnail for image-type notifications.", + "type": "string" + }, + "items": { + "optional": true, + "description": "Items for multi-item notifications.", + "type": "array", + "items": { "$ref": "NotificationItem" } + }, + "progress": { + "optional": true, + "description": "Current progress ranges from 0 to 100.", + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "isClickable": { + "optional": true, + "description": "Whether to show UI indicating that the app will visibly respond to clicks on the body of a notification.", + "type": "boolean" + } + } + }, + { + "id": "UpdateNotificationOptions", + "type": "object", + "properties": { + "type": { + "optional": true, + "description": "Which type of notification to display.", + "$ref": "TemplateType" + }, + "iconUrl": { + "optional": true, + "description": "A URL to the sender's avatar, app icon, or a thumbnail for image notifications.", + "type": "string" + }, + "appIconMaskUrl": { + "optional": true, + "description": "A URL to the app icon mask.", + "type": "string" + }, + "title": { + "optional": true, + "description": "Title of the notification (e.g. sender name for email).", + "type": "string" + }, + "message": { + "optional": true, + "description": "Main notification content.", + "type": "string" + }, + "contextMessage": { + "optional": true, + "description": "Alternate notification content with a lower-weight font.", + "type": "string" + }, + "priority": { + "optional": true, + "description": "Priority ranges from -2 to 2. -2 is lowest priority. 2 is highest. Zero is default.", + "type": "integer", + "minimum": -2, + "maximum": 2 + }, + "eventTime": { + "optional": true, + "description": "A timestamp associated with the notification, in milliseconds past the epoch.", + "type": "number" + }, + "buttons": { + "unsupported": true, + "optional": true, + "description": "Text and icons for up to two notification action buttons.", + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "iconUrl": { + "optional": true, + "type": "string" + } + } + } + }, + "imageUrl": { + "optional": true, + "description": "A URL to the image thumbnail for image-type notifications.", + "type": "string" + }, + "items": { + "optional": true, + "description": "Items for multi-item notifications.", + "type": "array", + "items": { "$ref": "NotificationItem" } + }, + "progress": { + "optional": true, + "description": "Current progress ranges from 0 to 100.", + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "isClickable": { + "optional": true, + "description": "Whether to show UI indicating that the app will visibly respond to clicks on the body of a notification.", + "type": "boolean" + } + } + } + ], + "functions": [ + { + "name": "create", + "type": "function", + "description": "Creates and displays a notification.", + "async": "callback", + "parameters": [ + { + "optional": true, + "type": "string", + "name": "notificationId", + "description": "Identifier of the notification. If it is empty, this method generates an id. If it matches an existing notification, this method first clears that notification before proceeding with the create operation." + }, + { + "$ref": "CreateNotificationOptions", + "name": "options", + "description": "Contents of the notification." + }, + { + "optional": true, + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "notificationId", + "type": "string", + "description": "The notification id (either supplied or generated) that represents the created notification." + } + ] + } + ] + }, + { + "name": "update", + "unsupported": true, + "type": "function", + "description": "Updates an existing notification.", + "async": "callback", + "parameters": [ + { + "type": "string", + "name": "notificationId", + "description": "The id of the notification to be updated." + }, + { + "$ref": "UpdateNotificationOptions", + "name": "options", + "description": "Contents of the notification to update to." + }, + { + "optional": true, + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "wasUpdated", + "type": "boolean", + "description": "Indicates whether a matching notification existed." + } + ] + } + ] + }, + { + "name": "clear", + "type": "function", + "description": "Clears an existing notification.", + "async": "callback", + "parameters": [ + { + "type": "string", + "name": "notificationId", + "description": "The id of the notification to be updated." + }, + { + "optional": true, + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "wasCleared", + "type": "boolean", + "description": "Indicates whether a matching notification existed." + } + ] + } + ] + }, + { + "name": "getAll", + "type": "function", + "description": "Retrieves all the notifications.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "notifications", + "type": "object", + "description": "The set of notifications currently in the system." + } + ] + } + ] + }, + { + "name": "getPermissionLevel", + "unsupported": true, + "type": "function", + "description": "Retrieves whether the user has enabled notifications from this app or extension.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "level", + "$ref": "PermissionLevel", + "description": "The current permission level." + } + ] + } + ] + } + ], + "events": [ + { + "name": "onClosed", + "type": "function", + "description": "Fired when the notification closed, either by the system or by user action.", + "parameters": [ + { + "type": "string", + "name": "notificationId", + "description": "The notificationId of the closed notification." + }, + { + "type": "boolean", + "name": "byUser", + "description": "True if the notification was closed by the user." + } + ] + }, + { + "name": "onClicked", + "type": "function", + "description": "Fired when the user clicked in a non-button area of the notification.", + "parameters": [ + { + "type": "string", + "name": "notificationId", + "description": "The notificationId of the clicked notification." + } + ] + }, + { + "name": "onButtonClicked", + "type": "function", + "description": "Fired when the user pressed a button in the notification.", + "parameters": [ + { + "type": "string", + "name": "notificationId", + "description": "The notificationId of the clicked notification." + }, + { + "type": "number", + "name": "buttonIndex", + "description": "The index of the button clicked by the user." + } + ] + }, + { + "name": "onPermissionLevelChanged", + "unsupported": true, + "type": "function", + "description": "Fired when the user changes the permission level.", + "parameters": [ + { + "$ref": "PermissionLevel", + "name": "level", + "description": "The new permission level." + } + ] + }, + { + "name": "onShowSettings", + "unsupported": true, + "type": "function", + "description": "Fired when the user clicked on a link for the app's notification settings.", + "parameters": [ + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/runtime.json b/toolkit/components/webextensions/schemas/runtime.json new file mode 100644 index 000000000..b3f12a768 --- /dev/null +++ b/toolkit/components/webextensions/schemas/runtime.json @@ -0,0 +1,592 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "Permission", + "choices": [{ + "type": "string", + "enum": [ + "nativeMessaging" + ] + }] + } + ] + }, + { + "namespace": "runtime", + "allowedContexts": ["content"], + "description": "Use the browser.runtime API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.", + "types": [ + { + "id": "Port", + "type": "object", + "allowedContexts": ["content"], + "description": "An object which allows two way communication with other pages.", + "properties": { + "name": {"type": "string"}, + "disconnect": { "type": "function" }, + "onDisconnect": { "$ref": "events.Event" }, + "onMessage": { "$ref": "events.Event" }, + "postMessage": {"type": "function"}, + "sender": { + "$ref": "MessageSender", + "optional": true, + "description": "This property will only be present on ports passed to onConnect/onConnectExternal listeners." + } + }, + "additionalProperties": { "type": "any"} + }, + { + "id": "MessageSender", + "type": "object", + "allowedContexts": ["content"], + "description": "An object containing information about the script context that sent a message or request.", + "properties": { + "tab": {"$ref": "tabs.Tab", "optional": true, "description": "The $(ref:tabs.Tab) which opened the connection, if any. This property will only be present when the connection was opened from a tab (including content scripts), and only if the receiver is an extension, not an app."}, + "frameId": {"type": "integer", "optional": true, "description": "The $(topic:frame_ids)[frame] that opened the connection. 0 for top-level frames, positive for child frames. This will only be set when tab is set."}, + "id": {"type": "string", "optional": true, "description": "The ID of the extension or app that opened the connection, if any."}, + "url": {"type": "string", "optional": true, "description": "The URL of the page or frame that opened the connection. If the sender is in an iframe, it will be iframe's URL not the URL of the page which hosts it."}, + "tlsChannelId": {"unsupported": true, "type": "string", "optional": true, "description": "The TLS channel ID of the page or frame that opened the connection, if requested by the extension or app, and if available."} + } + }, + { + "id": "PlatformOs", + "type": "string", + "allowedContexts": ["content"], + "description": "The operating system the browser is running on.", + "enum": ["mac", "win", "android", "cros", "linux", "openbsd"] + }, + { + "id": "PlatformArch", + "type": "string", + "enum": ["arm", "x86-32", "x86-64"], + "allowedContexts": ["content"], + "description": "The machine's processor architecture." + }, + { + "id": "PlatformInfo", + "type": "object", + "allowedContexts": ["content"], + "description": "An object containing information about the current platform.", + "properties": { + "os": { + "$ref": "PlatformOs", + "description": "The operating system the browser is running on." + }, + "arch": { + "$ref": "PlatformArch", + "description": "The machine's processor architecture." + }, + "nacl_arch" : { + "unsupported": true, + "description": "The native client architecture. This may be different from arch on some platforms.", + "$ref": "PlatformNaclArch" + } + } + }, + { + "id": "BrowserInfo", + "type": "object", + "description": "An object containing information about the current browser.", + "properties": { + "name": { + "type": "string", + "description": "The name of the browser, for example 'Firefox'." + }, + "vendor": { + "type": "string", + "description": "The name of the browser vendor, for example 'Mozilla'." + }, + "version": { + "type": "string", + "description": "The browser's version, for example '42.0.0' or '0.8.1pre'." + }, + "buildID": { + "type": "string", + "description": "The browser's build ID/date, for example '20160101'." + } + } + }, + { + "id": "RequestUpdateCheckStatus", + "type": "string", + "enum": ["throttled", "no_update", "update_available"], + "allowedContexts": ["content"], + "description": "Result of the update check." + }, + { + "id": "OnInstalledReason", + "type": "string", + "enum": ["install", "update", "browser_update"], + "allowedContexts": ["content"], + "description": "The reason that this event is being dispatched." + }, + { + "id": "OnRestartRequiredReason", + "type": "string", + "allowedContexts": ["content"], + "description": "The reason that the event is being dispatched. 'app_update' is used when the restart is needed because the application is updated to a newer version. 'os_update' is used when the restart is needed because the browser/OS is updated to a newer version. 'periodic' is used when the system runs for more than the permitted uptime set in the enterprise policy.", + "enum": ["app_update", "os_update", "periodic"] + } + ], + "properties": { + "lastError": { + "type": "object", + "optional": true, + "allowedContexts": ["content"], + "description": "This will be defined during an API method callback if there was an error", + "properties": { + "message": { + "optional": true, + "type": "string", + "description": "Details about the error which occurred." + } + }, + "additionalProperties": { + "type": "any" + } + }, + "id": { + "type": "string", + "allowedContexts": ["content"], + "description": "The ID of the extension/app." + } + }, + "functions": [ + { + "name": "getBackgroundPage", + "type": "function", + "description": "Retrieves the JavaScript 'window' object for the background page running inside the current extension/app. If the background page is an event page, the system will ensure it is loaded before calling the callback. If there is no background page, an error is set.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "backgroundPage", + "optional": true, + "type": "object", + "isInstanceOf": "Window", + "additionalProperties": { "type": "any" }, + "description": "The JavaScript 'window' object for the background page." + } + ] + } + ] + }, + { + "name": "openOptionsPage", + "type": "function", + "description": "

Open your Extension's options page, if possible.

The precise behavior may depend on your manifest's $(topic:optionsV2)[options_ui] or $(topic:options)[options_page] key, or what the browser happens to support at the time.

If your Extension does not declare an options page, or the browser failed to create one for some other reason, the callback will set $(ref:lastError).

", + "async": "callback", + "parameters": [{ + "type": "function", + "name": "callback", + "parameters": [], + "optional": true + }] + }, + { + "name": "getManifest", + "allowedContexts": ["content"], + "description": "Returns details about the app or extension from the manifest. The object returned is a serialization of the full $(topic:manifest)[manifest file].", + "type": "function", + "parameters": [], + "returns": { + "type": "object", + "properties": {}, + "additionalProperties": { "type": "any" }, + "description": "The manifest details." + } + }, + { + "name": "getURL", + "type": "function", + "allowedContexts": ["content"], + "description": "Converts a relative path within an app/extension install directory to a fully-qualified URL.", + "parameters": [ + { + "type": "string", + "name": "path", + "description": "A path to a resource within an app/extension expressed relative to its install directory." + } + ], + "returns": { + "type": "string", + "description": "The fully-qualified URL to the resource." + } + }, + { + "name": "setUninstallURL", + "type": "function", + "description": "Sets the URL to be visited upon uninstallation. This may be used to clean up server-side data, do analytics, and implement surveys. Maximum 255 characters.", + "async": "callback", + "parameters": [ + { + "type": "string", + "name": "url", + "maxLength": 255, + "description": "URL to be opened after the extension is uninstalled. This URL must have an http: or https: scheme. Set an empty string to not open a new tab upon uninstallation." + }, + { + "type": "function", + "name": "callback", + "optional": true, + "description": "Called when the uninstall URL is set. If the given URL is invalid, $(ref:runtime.lastError) will be set.", + "parameters": [] + } + ] + }, + { + "name": "reload", + "description": "Reloads the app or extension.", + "type": "function", + "parameters": [] + }, + { + "name": "requestUpdateCheck", + "unsupported": true, + "type": "function", + "description": "Requests an update check for this app/extension.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "status", + "$ref": "RequestUpdateCheckStatus", + "description": "Result of the update check." + }, + { + "name": "details", + "type": "object", + "optional": true, + "properties": { + "version": { + "type": "string", + "description": "The version of the available update." + } + }, + "description": "If an update is available, this contains more information about the available update." + } + ] + } + ] + }, + { + "name": "restart", + "unsupported": true, + "description": "Restart the device when the app runs in kiosk mode. Otherwise, it's no-op.", + "type": "function", + "parameters": [] + }, + { + "name": "connect", + "type": "function", + "allowedContexts": ["content"], + "description": "Attempts to connect to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and $(topic:manifest/externally_connectable)[web messaging]. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via $(ref:tabs.connect).", + "parameters": [ + {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension or app to connect to. If omitted, a connection will be attempted with your own extension. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging]."}, + { + "type": "object", + "name": "connectInfo", + "properties": { + "name": { "type": "string", "optional": true, "description": "Will be passed into onConnect for processes that are listening for the connection event." }, + "includeTlsChannelId": { "type": "boolean", "optional": true, "description": "Whether the TLS channel ID will be passed into onConnectExternal for processes that are listening for the connection event." } + }, + "optional": true + } + ], + "returns": { + "$ref": "Port", + "description": "Port through which messages can be sent and received. The port's $(ref:runtime.Port onDisconnect) event is fired if the extension/app does not exist. " + } + }, + { + "name": "connectNative", + "type": "function", + "description": "Connects to a native application in the host machine.", + "permissions": ["nativeMessaging"], + "parameters": [ + { + "type": "string", + "name": "application", + "description": "The name of the registered application to connect to." + } + ], + "returns": { + "$ref": "Port", + "description": "Port through which messages can be sent and received with the application" + } + }, + { + "name": "sendMessage", + "type": "function", + "allowAmbiguousOptionalArguments": true, + "allowedContexts": ["content"], + "description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).", + "async": "responseCallback", + "parameters": [ + {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension/app to send the message to. If omitted, the message will be sent to your own extension/app. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging]."}, + { "type": "any", "name": "message" }, + { + "type": "object", + "name": "options", + "properties": { + "includeTlsChannelId": { "type": "boolean", "optional": true, "description": "Whether the TLS channel ID will be passed into onMessageExternal for processes that are listening for the connection event." } + }, + "optional": true + }, + { + "type": "function", + "name": "responseCallback", + "optional": true, + "parameters": [ + { + "name": "response", + "type": "any", + "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the extension, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message." + } + ] + } + ] + }, + { + "name": "sendNativeMessage", + "type": "function", + "description": "Send a single message to a native application.", + "permissions": ["nativeMessaging"], + "async": "responseCallback", + "parameters": [ + { + "name": "application", + "description": "The name of the native messaging host.", + "type": "string" + }, + { + "name": "message", + "description": "The message that will be passed to the native messaging host.", + "type": "any" + }, + { + "type": "function", + "name": "responseCallback", + "optional": true, + "parameters": [ + { + "name": "response", + "type": "any", + "description": "The response message sent by the native messaging host. If an error occurs while connecting to the native messaging host, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message." + } + ] + } + ] + }, + { + "name": "getBrowserInfo", + "type": "function", + "description": "Returns information about the current browser.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "description": "Called with results", + "parameters": [ + { + "name": "browserInfo", + "$ref": "BrowserInfo" + } + ] + } + ] + }, + { + "name": "getPlatformInfo", + "type": "function", + "description": "Returns information about the current platform.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "description": "Called with results", + "parameters": [ + { + "name": "platformInfo", + "$ref": "PlatformInfo" + } + ] + } + ] + }, + { + "name": "getPackageDirectoryEntry", + "unsupported": true, + "type": "function", + "description": "Returns a DirectoryEntry for the package directory.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "directoryEntry", + "type": "object", + "additionalProperties": { "type": "any" }, + "isInstanceOf": "DirectoryEntry" + } + ] + } + ] + } + ], + "events": [ + { + "name": "onStartup", + "type": "function", + "description": "Fired when a profile that has this extension installed first starts up. This event is not fired for incognito profiles." + }, + { + "name": "onInstalled", + "type": "function", + "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when the browser is updated to a new version.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "reason": { + "$ref": "OnInstalledReason", + "description": "The reason that this event is being dispatched." + }, + "previousVersion": { + "type": "string", + "optional": true, + "unsupported": true, + "description": "Indicates the previous version of the extension, which has just been updated. This is present only if 'reason' is 'update'." + }, + "id": { + "type": "string", + "optional": true, + "unsupported": true, + "description": "Indicates the ID of the imported shared module extension which updated. This is present only if 'reason' is 'shared_module_update'." + } + } + } + ] + }, + { + "name": "onSuspend", + "unsupported": true, + "type": "function", + "description": "Sent to the event page just before it is unloaded. This gives the extension opportunity to do some clean up. Note that since the page is unloading, any asynchronous operations started while handling this event are not guaranteed to complete. If more activity for the event page occurs before it gets unloaded the onSuspendCanceled event will be sent and the page won't be unloaded. " + }, + { + "name": "onSuspendCanceled", + "unsupported": true, + "type": "function", + "description": "Sent after onSuspend to indicate that the app won't be unloaded after all." + }, + { + "name": "onUpdateAvailable", + "type": "function", + "description": "Fired when an update is available, but isn't installed immediately because the app is currently running. If you do nothing, the update will be installed the next time the background page gets unloaded, if you want it to be installed sooner you can explicitly call $(ref:runtime.reload). If your extension is using a persistent background page, the background page of course never gets unloaded, so unless you call $(ref:runtime.reload) manually in response to this event the update will not get installed until the next time the browser itself restarts. If no handlers are listening for this event, and your extension has a persistent background page, it behaves as if $(ref:runtime.reload) is called in response to this event.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "version": { + "type": "string", + "description": "The version number of the available update." + } + }, + "additionalProperties": { "type": "any" }, + "description": "The manifest details of the available update." + } + ] + }, + { + "name": "onBrowserUpdateAvailable", + "unsupported": true, + "type": "function", + "description": "Fired when an update for the browser is available, but isn't installed immediately because a browser restart is required.", + "deprecated": "Please use $(ref:runtime.onRestartRequired).", + "parameters": [] + }, + { + "name": "onConnect", + "type": "function", + "allowedContexts": ["content"], + "description": "Fired when a connection is made from either an extension process or a content script.", + "parameters": [ + {"$ref": "Port", "name": "port"} + ] + }, + { + "name": "onConnectExternal", + "unsupported": true, + "type": "function", + "description": "Fired when a connection is made from another extension.", + "parameters": [ + {"$ref": "Port", "name": "port"} + ] + }, + { + "name": "onMessage", + "type": "function", + "allowedContexts": ["content"], + "description": "Fired when a message is sent from either an extension process or a content script.", + "parameters": [ + {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."}, + {"name": "sender", "$ref": "MessageSender" }, + {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one onMessage listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called)." } + ], + "returns": { + "type": "boolean", + "optional": true, + "description": "Return true from the event listener if you wish to call sendResponse after the event listener returns." + } + }, + { + "name": "onMessageExternal", + "unsupported": true, + "type": "function", + "description": "Fired when a message is sent from another extension/app. Cannot be used in a content script.", + "parameters": [ + {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."}, + {"name": "sender", "$ref": "MessageSender" }, + {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one onMessage listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called)." } + ], + "returns": { + "type": "boolean", + "optional": true, + "description": "Return true from the event listener if you wish to call sendResponse after the event listener returns." + } + }, + { + "name": "onRestartRequired", + "unsupported": true, + "type": "function", + "description": "Fired when an app or the device that it runs on needs to be restarted. The app should close all its windows at its earliest convenient time to let the restart to happen. If the app does nothing, a restart will be enforced after a 24-hour grace period has passed. Currently, this event is only fired for Chrome OS kiosk apps.", + "parameters": [ + { + "$ref": "OnRestartRequiredReason", + "name": "reason", + "description": "The reason that the event is being dispatched." + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/storage.json b/toolkit/components/webextensions/schemas/storage.json new file mode 100644 index 000000000..a54a20942 --- /dev/null +++ b/toolkit/components/webextensions/schemas/storage.json @@ -0,0 +1,229 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "storage", + "allowedContexts": ["content"], + "defaultContexts": ["content"], + "description": "Use the browser.storage API to store, retrieve, and track changes to user data.", + "permissions": ["storage"], + "types": [ + { + "id": "StorageChange", + "type": "object", + "properties": { + "oldValue": { + "type": "any", + "description": "The old value of the item, if there was an old value.", + "optional": true + }, + "newValue": { + "type": "any", + "description": "The new value of the item, if there is a new value.", + "optional": true + } + } + }, + { + "id": "StorageArea", + "type": "object", + "functions": [ + { + "name": "get", + "type": "function", + "description": "Gets one or more items from storage.", + "async": "callback", + "parameters": [ + { + "name": "keys", + "choices": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" } }, + { + "type": "object", + "description": "Storage items to return in the callback, where the values are replaced with those from storage if they exist.", + "additionalProperties": { "type": "any" } + } + ], + "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object). An empty list or object will return an empty result object. Pass in null to get the entire contents of storage.", + "optional": true + }, + { + "name": "callback", + "type": "function", + "description": "Callback with storage items, or on failure (in which case $(ref:runtime.lastError) will be set).", + "parameters": [ + { + "name": "items", + "type": "object", + "additionalProperties": { "type": "any" }, + "description": "Object with items in their key-value mappings." + } + ] + } + ] + }, + { + "name": "getBytesInUse", + "unsupported": true, + "type": "function", + "description": "Gets the amount of space (in bytes) being used by one or more items.", + "async": "callback", + "parameters": [ + { + "name": "keys", + "choices": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" } } + ], + "description": "A single key or list of keys to get the total usage for. An empty list will return 0. Pass in null to get the total usage of all of storage.", + "optional": true + }, + { + "name": "callback", + "type": "function", + "description": "Callback with the amount of space being used by storage, or on failure (in which case $(ref:runtime.lastError) will be set).", + "parameters": [ + { + "name": "bytesInUse", + "type": "integer", + "description": "Amount of space being used in storage, in bytes." + } + ] + } + ] + }, + { + "name": "set", + "type": "function", + "description": "Sets multiple items.", + "async": "callback", + "parameters": [ + { + "name": "items", + "type": "object", + "additionalProperties": { "type": "any" }, + "description": "

An object which gives each key/value pair to update storage with. Any other key/value pairs in storage will not be affected.

Primitive values such as numbers will serialize as expected. Values with a typeof \"object\" and \"function\" will typically serialize to {}, with the exception of Array (serializes as expected), Date, and Regex (serialize using their String representation).

" + }, + { + "name": "callback", + "type": "function", + "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).", + "parameters": [], + "optional": true + } + ] + }, + { + "name": "remove", + "type": "function", + "description": "Removes one or more items from storage.", + "async": "callback", + "parameters": [ + { + "name": "keys", + "choices": [ + {"type": "string"}, + {"type": "array", "items": {"type": "string"}} + ], + "description": "A single key or a list of keys for items to remove." + }, + { + "name": "callback", + "type": "function", + "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).", + "parameters": [], + "optional": true + } + ] + }, + { + "name": "clear", + "type": "function", + "description": "Removes all items from storage.", + "async": "callback", + "parameters": [ + { + "name": "callback", + "type": "function", + "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).", + "parameters": [], + "optional": true + } + ] + } + ] + } + ], + "events": [ + { + "name": "onChanged", + "type": "function", + "description": "Fired when one or more items change.", + "parameters": [ + { + "name": "changes", + "type": "object", + "additionalProperties": { "$ref": "StorageChange" }, + "description": "Object mapping each key that changed to its corresponding $(ref:storage.StorageChange) for that item." + }, + { + "name": "areaName", + "type": "string", + "description": "The name of the storage area (\"sync\", \"local\" or \"managed\") the changes are for." + } + ] + } + ], + "properties": { + "sync": { + "$ref": "StorageArea", + "description": "Items in the sync storage area are synced by the browser.", + "properties": { + "QUOTA_BYTES": { + "value": 102400, + "description": "The maximum total amount (in bytes) of data that can be stored in sync storage, as measured by the JSON stringification of every value plus every key's length. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)." + }, + "QUOTA_BYTES_PER_ITEM": { + "value": 8192, + "description": "The maximum size (in bytes) of each individual item in sync storage, as measured by the JSON stringification of its value plus its key length. Updates containing items larger than this limit will fail immediately and set $(ref:runtime.lastError)." + }, + "MAX_ITEMS": { + "value": 512, + "description": "The maximum number of items that can be stored in sync storage. Updates that would cause this limit to be exceeded will fail immediately and set $(ref:runtime.lastError)." + }, + "MAX_WRITE_OPERATIONS_PER_HOUR": { + "value": 1800, + "description": "

The maximum number of set, remove, or clear operations that can be performed each hour. This is 1 every 2 seconds, a lower ceiling than the short term higher writes-per-minute limit.

Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError).

" + }, + "MAX_WRITE_OPERATIONS_PER_MINUTE": { + "value": 120, + "description": "

The maximum number of set, remove, or clear operations that can be performed each minute. This is 2 per second, providing higher throughput than writes-per-hour over a shorter period of time.

Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError).

" + }, + "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE": { + "value": 1000000, + "deprecated": "The storage.sync API no longer has a sustained write operation quota.", + "description": "" + } + } + }, + "local": { + "$ref": "StorageArea", + "description": "Items in the local storage area are local to each machine.", + "properties": { + "QUOTA_BYTES": { + "value": 5242880, + "description": "The maximum amount (in bytes) of data that can be stored in local storage, as measured by the JSON stringification of every value plus every key's length. This value will be ignored if the extension has the unlimitedStorage permission. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)." + } + } + }, + "managed": { + "unsupported": true, + "$ref": "StorageArea", + "description": "Items in the managed storage area are set by the domain administrator, and are read-only for the extension; trying to modify this namespace results in an error." + } + } + } +] diff --git a/toolkit/components/webextensions/schemas/test.json b/toolkit/components/webextensions/schemas/test.json new file mode 100644 index 000000000..25a62a96b --- /dev/null +++ b/toolkit/components/webextensions/schemas/test.json @@ -0,0 +1,215 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "test", + "allowedContexts": ["content"], + "defaultContexts": ["content"], + "description": "none", + "functions": [ + { + "name": "notifyFail", + "type": "function", + "description": "Notifies the browser process that test code running in the extension failed. This is only used for internal unit testing.", + "parameters": [ + {"type": "string", "name": "message"} + ] + }, + { + "name": "notifyPass", + "type": "function", + "description": "Notifies the browser process that test code running in the extension passed. This is only used for internal unit testing.", + "parameters": [ + {"type": "string", "name": "message", "optional": true} + ] + }, + { + "name": "log", + "type": "function", + "description": "Logs a message during internal unit testing.", + "parameters": [ + {"type": "string", "name": "message"} + ] + }, + { + "name": "sendMessage", + "type": "function", + "description": "Sends a string message to the browser process, generating a Notification that C++ test code can wait for.", + "allowAmbiguousOptionalArguments": true, + "parameters": [ + {"type": "any", "name": "arg1", "optional": true}, + {"type": "any", "name": "arg2", "optional": true} + ] + }, + { + "name": "fail", + "type": "function", + "parameters": [ + {"type": "any", "name": "message", "optional": true} + ] + }, + { + "name": "succeed", + "type": "function", + "parameters": [ + {"type": "any", "name": "message", "optional": true} + ] + }, + { + "name": "assertTrue", + "type": "function", + "allowAmbiguousOptionalArguments": true, + "parameters": [ + {"name": "test", "type": "any", "optional": true}, + {"type": "string", "name": "message", "optional": true} + ] + }, + { + "name": "assertFalse", + "type": "function", + "allowAmbiguousOptionalArguments": true, + "parameters": [ + {"name": "test", "type": "any", "optional": true}, + {"type": "string", "name": "message", "optional": true} + ] + }, + { + "name": "assertBool", + "type": "function", + "unsupported": true, + "parameters": [ + { + "name": "test", + "choices": [ + {"type": "string"}, + {"type": "boolean"} + ] + }, + {"type": "boolean", "name": "expected"}, + {"type": "string", "name": "message", "optional": true} + ] + }, + { + "name": "checkDeepEq", + "type": "function", + "unsupported": true, + "allowAmbiguousOptionalArguments": true, + "parameters": [ + {"type": "any", "name": "expected"}, + {"type": "any", "name": "actual"} + ] + }, + { + "name": "assertEq", + "type": "function", + "allowAmbiguousOptionalArguments": true, + "parameters": [ + {"type": "any", "name": "expected", "optional": true}, + {"type": "any", "name": "actual", "optional": true}, + {"type": "string", "name": "message", "optional": true} + ] + }, + { + "name": "assertNoLastError", + "type": "function", + "unsupported": true, + "parameters": [] + }, + { + "name": "assertLastError", + "type": "function", + "unsupported": true, + "parameters": [ + {"type": "string", "name": "expectedError"} + ] + }, + { + "name": "assertRejects", + "type": "function", + "async": true, + "parameters": [ + { + "name": "promise", + "$ref": "Promise" + }, + { + "name": "expectedError", + "$ref": "ExpectedError", + "optional": true + }, + { + "name": "message", + "type": "string", + "optional": true + } + ] + }, + { + "name": "assertThrows", + "type": "function", + "parameters": [ + { + "name": "func", + "type": "function" + }, + { + "name": "expectedError", + "$ref": "ExpectedError", + "optional": true + }, + { + "name": "message", + "type": "string", + "optional": true + } + ] + } + ], + "types": [ + { + "id": "ExpectedError", + "choices": [ + {"type": "string"}, + {"type": "object", "isInstanceOf": "RegExp", "additionalProperties": true}, + {"type": "function"} + ] + }, + { + "id": "Promise", + "choices": [ + { + "type": "object", + "properties": { + "then": {"type": "function"} + }, + "additionalProperties": true + }, + { + "type": "object", + "isInstanceOf": "Promise", + "additionalProperties": true + } + ] + } + ], + "events": [ + { + "name": "onMessage", + "type": "function", + "description": "Used to test sending messages to extensions.", + "parameters": [ + { + "type": "string", + "name": "message" + }, + { + "type": "any", + "name": "argument" + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/top_sites.json b/toolkit/components/webextensions/schemas/top_sites.json new file mode 100644 index 000000000..fbfbc4b62 --- /dev/null +++ b/toolkit/components/webextensions/schemas/top_sites.json @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "Permission", + "choices": [{ + "type": "string", + "enum": [ + "topSites" + ] + }] + } + ] + }, + { + "namespace": "topSites", + "description": "Use the chrome.topSites API to access the top sites that are displayed on the new tab page. ", + "permissions": ["topSites"], + "types": [ + { + "id": "MostVisitedURL", + "type": "object", + "description": "An object encapsulating a most visited URL, such as the URLs on the new tab page.", + "properties": { + "url": { + "type": "string", + "description": "The most visited URL." + }, + "title": { + "type": "string", + "optional": true, + "description": "The title of the page." + } + } + } + ], + "functions": [ + { + "name": "get", + "type": "function", + "description": "Gets a list of top sites.", + "async": "callback", + "parameters": [ + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "results", + "type": "array", + "items": { + "$ref": "MostVisitedURL" + } + } + ] + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/web_navigation.json b/toolkit/components/webextensions/schemas/web_navigation.json new file mode 100644 index 000000000..1e13b181a --- /dev/null +++ b/toolkit/components/webextensions/schemas/web_navigation.json @@ -0,0 +1,387 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "Permission", + "choices": [{ + "type": "string", + "enum": [ + "webNavigation" + ] + }] + } + ] + }, + { + "namespace": "webNavigation", + "description": "Use the browser.webNavigation API to receive notifications about the status of navigation requests in-flight.", + "permissions": ["webNavigation"], + "types": [ + { + "id": "TransitionType", + "type": "string", + "enum": ["link", "typed", "auto_bookmark", "auto_subframe", "manual_subframe", "generated", "start_page", "form_submit", "reload", "keyword", "keyword_generated"], + "description": "Cause of the navigation. The same transition types as defined in the history API are used. These are the same transition types as defined in the $(topic:transition_types)[history API] except with \"start_page\" in place of \"auto_toplevel\" (for backwards compatibility)." + }, + { + "id": "TransitionQualifier", + "type": "string", + "enum": ["client_redirect", "server_redirect", "forward_back", "from_address_bar"] + }, + { + "id": "EventUrlFilters", + "type": "object", + "properties": { + "url": { + "type": "array", + "minItems": 1, + "items": { "$ref": "events.UrlFilter" } + } + } + } + ], + "functions": [ + { + "name": "getFrame", + "type": "function", + "description": "Retrieves information about the given frame. A frame refers to an <iframe> or a <frame> of a web page and is identified by a tab ID and a frame ID.", + "async": "callback", + "parameters": [ + { + "type": "object", + "name": "details", + "description": "Information about the frame to retrieve information about.", + "properties": { + "tabId": { "type": "integer", "minimum": 0, "description": "The ID of the tab in which the frame is." }, + "processId": {"optional": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."}, + "frameId": { "type": "integer", "minimum": 0, "description": "The ID of the frame in the given tab." } + } + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "type": "object", + "name": "details", + "optional": true, + "description": "Information about the requested frame, null if the specified frame ID and/or tab ID are invalid.", + "properties": { + "errorOccurred": { + "unsupported": true, + "type": "boolean", + "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired." + }, + "url": { + "type": "string", + "description": "The URL currently associated with this frame, if the frame identified by the frameId existed at one point in the given tab. The fact that an URL is associated with a given frameId does not imply that the corresponding frame still exists." + }, + "parentFrameId": { + "type": "integer", + "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists." + } + } + } + ] + } + ] + }, + { + "name": "getAllFrames", + "type": "function", + "description": "Retrieves information about all frames of a given tab.", + "async": "callback", + "parameters": [ + { + "type": "object", + "name": "details", + "description": "Information about the tab to retrieve all frames from.", + "properties": { + "tabId": { "type": "integer", "minimum": 0, "description": "The ID of the tab." } + } + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "details", + "type": "array", + "description": "A list of frames in the given tab, null if the specified tab ID is invalid.", + "optional": true, + "items": { + "type": "object", + "properties": { + "errorOccurred": { + "unsupported": true, + "type": "boolean", + "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired." + }, + "processId": { + "unsupported": true, + "type": "integer", + "description": "The ID of the process runs the renderer for this tab." + }, + "frameId": { + "type": "integer", + "description": "The ID of the frame. 0 indicates that this is the main frame; a positive value indicates the ID of a subframe." + }, + "parentFrameId": { + "type": "integer", + "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists." + }, + "url": { + "type": "string", + "description": "The URL currently associated with this frame." + } + } + } + } + ] + } + ] + } + ], + "events": [ + { + "name": "onBeforeNavigate", + "type": "function", + "description": "Fired when a navigation is about to occur.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation is about to occur."}, + "url": {"type": "string"}, + "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."}, + "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique for a given tab and process."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."}, + "timeStamp": {"type": "number", "description": "The time when the browser was about to start the navigation, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "name": "filters", + "optional": true, + "$ref": "EventUrlFilters", + "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event." + } + ] + }, + { + "name": "onCommitted", + "type": "function", + "description": "Fired when a navigation is committed. The document (and the resources it refers to, such as images and subframes) might still be downloading, but at least part of the document has been received from the server and the browser has decided to switch to the new document.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."}, + "url": {"type": "string"}, + "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."}, + "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."}, + "transitionType": {"unsupported": true, "$ref": "TransitionType", "description": "Cause of the navigation."}, + "transitionQualifiers": {"unsupported": true, "type": "array", "description": "A list of transition qualifiers.", "items": {"$ref": "TransitionQualifier"}}, + "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "name": "filters", + "optional": true, + "$ref": "EventUrlFilters", + "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event." + } + ] + }, + { + "name": "onDOMContentLoaded", + "type": "function", + "description": "Fired when the page's DOM is fully constructed, but the referenced resources may not finish loading.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."}, + "url": {"type": "string"}, + "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."}, + "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."}, + "timeStamp": {"type": "number", "description": "The time when the page's DOM was fully constructed, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "name": "filters", + "optional": true, + "$ref": "EventUrlFilters", + "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event." + } + ] + }, + { + "name": "onCompleted", + "type": "function", + "description": "Fired when a document, including the resources it refers to, is completely loaded and initialized.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."}, + "url": {"type": "string"}, + "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."}, + "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."}, + "timeStamp": {"type": "number", "description": "The time when the document finished loading, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "name": "filters", + "optional": true, + "$ref": "EventUrlFilters", + "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event." + } + ] + }, + { + "name": "onErrorOccurred", + "type": "function", + "description": "Fired when an error occurs and the navigation is aborted. This can happen if either a network error occurred, or the user aborted the navigation.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."}, + "url": {"type": "string"}, + "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."}, + "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."}, + "error": {"unsupported": true, "type": "string", "description": "The error description."}, + "timeStamp": {"type": "number", "description": "The time when the error occurred, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "name": "filters", + "optional": true, + "$ref": "EventUrlFilters", + "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event." + } + ] + }, + { + "name": "onCreatedNavigationTarget", + "unsupported": true, + "type": "function", + "description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "sourceTabId": {"type": "integer", "description": "The ID of the tab in which the navigation is triggered."}, + "sourceProcessId": {"type": "integer", "description": "The ID of the process runs the renderer for the source tab."}, + "sourceFrameId": {"type": "integer", "description": "The ID of the frame with sourceTabId in which the navigation is triggered. 0 indicates the main frame."}, + "url": {"type": "string", "description": "The URL to be opened in the new window."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the url is opened"}, + "timeStamp": {"type": "number", "description": "The time when the browser was about to create a new view, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "name": "filters", + "optional": true, + "$ref": "EventUrlFilters", + "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event." + } + ] + }, + { + "name": "onReferenceFragmentUpdated", + "type": "function", + "description": "Fired when the reference fragment of a frame was updated. All future events for that frame will use the updated URL.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."}, + "url": {"type": "string"}, + "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."}, + "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."}, + "transitionType": {"unsupported": true, "$ref": "TransitionType", "description": "Cause of the navigation."}, + "transitionQualifiers": {"unsupported": true, "type": "array", "description": "A list of transition qualifiers.", "items": {"$ref": "TransitionQualifier"}}, + "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "name": "filters", + "optional": true, + "$ref": "EventUrlFilters", + "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event." + } + ] + }, + { + "name": "onTabReplaced", + "type": "function", + "description": "Fired when the contents of the tab is replaced by a different (usually previously pre-rendered) tab.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "replacedTabId": {"type": "integer", "description": "The ID of the tab that was replaced."}, + "tabId": {"type": "integer", "description": "The ID of the tab that replaced the old tab."}, + "timeStamp": {"type": "number", "description": "The time when the replacement happened, in milliseconds since the epoch."} + } + } + ] + }, + { + "name": "onHistoryStateUpdated", + "type": "function", + "description": "Fired when the frame's history was updated to a new URL. All future events for that frame will use the updated URL.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."}, + "url": {"type": "string"}, + "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."}, + "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."}, + "transitionType": {"unsupported": true, "$ref": "TransitionType", "description": "Cause of the navigation."}, + "transitionQualifiers": {"unsupported": true, "type": "array", "description": "A list of transition qualifiers.", "items": {"$ref": "TransitionQualifier"}}, + "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "name": "filters", + "optional": true, + "$ref": "EventUrlFilters", + "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event." + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/schemas/web_request.json b/toolkit/components/webextensions/schemas/web_request.json new file mode 100644 index 000000000..4035aea6e --- /dev/null +++ b/toolkit/components/webextensions/schemas/web_request.json @@ -0,0 +1,616 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "manifest", + "types": [ + { + "$extend": "Permission", + "choices": [{ + "type": "string", + "enum": [ + "webRequest", + "webRequestBlocking" + ] + }] + } + ] + }, + { + "namespace": "webRequest", + "description": "Use the browser.webRequest API to observe and analyze traffic and to intercept, block, or modify requests in-flight.", + "permissions": ["webRequest"], + "properties": { + "MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES": { + "value": 20, + "description": "The maximum number of times that handlerBehaviorChanged can be called per 10 minute sustained interval. handlerBehaviorChanged is an expensive function call that shouldn't be called often." + } + }, + "types": [ + { + "id": "ResourceType", + "type": "string", + "enum": [ + "main_frame", + "sub_frame", + "stylesheet", + "script", + "image", + "object", + "xmlhttprequest", + "xbl", + "xslt", + "ping", + "beacon", + "xml_dtd", + "font", + "media", + "websocket", + "csp_report", + "imageset", + "web_manifest", + "other" + ] + }, + { + "id": "OnBeforeRequestOptions", + "type": "string", + "enum": ["blocking", "requestBody"] + }, + { + "id": "OnBeforeSendHeadersOptions", + "type": "string", + "enum": ["requestHeaders", "blocking"] + }, + { + "id": "OnSendHeadersOptions", + "type": "string", + "enum": ["requestHeaders"] + }, + { + "id": "OnHeadersReceivedOptions", + "type": "string", + "enum": ["blocking", "responseHeaders"] + }, + { + "id": "OnAuthRequiredOptions", + "type": "string", + "enum": ["responseHeaders", "blocking", "asyncBlocking"] + }, + { + "id": "OnResponseStartedOptions", + "type": "string", + "enum": ["responseHeaders"] + }, + { + "id": "OnBeforeRedirectOptions", + "type": "string", + "enum": ["responseHeaders"] + }, + { + "id": "OnCompletedOptions", + "type": "string", + "enum": ["responseHeaders"] + }, + { + "id": "RequestFilter", + "type": "object", + "description": "An object describing filters to apply to webRequest events.", + "properties": { + "urls": { + "type": "array", + "description": "A list of URLs or URL patterns. Requests that cannot match any of the URLs will be filtered out.", + "items": { "type": "string" } + }, + "types": { + "type": "array", + "optional": true, + "description": "A list of request types. Requests that cannot match any of the types will be filtered out.", + "items": { "$ref": "ResourceType" } + }, + "tabId": { "type": "integer", "optional": true }, + "windowId": { "type": "integer", "optional": true } + } + }, + { + "id": "HttpHeaders", + "type": "array", + "description": "An array of HTTP headers. Each header is represented as a dictionary containing the keys name and either value or binaryValue.", + "items": { + "type": "object", + "properties": { + "name": {"type": "string", "description": "Name of the HTTP header."}, + "value": {"type": "string", "optional": true, "description": "Value of the HTTP header if it can be represented by UTF-8."}, + "binaryValue": { + "type": "array", + "optional": true, + "description": "Value of the HTTP header if it cannot be represented by UTF-8, stored as individual byte values (0..255).", + "items": {"type": "integer"} + } + } + } + }, + { + "id": "BlockingResponse", + "type": "object", + "description": "Returns value for event handlers that have the 'blocking' extraInfoSpec applied. Allows the event handler to modify network requests.", + "properties": { + "cancel": { + "type": "boolean", + "optional": true, + "description": "If true, the request is cancelled. Used in onBeforeRequest, this prevents the request from being sent." + }, + "redirectUrl": { + "type": "string", + "optional": true, + "description": "Only used as a response to the onBeforeRequest and onHeadersReceived events. If set, the original request is prevented from being sent/completed and is instead redirected to the given URL. Redirections to non-HTTP schemes such as data: are allowed. Redirects initiated by a redirect action use the original request method for the redirect, with one exception: If the redirect is initiated at the onHeadersReceived stage, then the redirect will be issued using the GET method." + }, + "requestHeaders": { + "$ref": "HttpHeaders", + "optional": true, + "description": "Only used as a response to the onBeforeSendHeaders event. If set, the request is made with these request headers instead." + }, + "responseHeaders": { + "$ref": "HttpHeaders", + "optional": true, + "description": "Only used as a response to the onHeadersReceived event. If set, the server is assumed to have responded with these response headers instead. Only return responseHeaders if you really want to modify the headers in order to limit the number of conflicts (only one extension may modify responseHeaders for each request)." + }, + "authCredentials": { + "type": "object", + "description": "Only used as a response to the onAuthRequired event. If set, the request is made using the supplied credentials.", + "optional": true, + "properties": { + "username": {"type": "string"}, + "password": {"type": "string"} + } + } + } + }, + { + "id": "UploadData", + "type": "object", + "properties": { + "bytes": { + "type": "any", + "optional": true, + "description": "An ArrayBuffer with a copy of the data." + }, + "file": { + "type": "string", + "optional": true, + "description": "A string with the file's path and name." + } + }, + "description": "Contains data uploaded in a URL request." + } + ], + "functions": [ + { + "name": "handlerBehaviorChanged", + "type": "function", + "description": "Needs to be called when the behavior of the webRequest handlers has changed to prevent incorrect handling due to caching. This function call is expensive. Don't call it often.", + "async": "callback", + "parameters": [ + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [] + } + ] + } + ], + "events": [ + { + "name": "onBeforeRequest", + "type": "function", + "description": "Fired when a request is about to occur.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "requestBody": { + "type": "object", + "optional": true, + "description": "Contains the HTTP request body data. Only provided if extraInfoSpec contains 'requestBody'.", + "properties": { + "error": {"type": "string", "optional": true, "description": "Errors when obtaining request body data."}, + "formData": { + "type": "object", + "optional": true, + "description": "If the request method is POST and the body is a sequence of key-value pairs encoded in UTF8, encoded as either multipart/form-data, or application/x-www-form-urlencoded, this dictionary is present and for each key contains the list of all values for that key. If the data is of another media type, or if it is malformed, the dictionary is not present. An example value of this dictionary is {'key': ['value1', 'value2']}.", + "properties": {}, + "additionalProperties": { + "type": "array", + "items": { "type": "string" } + } + }, + "raw" : { + "type": "array", + "optional": true, + "items": {"$ref": "UploadData"}, + "description": "If the request method is PUT or POST, and the body is not already parsed in formData, then the unparsed request body elements are contained in this array." + } + } + }, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."} + } + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + }, + { + "type": "array", + "optional": true, + "name": "extraInfoSpec", + "description": "Array of extra information that should be passed to the listener function.", + "items": { + "$ref": "OnBeforeRequestOptions" + } + } + ], + "returns": { + "$ref": "BlockingResponse", + "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.", + "optional": true + } + }, + { + "name": "onBeforeSendHeaders", + "type": "function", + "description": "Fired before sending an HTTP request, once the request headers are available. This may occur after a TCP connection is made to the server, but before any HTTP data is sent. ", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, + "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that are going to be sent out with this request."} + } + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + }, + { + "type": "array", + "optional": true, + "name": "extraInfoSpec", + "description": "Array of extra information that should be passed to the listener function.", + "items": { + "$ref": "OnBeforeSendHeadersOptions" + } + } + ], + "returns": { + "$ref": "BlockingResponse", + "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.", + "optional": true + } + }, + { + "name": "onSendHeaders", + "type": "function", + "description": "Fired just before a request is going to be sent to the server (modifications of previous onBeforeSendHeaders callbacks are visible by the time onSendHeaders is fired).", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, + "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that have been sent out with this request."} + } + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + }, + { + "type": "array", + "optional": true, + "name": "extraInfoSpec", + "description": "Array of extra information that should be passed to the listener function.", + "items": { + "$ref": "OnSendHeadersOptions" + } + } + ] + }, + { + "name": "onHeadersReceived", + "type": "function", + "description": "Fired when HTTP response headers of a request have been received.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, + "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line)."}, + "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that have been received with this response."}, + "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."} + } + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + }, + { + "type": "array", + "optional": true, + "name": "extraInfoSpec", + "description": "Array of extra information that should be passed to the listener function.", + "items": { + "$ref": "OnHeadersReceivedOptions" + } + } + ], + "returns": { + "$ref": "BlockingResponse", + "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.", + "optional": true + } + }, + { + "name": "onAuthRequired", + "unsupported": true, + "type": "function", + "description": "Fired when an authentication failure is received. The listener has three options: it can provide authentication credentials, it can cancel the request and display the error page, or it can take no action on the challenge. If bad user credentials are provided, this may be called multiple times for the same request.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, + "scheme": {"type": "string", "description": "The authentication scheme, e.g. Basic or Digest."}, + "realm": {"type": "string", "description": "The authentication realm provided by the server, if there is one.", "optional": true}, + "challenger": {"type": "object", "description": "The server requesting authentication.", "properties": {"host": {"type": "string"}, "port": {"type": "integer"}}}, + "isProxy": {"type": "boolean", "description": "True for Proxy-Authenticate, false for WWW-Authenticate."}, + "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."}, + "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}, + "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."} + } + }, + { + "type": "function", + "optional": true, + "name": "callback", + "parameters": [ + {"name": "response", "$ref": "BlockingResponse"} + ] + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + }, + { + "type": "array", + "optional": true, + "name": "extraInfoSpec", + "description": "Array of extra information that should be passed to the listener function.", + "items": { + "$ref": "OnAuthRequiredOptions" + } + } + ], + "returns": { + "$ref": "BlockingResponse", + "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.", + "optional": true + } + }, + { + "name": "onResponseStarted", + "type": "function", + "description": "Fired when the first byte of the response body is received. For HTTP requests, this means that the status line and response headers are available.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, + "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."}, + "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."}, + "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}, + "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."}, + "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."} + } + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + }, + { + "type": "array", + "optional": true, + "name": "extraInfoSpec", + "description": "Array of extra information that should be passed to the listener function.", + "items": { + "$ref": "OnResponseStartedOptions" + } + } + ] + }, + { + "name": "onBeforeRedirect", + "type": "function", + "description": "Fired when a server-initiated redirect is about to occur.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, + "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."}, + "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."}, + "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}, + "redirectUrl": {"type": "string", "description": "The new URL."}, + "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this redirect."}, + "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."} + } + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + }, + { + "type": "array", + "optional": true, + "name": "extraInfoSpec", + "description": "Array of extra information that should be passed to the listener function.", + "items": { + "$ref": "OnBeforeRedirectOptions" + } + } + ] + }, + { + "name": "onCompleted", + "type": "function", + "description": "Fired when a request is completed.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, + "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."}, + "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."}, + "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}, + "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."}, + "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."} + } + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + }, + { + "type": "array", + "optional": true, + "name": "extraInfoSpec", + "description": "Array of extra information that should be passed to the listener function.", + "items": { + "$ref": "OnCompletedOptions" + } + } + ] + }, + { + "name": "onErrorOccurred", + "type": "function", + "description": "Fired when an error occurs.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."}, + "url": {"type": "string"}, + "method": {"type": "string", "description": "Standard HTTP method."}, + "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, + "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, + "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, + "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."}, + "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."}, + "error": {"type": "string", "description": "The error description. This string is not guaranteed to remain backwards compatible between releases. You must not parse and act based upon its content."} + } + } + ], + "extraParameters": [ + { + "$ref": "RequestFilter", + "name": "filter", + "description": "A set of filters that restricts the events that will be sent to this listener." + } + ] + } + ] + } +] diff --git a/toolkit/components/webextensions/test/mochitest/.eslintrc.js b/toolkit/components/webextensions/test/mochitest/.eslintrc.js new file mode 100644 index 000000000..53938410b --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/.eslintrc.js @@ -0,0 +1,35 @@ +"use strict"; + +module.exports = { // eslint-disable-line no-undef + "extends": "../../../../../testing/mochitest/mochitest.eslintrc.js", + + "env": { + "webextensions": true, + }, + + "globals": { + "ChromeWorker": false, + "onmessage": true, + "sendAsyncMessage": false, + + "waitForLoad": true, + "promiseConsoleOutput": true, + + "ExtensionTestUtils": false, + "NetUtil": true, + "webrequest_test": false, + "XPCOMUtils": true, + + // head_webrequest.js symbols + "addStylesheet": true, + "addLink": true, + "addImage": true, + "addScript": true, + "addFrame": true, + "makeExtension": false, + }, + + "rules": { + "no-shadow": 0, + }, +}; diff --git a/toolkit/components/webextensions/test/mochitest/chrome.ini b/toolkit/components/webextensions/test/mochitest/chrome.ini new file mode 100644 index 000000000..26585cad7 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/chrome.ini @@ -0,0 +1,35 @@ +[DEFAULT] +support-files = + chrome_head.js + head.js + head_cookies.js + file_sample.html + webrequest_chromeworker.js + webrequest_test.jsm +tags = webextensions + +[test_chrome_ext_background_debug_global.html] +skip-if = (os == 'android') # android doesn't have devtools +[test_chrome_ext_background_page.html] +skip-if = (toolkit == 'android') # android doesn't have devtools +[test_chrome_ext_eventpage_warning.html] +[test_chrome_ext_contentscript_unrecognizedprop_warning.html] +skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android. +[test_chrome_ext_hybrid_addons.html] +[test_chrome_ext_trustworthy_origin.html] +[test_chrome_ext_webnavigation_resolved_urls.html] +skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android. +[test_chrome_ext_shutdown_cleanup.html] +[test_chrome_native_messaging_paths.html] +skip-if = os != "mac" && os != "linux" +[test_ext_cookies_expiry.html] +[test_ext_cookies_permissions_bad.html] +[test_ext_cookies_permissions_good.html] +[test_ext_cookies_containers.html] +[test_ext_jsversion.html] +[test_ext_schema.html] +[test_chrome_ext_storage_cleanup.html] +[test_chrome_ext_idle.html] +[test_chrome_ext_downloads_saveAs.html] +[test_chrome_ext_webrequest_background_events.html] +skip-if = os == 'android' # webrequest api unsupported (bug 1258975). diff --git a/toolkit/components/webextensions/test/mochitest/chrome_head.js b/toolkit/components/webextensions/test/mochitest/chrome_head.js new file mode 100644 index 000000000..da2f53a02 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/chrome_head.js @@ -0,0 +1,12 @@ +"use strict"; + +const { + classes: Cc, + interfaces: Ci, + utils: Cu, + results: Cr, +} = Components; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + diff --git a/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page1.html b/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page1.html new file mode 100644 index 000000000..663ebc611 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page1.html @@ -0,0 +1,12 @@ + + + + + + + +
+
+ + + diff --git a/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page2.html b/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page2.html new file mode 100644 index 000000000..cc1acc83d --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page2.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page3.html b/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page3.html new file mode 100644 index 000000000..a0a26a2e9 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_WebNavigation_page3.html @@ -0,0 +1,9 @@ + + + + + +click me + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_WebRequest_page3.html b/toolkit/components/webextensions/test/mochitest/file_WebRequest_page3.html new file mode 100644 index 000000000..5807dd439 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_WebRequest_page3.html @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_csp.html b/toolkit/components/webextensions/test/mochitest/file_csp.html new file mode 100644 index 000000000..206e44390 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_csp.html @@ -0,0 +1,14 @@ + + + + + + + + +
Sample text
+ + + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_csp.html^headers^ b/toolkit/components/webextensions/test/mochitest/file_csp.html^headers^ new file mode 100644 index 000000000..4c6fa3c26 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_csp.html^headers^ @@ -0,0 +1 @@ +Content-Security-Policy: default-src 'self' diff --git a/toolkit/components/webextensions/test/mochitest/file_ext_test_api_injection.js b/toolkit/components/webextensions/test/mochitest/file_ext_test_api_injection.js new file mode 100644 index 000000000..06dfae65e --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_ext_test_api_injection.js @@ -0,0 +1,12 @@ +"use strict"; + +var {interfaces: Ci} = Components; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +Services.console.registerListener(function listener(message) { + if (/WebExt Privilege Escalation/.test(message.message)) { + Services.console.unregisterListener(listener); + sendAsyncMessage("console-message", {message: message.message}); + } +}); diff --git a/toolkit/components/webextensions/test/mochitest/file_image_bad.png b/toolkit/components/webextensions/test/mochitest/file_image_bad.png new file mode 100644 index 000000000..4c3be5084 Binary files /dev/null and b/toolkit/components/webextensions/test/mochitest/file_image_bad.png differ diff --git a/toolkit/components/webextensions/test/mochitest/file_image_good.png b/toolkit/components/webextensions/test/mochitest/file_image_good.png new file mode 100644 index 000000000..769c63634 Binary files /dev/null and b/toolkit/components/webextensions/test/mochitest/file_image_good.png differ diff --git a/toolkit/components/webextensions/test/mochitest/file_image_redirect.png b/toolkit/components/webextensions/test/mochitest/file_image_redirect.png new file mode 100644 index 000000000..4c3be5084 Binary files /dev/null and b/toolkit/components/webextensions/test/mochitest/file_image_redirect.png differ diff --git a/toolkit/components/webextensions/test/mochitest/file_mixed.html b/toolkit/components/webextensions/test/mochitest/file_mixed.html new file mode 100644 index 000000000..f3c7dda58 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_mixed.html @@ -0,0 +1,13 @@ + + + + + + + + +
Sample text
+ + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_permission_xhr.html b/toolkit/components/webextensions/test/mochitest/file_permission_xhr.html new file mode 100644 index 000000000..22a55f90d --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_permission_xhr.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_privilege_escalation.html b/toolkit/components/webextensions/test/mochitest/file_privilege_escalation.html new file mode 100644 index 000000000..258f7058d --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_privilege_escalation.html @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_sample.html b/toolkit/components/webextensions/test/mochitest/file_sample.html new file mode 100644 index 000000000..a20e49a1f --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_sample.html @@ -0,0 +1,12 @@ + + + + + + + + +
Sample text
+ + + diff --git a/toolkit/components/webextensions/test/mochitest/file_script_bad.js b/toolkit/components/webextensions/test/mochitest/file_script_bad.js new file mode 100644 index 000000000..c425122c7 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_script_bad.js @@ -0,0 +1,3 @@ +"use strict"; + +window.failure = true; diff --git a/toolkit/components/webextensions/test/mochitest/file_script_good.js b/toolkit/components/webextensions/test/mochitest/file_script_good.js new file mode 100644 index 000000000..1848edf68 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_script_good.js @@ -0,0 +1,3 @@ +"use strict"; + +window.success = window.success ? window.success + 1 : 1; diff --git a/toolkit/components/webextensions/test/mochitest/file_script_redirect.js b/toolkit/components/webextensions/test/mochitest/file_script_redirect.js new file mode 100644 index 000000000..c89a196c2 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_script_redirect.js @@ -0,0 +1,4 @@ +"use strict"; + +window.failure = true; + diff --git a/toolkit/components/webextensions/test/mochitest/file_script_xhr.js b/toolkit/components/webextensions/test/mochitest/file_script_xhr.js new file mode 100644 index 000000000..07f80eb2e --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_script_xhr.js @@ -0,0 +1,5 @@ +"use strict"; + +var request = new XMLHttpRequest(); +request.open("get", "http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest/xhr_resource", false); +request.send(); diff --git a/toolkit/components/webextensions/test/mochitest/file_style_bad.css b/toolkit/components/webextensions/test/mochitest/file_style_bad.css new file mode 100644 index 000000000..8dbc8dc7a --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_style_bad.css @@ -0,0 +1,3 @@ +#test { + color: green !important; +} diff --git a/toolkit/components/webextensions/test/mochitest/file_style_good.css b/toolkit/components/webextensions/test/mochitest/file_style_good.css new file mode 100644 index 000000000..46f9774b5 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_style_good.css @@ -0,0 +1,3 @@ +#test { + color: red; +} diff --git a/toolkit/components/webextensions/test/mochitest/file_style_redirect.css b/toolkit/components/webextensions/test/mochitest/file_style_redirect.css new file mode 100644 index 000000000..8dbc8dc7a --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_style_redirect.css @@ -0,0 +1,3 @@ +#test { + color: green !important; +} diff --git a/toolkit/components/webextensions/test/mochitest/file_teardown_test.js b/toolkit/components/webextensions/test/mochitest/file_teardown_test.js new file mode 100644 index 000000000..7246012ad --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_teardown_test.js @@ -0,0 +1,24 @@ +"use strict"; + +/* globals addMessageListener */ +let {Management} = Components.utils.import("resource://gre/modules/Extension.jsm", {}); +let events = []; +function record(type, extensionContext) { + let eventType = type == "proxy-context-load" ? "load" : "unload"; + let url = extensionContext.uri.spec; + let extensionId = extensionContext.extension.id; + events.push({eventType, url, extensionId}); +} + +Management.on("proxy-context-load", record); +Management.on("proxy-context-unload", record); +addMessageListener("cleanup", () => { + Management.off("proxy-context-load", record); + Management.off("proxy-context-unload", record); +}); + +addMessageListener("get-context-events", extensionId => { + sendAsyncMessage("context-events", events); + events = []; +}); +sendAsyncMessage("chromescript-startup"); diff --git a/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect.html b/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect.html new file mode 100644 index 000000000..cba3043f7 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect_httpHeaders.html b/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect_httpHeaders.html new file mode 100644 index 000000000..c5b436979 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect_httpHeaders.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect_httpHeaders.html^headers^ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect_httpHeaders.html^headers^ new file mode 100644 index 000000000..574a392a1 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_clientRedirect_httpHeaders.html^headers^ @@ -0,0 +1 @@ +Refresh: 1;url=dummy_page.html diff --git a/toolkit/components/webextensions/test/mochitest/file_webNavigation_frameClientRedirect.html b/toolkit/components/webextensions/test/mochitest/file_webNavigation_frameClientRedirect.html new file mode 100644 index 000000000..d360bcbb1 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_frameClientRedirect.html @@ -0,0 +1,12 @@ + + + + + + + +
+
+ + + diff --git a/toolkit/components/webextensions/test/mochitest/file_webNavigation_frameRedirect.html b/toolkit/components/webextensions/test/mochitest/file_webNavigation_frameRedirect.html new file mode 100644 index 000000000..06dbd4374 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_frameRedirect.html @@ -0,0 +1,12 @@ + + + + + + + +
+
+ + + diff --git a/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe.html b/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe.html new file mode 100644 index 000000000..307990714 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe.html @@ -0,0 +1,12 @@ + + + + + + + +
+
+ + + diff --git a/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe_page1.html b/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe_page1.html new file mode 100644 index 000000000..55bb7aa6a --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe_page1.html @@ -0,0 +1,8 @@ + + + + +

page1

+ page2 + + diff --git a/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe_page2.html b/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe_page2.html new file mode 100644 index 000000000..8f589f8bb --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_webNavigation_manualSubframe_page2.html @@ -0,0 +1,7 @@ + + + + +

page2

+ + diff --git a/toolkit/components/webextensions/test/mochitest/file_with_about_blank.html b/toolkit/components/webextensions/test/mochitest/file_with_about_blank.html new file mode 100644 index 000000000..af51c2e52 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/file_with_about_blank.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/head.js b/toolkit/components/webextensions/test/mochitest/head.js new file mode 100644 index 000000000..1b1a29472 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/head.js @@ -0,0 +1,13 @@ +"use strict"; + +/* exported waitForLoad */ + +function waitForLoad(win) { + return new Promise(resolve => { + win.addEventListener("load", function listener() { + win.removeEventListener("load", listener, true); + resolve(); + }, true); + }); +} + diff --git a/toolkit/components/webextensions/test/mochitest/head_cookies.js b/toolkit/components/webextensions/test/mochitest/head_cookies.js new file mode 100644 index 000000000..9f6966551 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/head_cookies.js @@ -0,0 +1,167 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* exported testCookies */ + +function* testCookies(options) { + // Changing the options object is a bit of a hack, but it allows us to easily + // pass an expiration date to the background script. + options.expiry = Date.now() / 1000 + 3600; + + async function background(backgroundOptions) { + // Ask the parent scope to change some cookies we may or may not have + // permission for. + let awaitChanges = new Promise(resolve => { + browser.test.onMessage.addListener(msg => { + browser.test.assertEq("cookies-changed", msg, "browser.test.onMessage"); + resolve(); + }); + }); + + let changed = []; + browser.cookies.onChanged.addListener(event => { + changed.push(`${event.cookie.name}:${event.cause}`); + }); + browser.test.sendMessage("change-cookies"); + + + // Try to access some cookies in various ways. + let {url, domain, secure} = backgroundOptions; + + let failures = 0; + let tallyFailure = error => { + failures++; + }; + + try { + await awaitChanges; + + let cookie = await browser.cookies.get({url, name: "foo"}); + browser.test.assertEq(backgroundOptions.shouldPass, cookie != null, "should pass == get cookie"); + + let cookies = await browser.cookies.getAll({domain}); + if (backgroundOptions.shouldPass) { + browser.test.assertEq(2, cookies.length, "expected number of cookies"); + } else { + browser.test.assertEq(0, cookies.length, "expected number of cookies"); + } + + await Promise.all([ + browser.cookies.set({url, domain, secure, name: "foo", "value": "baz", expirationDate: backgroundOptions.expiry}).catch(tallyFailure), + browser.cookies.set({url, domain, secure, name: "bar", "value": "quux", expirationDate: backgroundOptions.expiry}).catch(tallyFailure), + browser.cookies.remove({url, name: "deleted"}), + ]); + + if (backgroundOptions.shouldPass) { + // The order of eviction events isn't guaranteed, so just check that + // it's there somewhere. + let evicted = changed.indexOf("evicted:evicted"); + if (evicted < 0) { + browser.test.fail("got no eviction event"); + } else { + browser.test.succeed("got eviction event"); + changed.splice(evicted, 1); + } + + browser.test.assertEq("x:explicit,x:overwrite,x:explicit,x:explicit,foo:overwrite,foo:explicit,bar:explicit,deleted:explicit", + changed.join(","), "expected changes"); + } else { + browser.test.assertEq("", changed.join(","), "expected no changes"); + } + + if (!(backgroundOptions.shouldPass || backgroundOptions.shouldWrite)) { + browser.test.assertEq(2, failures, "Expected failures"); + } else { + browser.test.assertEq(0, failures, "Expected no failures"); + } + + browser.test.notifyPass("cookie-permissions"); + } catch (error) { + browser.test.fail(`Error: ${error} :: ${error.stack}`); + browser.test.notifyFail("cookie-permissions"); + } + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + "permissions": options.permissions, + }, + + background: `(${background})(${JSON.stringify(options)})`, + }); + + + let cookieSvc = SpecialPowers.Services.cookies; + + let domain = options.domain.replace(/^\.?/, "."); + + // This will be evicted after we add a fourth cookie. + cookieSvc.add(domain, "/", "evicted", "bar", options.secure, false, false, options.expiry); + // This will be modified by the background script. + cookieSvc.add(domain, "/", "foo", "bar", options.secure, false, false, options.expiry); + // This will be deleted by the background script. + cookieSvc.add(domain, "/", "deleted", "bar", options.secure, false, false, options.expiry); + + + yield extension.startup(); + + yield extension.awaitMessage("change-cookies"); + cookieSvc.add(domain, "/", "x", "y", options.secure, false, false, options.expiry); + cookieSvc.add(domain, "/", "x", "z", options.secure, false, false, options.expiry); + cookieSvc.remove(domain, "x", "/", false, {}); + extension.sendMessage("cookies-changed"); + + yield extension.awaitFinish("cookie-permissions"); + yield extension.unload(); + + + function getCookies(host) { + let cookies = []; + let enum_ = cookieSvc.getCookiesFromHost(host, {}); + while (enum_.hasMoreElements()) { + cookies.push(enum_.getNext().QueryInterface(SpecialPowers.Ci.nsICookie2)); + } + return cookies.sort((a, b) => String.localeCompare(a.name, b.name)); + } + + let cookies = getCookies(options.domain); + info(`Cookies: ${cookies.map(c => `${c.name}=${c.value}`)}`); + + if (options.shouldPass) { + is(cookies.length, 2, "expected two cookies for host"); + + is(cookies[0].name, "bar", "correct cookie name"); + is(cookies[0].value, "quux", "correct cookie value"); + + is(cookies[1].name, "foo", "correct cookie name"); + is(cookies[1].value, "baz", "correct cookie value"); + } else if (options.shouldWrite) { + // Note: |shouldWrite| applies only when |shouldPass| is false. + // This is necessary because, unfortunately, websites (and therefore web + // extensions) are allowed to write some cookies which they're not allowed + // to read. + is(cookies.length, 3, "expected three cookies for host"); + + is(cookies[0].name, "bar", "correct cookie name"); + is(cookies[0].value, "quux", "correct cookie value"); + + is(cookies[1].name, "deleted", "correct cookie name"); + + is(cookies[2].name, "foo", "correct cookie name"); + is(cookies[2].value, "baz", "correct cookie value"); + } else { + is(cookies.length, 2, "expected two cookies for host"); + + is(cookies[0].name, "deleted", "correct second cookie name"); + + is(cookies[1].name, "foo", "correct cookie name"); + is(cookies[1].value, "bar", "correct cookie value"); + } + + for (let cookie of cookies) { + cookieSvc.remove(cookie.host, cookie.name, "/", false, {}); + } + // Make sure we don't silently poison subsequent tests if something goes wrong. + is(getCookies(options.domain).length, 0, "cookies cleared"); +} diff --git a/toolkit/components/webextensions/test/mochitest/head_webrequest.js b/toolkit/components/webextensions/test/mochitest/head_webrequest.js new file mode 100644 index 000000000..96924e505 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/head_webrequest.js @@ -0,0 +1,331 @@ +"use strict"; + +let commonEvents = { + "onBeforeRequest": [{urls: [""]}, ["blocking"]], + "onBeforeSendHeaders": [{urls: [""]}, ["blocking", "requestHeaders"]], + "onSendHeaders": [{urls: [""]}, ["requestHeaders"]], + "onBeforeRedirect": [{urls: [""]}], + "onHeadersReceived": [{urls: [""]}, ["blocking", "responseHeaders"]], + "onResponseStarted": [{urls: [""]}], + "onCompleted": [{urls: [""]}, ["responseHeaders"]], + "onErrorOccurred": [{urls: [""]}], +}; + +function background(events) { + let expect; + let ignore; + let defaultOrigin; + + browser.test.onMessage.addListener((msg, expected) => { + if (msg !== "set-expected") { + return; + } + expect = expected.expect; + defaultOrigin = expected.origin; + ignore = expected.ignore; + let promises = []; + // Initialize some stuff we'll need in the tests. + for (let entry of Object.values(expect)) { + // a place for the test infrastructure to store some state. + entry.test = {}; + // Each entry in expected gets a Promise that will be resolved in the + // last event for that entry. This will either be onCompleted, or the + // last entry if an events list was provided. + promises.push(new Promise(resolve => { entry.test.resolve = resolve; })); + // If events was left undefined, we're expecting all normal events we're + // listening for, exclude onBeforeRedirect and onErrorOccurred + if (entry.events === undefined) { + entry.events = Object.keys(events).filter(name => name != "onErrorOccurred" && name != "onBeforeRedirect"); + } + if (entry.optional_events === undefined) { + entry.optional_events = []; + } + } + // When every expected entry has finished our test is done. + Promise.all(promises).then(() => { + browser.test.sendMessage("done"); + }); + browser.test.sendMessage("continue"); + }); + + // Retrieve the per-file/test expected values. + function getExpected(details) { + let url = new URL(details.url); + let filename; + if (url.protocol == "data:") { + // pathname is everything after protocol. + filename = url.pathname; + } else { + filename = url.pathname.split("/").pop(); + } + if (ignore && ignore.includes(filename)) { + return; + } + let expected = expect[filename]; + if (!expected) { + browser.test.fail(`unexpected request ${filename}`); + return; + } + // Save filename for redirect verification. + expected.test.filename = filename; + return expected; + } + + // Process any test header modifications that can happen in request or response phases. + // If a test includes headers, it needs a complete header object, no undefined + // objects even if empty: + // request: { + // add: {"HeaderName": "value",}, + // modify: {"HeaderName": "value",}, + // remove: ["HeaderName",], + // }, + // response: { + // add: {"HeaderName": "value",}, + // modify: {"HeaderName": "value",}, + // remove: ["HeaderName",], + // }, + function processHeaders(phase, expected, details) { + // This should only happen once per phase [request|response]. + browser.test.assertFalse(!!expected.test[phase], `First processing of headers for ${phase}`); + expected.test[phase] = true; + + let headers = details[`${phase}Headers`]; + browser.test.assertTrue(Array.isArray(headers), `${phase}Headers array present`); + + let {add, modify, remove} = expected.headers[phase]; + + for (let name in add) { + browser.test.assertTrue(!headers.find(h => h.name === name), `header ${name} to be added not present yet in ${phase}Headers`); + let header = {name: name}; + if (name.endsWith("-binary")) { + header.binaryValue = Array.from(add[name], c => c.charCodeAt(0)); + } else { + header.value = add[name]; + } + headers.push(header); + } + + let modifiedAny = false; + for (let header of headers) { + if (header.name.toLowerCase() in modify) { + header.value = modify[header.name.toLowerCase()]; + modifiedAny = true; + } + } + browser.test.assertTrue(modifiedAny, `at least one ${phase}Headers element to modify`); + + let deletedAny = false; + for (let j = headers.length; j-- > 0;) { + if (remove.includes(headers[j].name.toLowerCase())) { + headers.splice(j, 1); + deletedAny = true; + } + } + browser.test.assertTrue(deletedAny, `at least one ${phase}Headers element to delete`); + + return headers; + } + + // phase is request or response. + function checkHeaders(phase, expected, details) { + if (!/^https?:/.test(details.url)) { + return; + } + + let headers = details[`${phase}Headers`]; + browser.test.assertTrue(Array.isArray(headers), `valid ${phase}Headers array`); + + let {add, modify, remove} = expected.headers[phase]; + for (let name in add) { + let value = headers.find(h => h.name.toLowerCase() === name.toLowerCase()).value; + browser.test.assertEq(value, add[name], `header ${name} correctly injected in ${phase}Headers`); + } + + for (let name in modify) { + let value = headers.find(h => h.name.toLowerCase() === name.toLowerCase()).value; + browser.test.assertEq(value, modify[name], `header ${name} matches modified value`); + } + + for (let name of remove) { + let found = headers.find(h => h.name.toLowerCase() === name.toLowerCase()); + browser.test.assertFalse(!!found, `deleted header ${name} still found in ${phase}Headers`); + } + } + + function getListener(name) { + return details => { + let result = {}; + browser.test.log(`${name} ${details.requestId} ${details.url}`); + let expected = getExpected(details); + if (!expected) { + return result; + } + let expectedEvent = expected.events[0] == name; + if (expectedEvent) { + expected.events.shift(); + } else { + expectedEvent = expected.optional_events[0] == name; + if (expectedEvent) { + expected.optional_events.shift(); + } + } + browser.test.assertTrue(expectedEvent, `received ${name}`); + browser.test.assertEq(expected.type, details.type, "resource type is correct"); + browser.test.assertEq(expected.origin || defaultOrigin, details.originUrl, "origin is correct"); + + if (name == "onBeforeRequest") { + // Save some values to test request consistency in later events. + browser.test.assertTrue(details.tabId !== undefined, `tabId ${details.tabId}`); + browser.test.assertTrue(details.requestId !== undefined, `requestId ${details.requestId}`); + // Validate requestId if it's already set, this happens with redirects. + if (expected.test.requestId !== undefined) { + browser.test.assertEq("string", typeof expected.test.requestId, `requestid ${expected.test.requestId} is string`); + browser.test.assertEq("string", typeof details.requestId, `requestid ${details.requestId} is string`); + browser.test.assertEq("number", typeof parseInt(details.requestId, 10), "parsed requestid is number"); + browser.test.assertNotEq(expected.test.requestId, details.requestId, + `last requestId ${expected.test.requestId} different from this one ${details.requestId}`); + } else { + // Save any values we want to validate in later events. + expected.test.requestId = details.requestId; + expected.test.tabId = details.tabId; + } + // Tests we don't need to do every event. + browser.test.assertTrue(details.type.toUpperCase() in browser.webRequest.ResourceType, `valid resource type ${details.type}`); + if (details.type == "main_frame") { + browser.test.assertEq(0, details.frameId, "frameId is zero when type is main_frame bug 1329299"); + } + } else { + // On events after onBeforeRequest, check the previous values. + browser.test.assertEq(expected.test.requestId, details.requestId, "correct requestId"); + browser.test.assertEq(expected.test.tabId, details.tabId, "correct tabId"); + } + if (name == "onBeforeSendHeaders") { + if (expected.headers && expected.headers.request) { + result.requestHeaders = processHeaders("request", expected, details); + } + if (expected.redirect) { + browser.test.log(`${name} redirect request`); + result.redirectUrl = details.url.replace(expected.test.filename, expected.redirect); + } + } + if (name == "onSendHeaders") { + if (expected.headers && expected.headers.request) { + checkHeaders("request", expected, details); + } + } + if (name == "onHeadersReceived") { + browser.test.assertEq(expected.status || 200, details.statusCode, + `expected HTTP status received for ${details.url}`); + if (expected.headers && expected.headers.response) { + result.responseHeaders = processHeaders("response", expected, details); + } + } + if (name == "onCompleted") { + // If we have already completed a GET request for this url, + // and it was found, we expect for the response to come fromCache. + // expected.cached may be undefined, force boolean. + let expectCached = !!expected.cached && details.method === "GET" && details.statusCode != 404; + browser.test.assertEq(expectCached, details.fromCache, "fromCache is correct"); + // We can only tell IPs for non-cached HTTP requests. + if (!details.fromCache && /^https?:/.test(details.url)) { + browser.test.assertEq("127.0.0.1", details.ip, `correct ip for ${details.url}`); + } + if (expected.headers && expected.headers.response) { + checkHeaders("response", expected, details); + } + } + + if (expected.cancel && expected.cancel == name) { + browser.test.log(`${name} cancel request`); + browser.test.sendMessage("cancelled"); + result.cancel = true; + } + // If we've used up all the events for this test, resolve the promise. + // If something wrong happens and more events come through, there will be + // failures. + if (expected.events.length <= 0) { + expected.test.resolve(); + } + return result; + }; + } + + for (let [name, args] of Object.entries(events)) { + browser.test.log(`adding listener for ${name}`); + try { + browser.webRequest[name].addListener(getListener(name), ...args); + } catch (e) { + browser.test.assertTrue(/\brequestBody\b/.test(e.message), + "Request body is unsupported"); + + // RequestBody is disabled in release builds. + if (!/\brequestBody\b/.test(e.message)) { + throw e; + } + + args.splice(args.indexOf("requestBody"), 1); + browser.webRequest[name].addListener(getListener(name), ...args); + } + } +} + +/* exported makeExtension */ + +function makeExtension(events = commonEvents) { + return ExtensionTestUtils.loadExtension({ + manifest: { + permissions: [ + "webRequest", + "webRequestBlocking", + "", + ], + }, + background: `(${background})(${JSON.stringify(events)})`, + }); +} + +/* exported addStylesheet */ + +function addStylesheet(file) { + let link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("href", file); + document.body.appendChild(link); +} + +/* exported addLink */ + +function addLink(file) { + let a = document.createElement("a"); + a.setAttribute("href", file); + a.setAttribute("target", "_blank"); + document.body.appendChild(a); + return a; +} + +/* exported addImage */ + +function addImage(file) { + let img = document.createElement("img"); + img.setAttribute("src", file); + document.body.appendChild(img); +} + +/* exported addScript */ + +function addScript(file) { + let script = document.createElement("script"); + script.setAttribute("type", "text/javascript"); + script.setAttribute("src", file); + document.getElementsByTagName("head").item(0).appendChild(script); +} + +/* exported addFrame */ + +function addFrame(file) { + let frame = document.createElement("iframe"); + frame.setAttribute("width", "200"); + frame.setAttribute("height", "200"); + frame.setAttribute("src", file); + document.body.appendChild(frame); +} diff --git a/toolkit/components/webextensions/test/mochitest/mochitest.ini b/toolkit/components/webextensions/test/mochitest/mochitest.ini new file mode 100644 index 000000000..45586237e --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/mochitest.ini @@ -0,0 +1,114 @@ +[DEFAULT] +support-files = + head.js + file_mixed.html + head_webrequest.js + file_csp.html + file_csp.html^headers^ + file_WebRequest_page3.html + file_webNavigation_clientRedirect.html + file_webNavigation_clientRedirect_httpHeaders.html + file_webNavigation_clientRedirect_httpHeaders.html^headers^ + file_webNavigation_frameClientRedirect.html + file_webNavigation_frameRedirect.html + file_webNavigation_manualSubframe.html + file_webNavigation_manualSubframe_page1.html + file_webNavigation_manualSubframe_page2.html + file_WebNavigation_page1.html + file_WebNavigation_page2.html + file_WebNavigation_page3.html + file_with_about_blank.html + file_image_good.png + file_image_bad.png + file_image_redirect.png + file_style_good.css + file_style_bad.css + file_style_redirect.css + file_script_good.js + file_script_bad.js + file_script_redirect.js + file_script_xhr.js + file_sample.html + redirection.sjs + file_privilege_escalation.html + file_ext_test_api_injection.js + file_permission_xhr.html + file_teardown_test.js + return_headers.sjs + webrequest_worker.js +tags = webextensions + +[test_clipboard.html] +# skip-if = # disabled test case with_permission_allow_copy, see inline comment. +[test_ext_inIncognitoContext_window.html] +skip-if = os == 'android' # Android does not currently support windows. +[test_ext_geturl.html] +[test_ext_background_canvas.html] +[test_ext_content_security_policy.html] +[test_ext_contentscript.html] +[test_ext_contentscript_api_injection.html] +[test_ext_contentscript_async_loading.html] +[test_ext_contentscript_context.html] +[test_ext_contentscript_create_iframe.html] +[test_ext_contentscript_devtools_metadata.html] +[test_ext_contentscript_exporthelpers.html] +[test_ext_contentscript_css.html] +[test_ext_contentscript_about_blank.html] +[test_ext_contentscript_permission.html] +skip-if = os == 'android' # Android does not support tabs API. Bug 1260250 +[test_ext_contentscript_teardown.html] +skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250 +[test_ext_exclude_include_globs.html] +[test_ext_i18n_css.html] +[test_ext_generate.html] +[test_ext_notifications.html] +[test_ext_permission_xhr.html] +[test_ext_runtime_connect.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_runtime_connect_twoway.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_runtime_connect2.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_runtime_disconnect.html] +[test_ext_runtime_id.html] +[test_ext_sandbox_var.html] +[test_ext_sendmessage_reply.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_sendmessage_reply2.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_sendmessage_doublereply.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_sendmessage_no_receiver.html] +[test_ext_storage_content.html] +[test_ext_storage_tab.html] +skip-if = os == 'android' # Android does not currently support tabs. +[test_ext_test.html] +[test_ext_cookies.html] +skip-if = os == 'android' # Bug 1258975 on android. +[test_ext_background_api_injection.html] +[test_ext_background_generated_url.html] +[test_ext_background_teardown.html] +[test_ext_tab_teardown.html] +skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250 +[test_ext_unload_frame.html] +[test_ext_i18n.html] +skip-if = (os == 'android') # Bug 1258975 on android. +[test_ext_listener_proxies.html] +[test_ext_web_accessible_resources.html] +skip-if = (os == 'android') # Bug 1258975 on android. +[test_ext_webrequest_background_events.html] +skip-if = os == 'android' # webrequest api unsupported (bug 1258975). +[test_ext_webrequest_basic.html] +skip-if = os == 'android' # webrequest api unsupported (bug 1258975). +[test_ext_webrequest_suspend.html] +skip-if = os == 'android' # webrequest api unsupported (bug 1258975). +[test_ext_webrequest_upload.html] +skip-if = release_or_beta || os == 'android' # webrequest api unsupported (bug 1258975). +[test_ext_webnavigation.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_webnavigation_filters.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_window_postMessage.html] +[test_ext_subframes_privileges.html] +skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975). +[test_ext_xhr_capabilities.html] diff --git a/toolkit/components/webextensions/test/mochitest/redirection.sjs b/toolkit/components/webextensions/test/mochitest/redirection.sjs new file mode 100644 index 000000000..370ecd213 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/redirection.sjs @@ -0,0 +1,4 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 302); + aResponse.setHeader("Location", "./dummy_page.html"); +} diff --git a/toolkit/components/webextensions/test/mochitest/return_headers.sjs b/toolkit/components/webextensions/test/mochitest/return_headers.sjs new file mode 100644 index 000000000..54e2e5fb4 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/return_headers.sjs @@ -0,0 +1,20 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript sts=2 sw=2 et tw=80: */ +"use strict"; + +/* exported handleRequest */ + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + + let headers = {}; + // Why on earth is this a nsISimpleEnumerator... + let enumerator = request.headers; + while (enumerator.hasMoreElements()) { + let header = enumerator.getNext().data; + headers[header.toLowerCase()] = request.getHeader(header); + } + + response.write(JSON.stringify(headers)); +} + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_background_debug_global.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_background_debug_global.html new file mode 100644 index 000000000..0edf5ea86 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_background_debug_global.html @@ -0,0 +1,166 @@ + + + + WebExtension test + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_background_page.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_background_page.html new file mode 100644 index 000000000..3c4774652 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_background_page.html @@ -0,0 +1,84 @@ + + + + WebExtension test + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html new file mode 100644 index 000000000..e08121a8f --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html @@ -0,0 +1,80 @@ + + + + Test for content script unrecognized property on manifest + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_downloads_saveAs.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_downloads_saveAs.html new file mode 100644 index 000000000..c1aaae035 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_downloads_saveAs.html @@ -0,0 +1,68 @@ + + + + Test downloads.download() saveAs option + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_eventpage_warning.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_eventpage_warning.html new file mode 100644 index 000000000..ecea8237e --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_eventpage_warning.html @@ -0,0 +1,106 @@ + + + + Test for WebExtension EventPage Warning + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_hybrid_addons.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_hybrid_addons.html new file mode 100644 index 000000000..a74c551f0 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_hybrid_addons.html @@ -0,0 +1,141 @@ + + + + Test for hybrid addons: SDK or bootstrap.js + embedded WebExtension + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_idle.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_idle.html new file mode 100644 index 000000000..3c3063e67 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_idle.html @@ -0,0 +1,64 @@ + + + + WebExtension test + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html new file mode 100644 index 000000000..e3098e6b1 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html @@ -0,0 +1,50 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_storage_cleanup.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_storage_cleanup.html new file mode 100644 index 000000000..010769500 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_storage_cleanup.html @@ -0,0 +1,164 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_trustworthy_origin.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_trustworthy_origin.html new file mode 100644 index 000000000..573c08806 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_trustworthy_origin.html @@ -0,0 +1,53 @@ + + + + WebExtension test + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html new file mode 100644 index 000000000..768eb31fd --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html @@ -0,0 +1,83 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_ext_webrequest_background_events.html b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_webrequest_background_events.html new file mode 100644 index 000000000..a13c4d475 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_ext_webrequest_background_events.html @@ -0,0 +1,96 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_chrome_native_messaging_paths.html b/toolkit/components/webextensions/test/mochitest/test_chrome_native_messaging_paths.html new file mode 100644 index 000000000..29a148063 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_chrome_native_messaging_paths.html @@ -0,0 +1,61 @@ + + + + WebExtension test + + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_clipboard.html b/toolkit/components/webextensions/test/mochitest/test_clipboard.html new file mode 100644 index 000000000..900ee5f10 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_clipboard.html @@ -0,0 +1,140 @@ + + + + clipboard permission test + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_all_apis.js b/toolkit/components/webextensions/test/mochitest/test_ext_all_apis.js new file mode 100644 index 000000000..0f617c37e --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_all_apis.js @@ -0,0 +1,158 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +// Tests whether not too many APIs are visible by default. +// This file is used by test_ext_all_apis.html in browser/ and mobile/android/, +// which may modify the following variables to add or remove expected APIs. +/* globals expectedContentApisTargetSpecific */ +/* globals expectedBackgroundApisTargetSpecific */ + +// Generates a list of expectations. +function generateExpectations(list) { + return list.reduce((allApis, path) => { + return allApis.concat(`browser.${path}`, `chrome.${path}`); + }, []).sort(); +} + +let expectedCommonApis = [ + "extension.getURL", + "extension.inIncognitoContext", + "extension.lastError", + "i18n.detectLanguage", + "i18n.getAcceptLanguages", + "i18n.getMessage", + "i18n.getUILanguage", + "runtime.OnInstalledReason", + "runtime.OnRestartRequiredReason", + "runtime.PlatformArch", + "runtime.PlatformOs", + "runtime.RequestUpdateCheckStatus", + "runtime.getManifest", + "runtime.connect", + "runtime.getURL", + "runtime.id", + "runtime.lastError", + "runtime.onConnect", + "runtime.onMessage", + "runtime.sendMessage", + // If you want to add a new powerful test API, please see bug 1287233. + "test.assertEq", + "test.assertFalse", + "test.assertRejects", + "test.assertThrows", + "test.assertTrue", + "test.fail", + "test.log", + "test.notifyFail", + "test.notifyPass", + "test.onMessage", + "test.sendMessage", + "test.succeed", +]; + +let expectedContentApis = [ + ...expectedCommonApis, + ...expectedContentApisTargetSpecific, +]; + +let expectedBackgroundApis = [ + ...expectedCommonApis, + ...expectedBackgroundApisTargetSpecific, + "extension.ViewType", + "extension.getBackgroundPage", + "extension.getViews", + "extension.isAllowedFileSchemeAccess", + "extension.isAllowedIncognitoAccess", + // Note: extensionTypes is not visible in Chrome. + "extensionTypes.ImageFormat", + "extensionTypes.RunAt", + "management.ExtensionDisabledReason", + "management.ExtensionInstallType", + "management.ExtensionType", + "management.getSelf", + "management.uninstallSelf", + "runtime.getBackgroundPage", + "runtime.getBrowserInfo", + "runtime.getPlatformInfo", + "runtime.onInstalled", + "runtime.onStartup", + "runtime.onUpdateAvailable", + "runtime.openOptionsPage", + "runtime.reload", + "runtime.setUninstallURL", +]; + +function sendAllApis() { + function isEvent(key, val) { + if (!/^on[A-Z]/.test(key)) { + return false; + } + let eventKeys = []; + for (let prop in val) { + eventKeys.push(prop); + } + eventKeys = eventKeys.sort().join(); + return eventKeys === "addListener,hasListener,removeListener"; + } + function mayRecurse(key, val) { + if (Object.keys(val).filter(k => !/^[A-Z\-0-9_]+$/.test(k)).length === 0) { + // Don't recurse on constants and empty objects. + return false; + } + return !isEvent(key, val); + } + + let results = []; + function diveDeeper(path, obj) { + for (let key in obj) { + let val = obj[key]; + if (typeof val == "object" && val !== null && mayRecurse(key, val)) { + diveDeeper(`${path}.${key}`, val); + } else if (val !== undefined) { + results.push(`${path}.${key}`); + } + } + } + diveDeeper("browser", browser); + diveDeeper("chrome", chrome); + browser.test.sendMessage("allApis", results.sort()); +} + +add_task(function* test_enumerate_content_script_apis() { + let extensionData = { + manifest: { + content_scripts: [{ + matches: ["http://mochi.test/*/file_sample.html"], + js: ["contentscript.js"], + run_at: "document_start", + }], + }, + files: { + "contentscript.js": sendAllApis, + }, + }; + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + + let win = window.open("file_sample.html"); + let actualApis = yield extension.awaitMessage("allApis"); + win.close(); + let expectedApis = generateExpectations(expectedContentApis); + isDeeply(actualApis, expectedApis, "content script APIs"); + + yield extension.unload(); +}); + +add_task(function* test_enumerate_background_script_apis() { + let extensionData = { + background: sendAllApis, + }; + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + let actualApis = yield extension.awaitMessage("allApis"); + let expectedApis = generateExpectations(expectedBackgroundApis); + isDeeply(actualApis, expectedApis, "background script APIs"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_background_api_injection.html b/toolkit/components/webextensions/test/mochitest/test_ext_background_api_injection.html new file mode 100644 index 000000000..f43a59f81 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_background_api_injection.html @@ -0,0 +1,46 @@ + + + + Test for privilege escalation into content pages + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_background_canvas.html b/toolkit/components/webextensions/test/mochitest/test_ext_background_canvas.html new file mode 100644 index 000000000..bff7190cb --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_background_canvas.html @@ -0,0 +1,47 @@ + + + + Test for background page canvas rendering + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_background_generated_url.html b/toolkit/components/webextensions/test/mochitest/test_ext_background_generated_url.html new file mode 100644 index 000000000..f4fcf3d34 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_background_generated_url.html @@ -0,0 +1,47 @@ + + + + Test _generated_background_page.html + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_background_teardown.html b/toolkit/components/webextensions/test/mochitest/test_ext_background_teardown.html new file mode 100644 index 000000000..bb6b2e970 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_background_teardown.html @@ -0,0 +1,76 @@ + + + + Test for background script teardown + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_content_security_policy.html b/toolkit/components/webextensions/test/mochitest/test_ext_content_security_policy.html new file mode 100644 index 000000000..a36f29563 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_content_security_policy.html @@ -0,0 +1,162 @@ + + + + WebExtension CSP test + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript.html new file mode 100644 index 000000000..39f1bfabd --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript.html @@ -0,0 +1,116 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_about_blank.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_about_blank.html new file mode 100644 index 000000000..3766678e7 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_about_blank.html @@ -0,0 +1,117 @@ + + + + Test content script match_about_blank option + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_api_injection.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_api_injection.html new file mode 100644 index 000000000..abf3d349f --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_api_injection.html @@ -0,0 +1,88 @@ + + + + Test for privilege escalation into iframe with content script APIs + + + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_async_loading.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_async_loading.html new file mode 100644 index 000000000..d78f7ce02 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_async_loading.html @@ -0,0 +1,54 @@ + + + + Test content script async loading + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_context.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_context.html new file mode 100644 index 000000000..97b1645dd --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_context.html @@ -0,0 +1,81 @@ + + + + Test for content script contexts + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_create_iframe.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_create_iframe.html new file mode 100644 index 000000000..8aac3e213 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_create_iframe.html @@ -0,0 +1,165 @@ + + + + Test for content script + + + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_css.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_css.html new file mode 100644 index 000000000..5630a1d68 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_css.html @@ -0,0 +1,48 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_devtools_metadata.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_devtools_metadata.html new file mode 100644 index 000000000..137a3cda4 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_devtools_metadata.html @@ -0,0 +1,81 @@ + + + + Test for Sandbox metadata on WebExtensions ContentScripts + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_exporthelpers.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_exporthelpers.html new file mode 100644 index 000000000..f3414901d --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_exporthelpers.html @@ -0,0 +1,95 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_incognito.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_incognito.html new file mode 100644 index 000000000..a2f38dce6 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_incognito.html @@ -0,0 +1,89 @@ + + + + Test for content script private browsing ID + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_permission.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_permission.html new file mode 100644 index 000000000..eaf815092 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_permission.html @@ -0,0 +1,59 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_teardown.html b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_teardown.html new file mode 100644 index 000000000..33a8c4ccc --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_contentscript_teardown.html @@ -0,0 +1,96 @@ + + + + Test for content script teardown + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_cookies.html b/toolkit/components/webextensions/test/mochitest/test_ext_cookies.html new file mode 100644 index 000000000..d414a4e46 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_cookies.html @@ -0,0 +1,234 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_cookies_containers.html b/toolkit/components/webextensions/test/mochitest/test_ext_cookies_containers.html new file mode 100644 index 000000000..bc4994eec --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_cookies_containers.html @@ -0,0 +1,93 @@ + + + + WebExtension test + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_cookies_expiry.html b/toolkit/components/webextensions/test/mochitest/test_ext_cookies_expiry.html new file mode 100644 index 000000000..3927d9e94 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_cookies_expiry.html @@ -0,0 +1,72 @@ + + + + WebExtension test + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_cookies_permissions_bad.html b/toolkit/components/webextensions/test/mochitest/test_ext_cookies_permissions_bad.html new file mode 100644 index 000000000..15a62855a --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_cookies_permissions_bad.html @@ -0,0 +1,112 @@ + + + + WebExtension test + + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_cookies_permissions_good.html b/toolkit/components/webextensions/test/mochitest/test_ext_cookies_permissions_good.html new file mode 100644 index 000000000..31e83188c --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_cookies_permissions_good.html @@ -0,0 +1,86 @@ + + + + WebExtension test + + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_exclude_include_globs.html b/toolkit/components/webextensions/test/mochitest/test_ext_exclude_include_globs.html new file mode 100644 index 000000000..640522b40 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_exclude_include_globs.html @@ -0,0 +1,92 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_generate.html b/toolkit/components/webextensions/test/mochitest/test_ext_generate.html new file mode 100644 index 000000000..cfafcbad9 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_generate.html @@ -0,0 +1,49 @@ + + + + Test for generating WebExtensions + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_geturl.html b/toolkit/components/webextensions/test/mochitest/test_ext_geturl.html new file mode 100644 index 000000000..6e39c2f5d --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_geturl.html @@ -0,0 +1,72 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_i18n.html b/toolkit/components/webextensions/test/mochitest/test_ext_i18n.html new file mode 100644 index 000000000..1f7330bbb --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_i18n.html @@ -0,0 +1,432 @@ + + + + + Test for WebExtension localization APIs + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_i18n_css.html b/toolkit/components/webextensions/test/mochitest/test_ext_i18n_css.html new file mode 100644 index 000000000..7c6a8eeaa --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_i18n_css.html @@ -0,0 +1,116 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_inIncognitoContext_window.html b/toolkit/components/webextensions/test/mochitest/test_ext_inIncognitoContext_window.html new file mode 100644 index 000000000..675cbb298 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_inIncognitoContext_window.html @@ -0,0 +1,49 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_jsversion.html b/toolkit/components/webextensions/test/mochitest/test_ext_jsversion.html new file mode 100644 index 000000000..da0c355e0 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_jsversion.html @@ -0,0 +1,86 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_listener_proxies.html b/toolkit/components/webextensions/test/mochitest/test_ext_listener_proxies.html new file mode 100644 index 000000000..ca8db873e --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_listener_proxies.html @@ -0,0 +1,63 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_notifications.html b/toolkit/components/webextensions/test/mochitest/test_ext_notifications.html new file mode 100644 index 000000000..d1b798cf9 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_notifications.html @@ -0,0 +1,224 @@ + + + + Test for notifications + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_permission_xhr.html b/toolkit/components/webextensions/test/mochitest/test_ext_permission_xhr.html new file mode 100644 index 000000000..07967d5d0 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_permission_xhr.html @@ -0,0 +1,119 @@ + + + + WebExtension Test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect.html b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect.html new file mode 100644 index 000000000..60351eaee --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect.html @@ -0,0 +1,83 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect2.html b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect2.html new file mode 100644 index 000000000..dce12b21b --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect2.html @@ -0,0 +1,103 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect_twoway.html b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect_twoway.html new file mode 100644 index 000000000..e84134eff --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_connect_twoway.html @@ -0,0 +1,127 @@ + + + + WebExtension test + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_runtime_disconnect.html b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_disconnect.html new file mode 100644 index 000000000..5764d0a3c --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_disconnect.html @@ -0,0 +1,78 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_runtime_id.html b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_id.html new file mode 100644 index 000000000..4cdefda41 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_runtime_id.html @@ -0,0 +1,61 @@ + + + + + Test for browser.runtime.id + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_sandbox_var.html b/toolkit/components/webextensions/test/mochitest/test_ext_sandbox_var.html new file mode 100644 index 000000000..426a71ac6 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_sandbox_var.html @@ -0,0 +1,60 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_schema.html b/toolkit/components/webextensions/test/mochitest/test_ext_schema.html new file mode 100644 index 000000000..8a0e11c56 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_schema.html @@ -0,0 +1,73 @@ + + + + Test for schema API creation + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_doublereply.html b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_doublereply.html new file mode 100644 index 000000000..a3ef37cad --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_doublereply.html @@ -0,0 +1,101 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_no_receiver.html b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_no_receiver.html new file mode 100644 index 000000000..96af6558e --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_no_receiver.html @@ -0,0 +1,83 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply.html b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply.html new file mode 100644 index 000000000..a4ac708b2 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply.html @@ -0,0 +1,79 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply2.html b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply2.html new file mode 100644 index 000000000..1ebc1b40f --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply2.html @@ -0,0 +1,93 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_storage_content.html b/toolkit/components/webextensions/test/mochitest/test_ext_storage_content.html new file mode 100644 index 000000000..09a33814a --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_storage_content.html @@ -0,0 +1,330 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_storage_tab.html b/toolkit/components/webextensions/test/mochitest/test_ext_storage_tab.html new file mode 100644 index 000000000..32d8e6af0 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_storage_tab.html @@ -0,0 +1,118 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_subframes_privileges.html b/toolkit/components/webextensions/test/mochitest/test_ext_subframes_privileges.html new file mode 100644 index 000000000..1f3a9a3c9 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_subframes_privileges.html @@ -0,0 +1,202 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_tab_teardown.html b/toolkit/components/webextensions/test/mochitest/test_ext_tab_teardown.html new file mode 100644 index 000000000..dc351e48a --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_tab_teardown.html @@ -0,0 +1,150 @@ + + + + Test for extension tab teardown + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_test.html b/toolkit/components/webextensions/test/mochitest/test_ext_test.html new file mode 100644 index 000000000..fef31e0e2 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_test.html @@ -0,0 +1,191 @@ + + + + Testing test + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_unload_frame.html b/toolkit/components/webextensions/test/mochitest/test_ext_unload_frame.html new file mode 100644 index 000000000..5572de281 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_unload_frame.html @@ -0,0 +1,170 @@ + + + + WebExtensions test + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_web_accessible_resources.html b/toolkit/components/webextensions/test/mochitest/test_ext_web_accessible_resources.html new file mode 100644 index 000000000..fa3228739 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_web_accessible_resources.html @@ -0,0 +1,353 @@ + + + + Test the web_accessible_resources manifest directive + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_webnavigation.html b/toolkit/components/webextensions/test/mochitest/test_ext_webnavigation.html new file mode 100644 index 000000000..2287fd9b1 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_webnavigation.html @@ -0,0 +1,559 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_webnavigation_filters.html b/toolkit/components/webextensions/test/mochitest/test_ext_webnavigation_filters.html new file mode 100644 index 000000000..a0de5e9e5 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_webnavigation_filters.html @@ -0,0 +1,308 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_background_events.html b/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_background_events.html new file mode 100644 index 000000000..78efeab35 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_background_events.html @@ -0,0 +1,116 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_basic.html b/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_basic.html new file mode 100644 index 000000000..ef77fee3b --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_basic.html @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + +
Sample text
+ + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_suspend.html b/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_suspend.html new file mode 100644 index 000000000..c8423ec7c --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_suspend.html @@ -0,0 +1,216 @@ + + + + Test for simple WebExtension + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_upload.html b/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_upload.html new file mode 100644 index 000000000..998ab9800 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_webrequest_upload.html @@ -0,0 +1,199 @@ + + + + + + + + + + + + + +
+ + + + +
+ +
+ + + +
+ + +
+ + +
+ + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_window_postMessage.html b/toolkit/components/webextensions/test/mochitest/test_ext_window_postMessage.html new file mode 100644 index 000000000..7d49d55ba --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_window_postMessage.html @@ -0,0 +1,105 @@ + + + + Test for content script + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_xhr_capabilities.html b/toolkit/components/webextensions/test/mochitest/test_ext_xhr_capabilities.html new file mode 100644 index 000000000..1afdadb9f --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/test_ext_xhr_capabilities.html @@ -0,0 +1,86 @@ + + + + Test XHR capabilities + + + + + + + + + + + + diff --git a/toolkit/components/webextensions/test/mochitest/webrequest_chromeworker.js b/toolkit/components/webextensions/test/mochitest/webrequest_chromeworker.js new file mode 100644 index 000000000..ccfb2ac1f --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/webrequest_chromeworker.js @@ -0,0 +1,8 @@ +"use strict"; + +onmessage = function(event) { + fetch("https://example.com/example.txt").then(() => { + postMessage("Done!"); + }); +}; + diff --git a/toolkit/components/webextensions/test/mochitest/webrequest_test.jsm b/toolkit/components/webextensions/test/mochitest/webrequest_test.jsm new file mode 100644 index 000000000..bfb148301 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/webrequest_test.jsm @@ -0,0 +1,22 @@ +"use strict"; + +this.EXPORTED_SYMBOLS = ["webrequest_test"]; + +Components.utils.importGlobalProperties(["fetch", "XMLHttpRequest"]); + +this.webrequest_test = { + testFetch(url) { + return fetch(url); + }, + + testXHR(url) { + return new Promise(resolve => { + let xhr = new XMLHttpRequest(); + xhr.open("HEAD", url); + xhr.onload = () => { + resolve(); + }; + xhr.send(); + }); + }, +}; diff --git a/toolkit/components/webextensions/test/mochitest/webrequest_worker.js b/toolkit/components/webextensions/test/mochitest/webrequest_worker.js new file mode 100644 index 000000000..dcffd0857 --- /dev/null +++ b/toolkit/components/webextensions/test/mochitest/webrequest_worker.js @@ -0,0 +1,3 @@ +"use strict"; + +fetch("https://example.com/example.txt"); diff --git a/toolkit/components/webextensions/test/xpcshell/.eslintrc.js b/toolkit/components/webextensions/test/xpcshell/.eslintrc.js new file mode 100644 index 000000000..3758537ef --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/.eslintrc.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { // eslint-disable-line no-undef + "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc.js", + + "globals": { + "browser": false, + }, +}; diff --git a/toolkit/components/webextensions/test/xpcshell/data/file_download.html b/toolkit/components/webextensions/test/xpcshell/data/file_download.html new file mode 100644 index 000000000..d970c6325 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/data/file_download.html @@ -0,0 +1,12 @@ + + + + + + + + +
Download HTML File
+ + + diff --git a/toolkit/components/webextensions/test/xpcshell/data/file_download.txt b/toolkit/components/webextensions/test/xpcshell/data/file_download.txt new file mode 100644 index 000000000..6293c7af7 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/data/file_download.txt @@ -0,0 +1 @@ +This is a sample file used in download tests. diff --git a/toolkit/components/webextensions/test/xpcshell/head.js b/toolkit/components/webextensions/test/xpcshell/head.js new file mode 100644 index 000000000..9e22be6da --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/head.js @@ -0,0 +1,111 @@ +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +/* exported createHttpServer, promiseConsoleOutput, cleanupDir */ + +Components.utils.import("resource://gre/modules/Task.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Timer.jsm"); +Components.utils.import("resource://testing-common/AddonTestUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", + "resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Extension", + "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData", + "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement", + "resource://gre/modules/ExtensionManagement.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils", + "resource://testing-common/ExtensionXPCShellUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "HttpServer", + "resource://testing-common/httpd.js"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Schemas", + "resource://gre/modules/Schemas.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +ExtensionTestUtils.init(this); + +/** + * Creates a new HttpServer for testing, and begins listening on the + * specified port. Automatically shuts down the server when the test + * unit ends. + * + * @param {integer} [port] + * The port to listen on. If omitted, listen on a random + * port. The latter is the preferred behavior. + * + * @returns {HttpServer} + */ +function createHttpServer(port = -1) { + let server = new HttpServer(); + server.start(port); + + do_register_cleanup(() => { + return new Promise(resolve => { + server.stop(resolve); + }); + }); + + return server; +} + +var promiseConsoleOutput = Task.async(function* (task) { + const DONE = `=== console listener ${Math.random()} done ===`; + + let listener; + let messages = []; + let awaitListener = new Promise(resolve => { + listener = msg => { + if (msg == DONE) { + resolve(); + } else { + void (msg instanceof Ci.nsIConsoleMessage); + messages.push(msg); + } + }; + }); + + Services.console.registerListener(listener); + try { + let result = yield task(); + + Services.console.logStringMessage(DONE); + yield awaitListener; + + return {messages, result}; + } finally { + Services.console.unregisterListener(listener); + } +}); + +// Attempt to remove a directory. If the Windows OS is still using the +// file sometimes remove() will fail. So try repeatedly until we can +// remove it or we give up. +function cleanupDir(dir) { + let count = 0; + return new Promise((resolve, reject) => { + function tryToRemoveDir() { + count += 1; + try { + dir.remove(true); + } catch (e) { + // ignore + } + if (!dir.exists()) { + return resolve(); + } + if (count >= 25) { + return reject(`Failed to cleanup directory: ${dir}`); + } + setTimeout(tryToRemoveDir, 100); + } + tryToRemoveDir(); + }); +} diff --git a/toolkit/components/webextensions/test/xpcshell/head_native_messaging.js b/toolkit/components/webextensions/test/xpcshell/head_native_messaging.js new file mode 100644 index 000000000..f7c619b76 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/head_native_messaging.js @@ -0,0 +1,131 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* globals AppConstants, FileUtils */ +/* exported getSubprocessCount, setupHosts, waitForSubprocessExit */ + +XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry", + "resource://testing-common/MockRegistry.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "setTimeout", + "resource://gre/modules/Timer.jsm"); + +let {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm"); + + +// It's important that we use a space in this directory name to make sure we +// correctly handle executing batch files with spaces in their path. +let tmpDir = FileUtils.getDir("TmpD", ["Native Messaging"]); +tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +do_register_cleanup(() => { + tmpDir.remove(true); +}); + +function getPath(filename) { + return OS.Path.join(tmpDir.path, filename); +} + +const ID = "native@tests.mozilla.org"; + + +function* setupHosts(scripts) { + const PERMS = {unixMode: 0o755}; + + const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); + const pythonPath = yield Subprocess.pathSearch(env.get("PYTHON")); + + function* writeManifest(script, scriptPath, path) { + let body = `#!${pythonPath} -u\n${script.script}`; + + yield OS.File.writeAtomic(scriptPath, body); + yield OS.File.setPermissions(scriptPath, PERMS); + + let manifest = { + name: script.name, + description: script.description, + path, + type: "stdio", + allowed_extensions: [ID], + }; + + let manifestPath = getPath(`${script.name}.json`); + yield OS.File.writeAtomic(manifestPath, JSON.stringify(manifest)); + + return manifestPath; + } + + switch (AppConstants.platform) { + case "macosx": + case "linux": + let dirProvider = { + getFile(property) { + if (property == "XREUserNativeMessaging") { + return tmpDir.clone(); + } else if (property == "XRESysNativeMessaging") { + return tmpDir.clone(); + } + return null; + }, + }; + + Services.dirsvc.registerProvider(dirProvider); + do_register_cleanup(() => { + Services.dirsvc.unregisterProvider(dirProvider); + }); + + for (let script of scripts) { + let path = getPath(`${script.name}.py`); + + yield writeManifest(script, path, path); + } + break; + + case "win": + const REGKEY = String.raw`Software\Mozilla\NativeMessagingHosts`; + + let registry = new MockRegistry(); + do_register_cleanup(() => { + registry.shutdown(); + }); + + for (let script of scripts) { + // It's important that we use a space in this filename. See directory + // name comment above. + let batPath = getPath(`batch ${script.name}.bat`); + let scriptPath = getPath(`${script.name}.py`); + + let batBody = `@ECHO OFF\n${pythonPath} -u "${scriptPath}" %*\n`; + yield OS.File.writeAtomic(batPath, batBody); + + // Create absolute and relative path versions of the entry. + for (let [name, path] of [[script.name, batPath], + [`relative.${script.name}`, OS.Path.basename(batPath)]]) { + script.name = name; + let manifestPath = yield writeManifest(script, scriptPath, path); + + registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + `${REGKEY}\\${script.name}`, "", manifestPath); + } + } + break; + + default: + ok(false, `Native messaging is not supported on ${AppConstants.platform}`); + } +} + + +function getSubprocessCount() { + return SubprocessImpl.Process.getWorker().call("getProcesses", []) + .then(result => result.size); +} +function waitForSubprocessExit() { + return SubprocessImpl.Process.getWorker().call("waitForNoProcesses", []).then(() => { + // Return to the main event loop to give IO handlers enough time to consume + // their remaining buffered input. + return new Promise(resolve => setTimeout(resolve, 0)); + }); +} diff --git a/toolkit/components/webextensions/test/xpcshell/head_sync.js b/toolkit/components/webextensions/test/xpcshell/head_sync.js new file mode 100644 index 000000000..9b66b78e7 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/head_sync.js @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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"; + +/* exported withSyncContext */ + +Components.utils.import("resource://gre/modules/Services.jsm", this); +Components.utils.import("resource://gre/modules/ExtensionCommon.jsm", this); + +var { + BaseContext, +} = ExtensionCommon; + +class Context extends BaseContext { + constructor(principal) { + super(); + Object.defineProperty(this, "principal", { + value: principal, + configurable: true, + }); + this.sandbox = Components.utils.Sandbox(principal, {wantXrays: false}); + this.extension = {id: "test@web.extension"}; + } + + get cloneScope() { + return this.sandbox; + } +} + +/** + * Call the given function with a newly-constructed context. + * Unload the context on the way out. + * + * @param {function} f the function to call + */ +function* withContext(f) { + const ssm = Services.scriptSecurityManager; + const PRINCIPAL1 = ssm.createCodebasePrincipalFromOrigin("http://www.example.org"); + const context = new Context(PRINCIPAL1); + try { + yield* f(context); + } finally { + yield context.unload(); + } +} + +/** + * Like withContext(), but also turn on the "storage.sync" pref for + * the duration of the function. + * Calls to this function can be replaced with calls to withContext + * once the pref becomes on by default. + * + * @param {function} f the function to call + */ +function* withSyncContext(f) { + const STORAGE_SYNC_PREF = "webextensions.storage.sync.enabled"; + let prefs = Services.prefs; + + try { + prefs.setBoolPref(STORAGE_SYNC_PREF, true); + yield* withContext(f); + } finally { + prefs.clearUserPref(STORAGE_SYNC_PREF); + } +} diff --git a/toolkit/components/webextensions/test/xpcshell/native_messaging.ini b/toolkit/components/webextensions/test/xpcshell/native_messaging.ini new file mode 100644 index 000000000..d0e1da163 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/native_messaging.ini @@ -0,0 +1,13 @@ +[DEFAULT] +head = head.js head_native_messaging.js +tail = +firefox-appdir = browser +skip-if = appname == "thunderbird" || os == "android" +subprocess = true +support-files = + data/** +tags = webextensions + +[test_ext_native_messaging.js] +[test_ext_native_messaging_perf.js] +[test_ext_native_messaging_unresponsive.js] diff --git a/toolkit/components/webextensions/test/xpcshell/test_csp_custom_policies.js b/toolkit/components/webextensions/test/xpcshell/test_csp_custom_policies.js new file mode 100644 index 000000000..b6213baac --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_csp_custom_policies.js @@ -0,0 +1,38 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Cu.import("resource://gre/modules/Preferences.jsm"); + +const ADDON_ID = "test@web.extension"; + +const aps = Cc["@mozilla.org/addons/policy-service;1"] + .getService(Ci.nsIAddonPolicyService).wrappedJSObject; + +do_register_cleanup(() => { + aps.setAddonCSP(ADDON_ID, null); +}); + +add_task(function* test_addon_csp() { + equal(aps.baseCSP, Preferences.get("extensions.webextensions.base-content-security-policy"), + "Expected base CSP value"); + + equal(aps.defaultCSP, Preferences.get("extensions.webextensions.default-content-security-policy"), + "Expected default CSP value"); + + equal(aps.getAddonCSP(ADDON_ID), aps.defaultCSP, + "CSP for unknown add-on ID should be the default CSP"); + + + const CUSTOM_POLICY = "script-src: 'self' https://xpcshell.test.custom.csp; object-src: 'none'"; + + aps.setAddonCSP(ADDON_ID, CUSTOM_POLICY); + + equal(aps.getAddonCSP(ADDON_ID), CUSTOM_POLICY, "CSP should point to add-on's custom policy"); + + + aps.setAddonCSP(ADDON_ID, null); + + equal(aps.getAddonCSP(ADDON_ID), aps.defaultCSP, + "CSP should revert to default when set to null"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_csp_validator.js b/toolkit/components/webextensions/test/xpcshell/test_csp_validator.js new file mode 100644 index 000000000..59a7322bc --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_csp_validator.js @@ -0,0 +1,85 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const cps = Cc["@mozilla.org/addons/content-policy;1"].getService(Ci.nsIAddonContentPolicy); + +add_task(function* test_csp_validator() { + let checkPolicy = (policy, expectedResult, message = null) => { + do_print(`Checking policy: ${policy}`); + + let result = cps.validateAddonCSP(policy); + equal(result, expectedResult); + }; + + checkPolicy("script-src 'self'; object-src 'self';", + null); + + let hash = "'sha256-NjZhMDQ1YjQ1MjEwMmM1OWQ4NDBlYzA5N2Q1OWQ5NDY3ZTEzYTNmMzRmNjQ5NGU1MzlmZmQzMmMxYmIzNWYxOCAgLQo='"; + + checkPolicy(`script-src 'self' https://com https://*.example.com moz-extension://09abcdef blob: filesystem: ${hash} 'unsafe-eval'; ` + + `object-src 'self' https://com https://*.example.com moz-extension://09abcdef blob: filesystem: ${hash}`, + null); + + checkPolicy("", + "Policy is missing a required \u2018script-src\u2019 directive"); + + checkPolicy("object-src 'none';", + "Policy is missing a required \u2018script-src\u2019 directive"); + + + checkPolicy("default-src 'self'", null, + "A valid default-src should count as a valid script-src or object-src"); + + checkPolicy("default-src 'self'; script-src 'self'", null, + "A valid default-src should count as a valid script-src or object-src"); + + checkPolicy("default-src 'self'; object-src 'self'", null, + "A valid default-src should count as a valid script-src or object-src"); + + + checkPolicy("default-src 'self'; script-src http://example.com", + "\u2018script-src\u2019 directive contains a forbidden http: protocol source", + "A valid default-src should not allow an invalid script-src directive"); + + checkPolicy("default-src 'self'; object-src http://example.com", + "\u2018object-src\u2019 directive contains a forbidden http: protocol source", + "A valid default-src should not allow an invalid object-src directive"); + + + checkPolicy("script-src 'self';", + "Policy is missing a required \u2018object-src\u2019 directive"); + + checkPolicy("script-src 'none'; object-src 'none'", + "\u2018script-src\u2019 must include the source 'self'"); + + checkPolicy("script-src 'self'; object-src 'none';", + null); + + checkPolicy("script-src 'self' 'unsafe-inline'; object-src 'self';", + "\u2018script-src\u2019 directive contains a forbidden 'unsafe-inline' keyword"); + + + let directives = ["script-src", "object-src"]; + + for (let [directive, other] of [directives, directives.slice().reverse()]) { + for (let src of ["https://*", "https://*.blogspot.com", "https://*"]) { + checkPolicy(`${directive} 'self' ${src}; ${other} 'self';`, + `https: wildcard sources in \u2018${directive}\u2019 directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)`); + } + + checkPolicy(`${directive} 'self' https:; ${other} 'self';`, + `https: protocol requires a host in \u2018${directive}\u2019 directives`); + + checkPolicy(`${directive} 'self' http://example.com; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden http: protocol source`); + + for (let protocol of ["http", "ftp", "meh"]) { + checkPolicy(`${directive} 'self' ${protocol}:; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden ${protocol}: protocol source`); + } + + checkPolicy(`${directive} 'self' 'nonce-01234'; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden 'nonce-*' keyword`); + } +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_alarms.js b/toolkit/components/webextensions/test/xpcshell/test_ext_alarms.js new file mode 100644 index 000000000..936c984c6 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_alarms.js @@ -0,0 +1,210 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_alarm_without_permissions() { + function backgroundScript() { + browser.test.assertTrue(!browser.alarms, + "alarm API is not available when the alarm permission is not required"); + browser.test.notifyPass("alarms_permission"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: [], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("alarms_permission"); + yield extension.unload(); +}); + + +add_task(function* test_alarm_fires() { + function backgroundScript() { + let ALARM_NAME = "test_ext_alarms"; + let timer; + + browser.alarms.onAlarm.addListener(alarm => { + browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the correct name"); + clearTimeout(timer); + browser.test.notifyPass("alarm-fires"); + }); + + browser.alarms.create(ALARM_NAME, {delayInMinutes: 0.02}); + + timer = setTimeout(async () => { + browser.test.fail("alarm fired within expected time"); + let wasCleared = await browser.alarms.clear(ALARM_NAME); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + browser.test.notifyFail("alarm-fires"); + }, 10000); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("alarm-fires"); + yield extension.unload(); +}); + + +add_task(function* test_alarm_fires_with_when() { + function backgroundScript() { + let ALARM_NAME = "test_ext_alarms"; + let timer; + + browser.alarms.onAlarm.addListener(alarm => { + browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the expected name"); + clearTimeout(timer); + browser.test.notifyPass("alarm-when"); + }); + + browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000}); + + timer = setTimeout(async () => { + browser.test.fail("alarm fired within expected time"); + let wasCleared = await browser.alarms.clear(ALARM_NAME); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + browser.test.notifyFail("alarm-when"); + }, 10000); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("alarm-when"); + yield extension.unload(); +}); + + +add_task(function* test_alarm_clear_non_matching_name() { + async function backgroundScript() { + let ALARM_NAME = "test_ext_alarms"; + + browser.alarms.create(ALARM_NAME, {when: Date.now() + 2000}); + + let wasCleared = await browser.alarms.clear(ALARM_NAME + "1"); + browser.test.assertFalse(wasCleared, "alarm was not cleared"); + + let alarms = await browser.alarms.getAll(); + browser.test.assertEq(1, alarms.length, "alarm was not removed"); + browser.test.notifyPass("alarm-clear"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("alarm-clear"); + yield extension.unload(); +}); + + +add_task(function* test_alarm_get_and_clear_single_argument() { + async function backgroundScript() { + browser.alarms.create({when: Date.now() + 2000}); + + let alarm = await browser.alarms.get(); + browser.test.assertEq("", alarm.name, "expected alarm returned"); + + let wasCleared = await browser.alarms.clear(); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + let alarms = await browser.alarms.getAll(); + browser.test.assertEq(0, alarms.length, "alarm was removed"); + + browser.test.notifyPass("alarm-single-arg"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("alarm-single-arg"); + yield extension.unload(); +}); + + +add_task(function* test_get_get_all_clear_all_alarms() { + async function backgroundScript() { + const ALARM_NAME = "test_alarm"; + + let suffixes = [0, 1, 2]; + + for (let suffix of suffixes) { + browser.alarms.create(ALARM_NAME + suffix, {when: Date.now() + (suffix + 1) * 10000}); + } + + let alarms = await browser.alarms.getAll(); + browser.test.assertEq(suffixes.length, alarms.length, "expected number of alarms were found"); + alarms.forEach((alarm, index) => { + browser.test.assertEq(ALARM_NAME + index, alarm.name, "alarm has the expected name"); + }); + + + for (let suffix of suffixes) { + let alarm = await browser.alarms.get(ALARM_NAME + suffix); + browser.test.assertEq(ALARM_NAME + suffix, alarm.name, "alarm has the expected name"); + browser.test.sendMessage(`get-${suffix}`); + } + + let wasCleared = await browser.alarms.clear(ALARM_NAME + suffixes[0]); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + alarms = await browser.alarms.getAll(); + browser.test.assertEq(2, alarms.length, "alarm was removed"); + + let alarm = await browser.alarms.get(ALARM_NAME + suffixes[0]); + browser.test.assertEq(undefined, alarm, "non-existent alarm is undefined"); + browser.test.sendMessage(`get-invalid`); + + wasCleared = await browser.alarms.clearAll(); + browser.test.assertTrue(wasCleared, "alarms were cleared"); + + alarms = await browser.alarms.getAll(); + browser.test.assertEq(0, alarms.length, "no alarms exist"); + browser.test.sendMessage("clearAll"); + browser.test.sendMessage("clear"); + browser.test.sendMessage("getAll"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + yield Promise.all([ + extension.startup(), + extension.awaitMessage("getAll"), + extension.awaitMessage("get-0"), + extension.awaitMessage("get-1"), + extension.awaitMessage("get-2"), + extension.awaitMessage("clear"), + extension.awaitMessage("get-invalid"), + extension.awaitMessage("clearAll"), + ]); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_does_not_fire.js b/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_does_not_fire.js new file mode 100644 index 000000000..11407b108 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_does_not_fire.js @@ -0,0 +1,33 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_cleared_alarm_does_not_fire() { + async function backgroundScript() { + let ALARM_NAME = "test_ext_alarms"; + + browser.alarms.onAlarm.addListener(alarm => { + browser.test.fail("cleared alarm does not fire"); + browser.test.notifyFail("alarm-cleared"); + }); + browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000}); + + let wasCleared = await browser.alarms.clear(ALARM_NAME); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + await new Promise(resolve => setTimeout(resolve, 2000)); + + browser.test.notifyPass("alarm-cleared"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("alarm-cleared"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_periodic.js b/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_periodic.js new file mode 100644 index 000000000..6bcdf4e33 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_periodic.js @@ -0,0 +1,44 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_periodic_alarm_fires() { + function backgroundScript() { + const ALARM_NAME = "test_ext_alarms"; + let count = 0; + let timer; + + browser.alarms.onAlarm.addListener(async alarm => { + browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the expected name"); + if (count++ === 3) { + clearTimeout(timer); + let wasCleared = await browser.alarms.clear(ALARM_NAME); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + browser.test.notifyPass("alarm-periodic"); + } + }); + + browser.alarms.create(ALARM_NAME, {periodInMinutes: 0.02}); + + timer = setTimeout(async () => { + browser.test.fail("alarm fired expected number of times"); + + let wasCleared = await browser.alarms.clear(ALARM_NAME); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + browser.test.notifyFail("alarm-periodic"); + }, 30000); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("alarm-periodic"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_replaces.js b/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_replaces.js new file mode 100644 index 000000000..96f61acb5 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_alarms_replaces.js @@ -0,0 +1,44 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + + +add_task(function* test_duplicate_alarm_name_replaces_alarm() { + function backgroundScript() { + let count = 0; + + browser.alarms.onAlarm.addListener(async alarm => { + if (alarm.name === "master alarm") { + browser.alarms.create("child alarm", {delayInMinutes: 0.05}); + let results = await browser.alarms.getAll(); + + browser.test.assertEq(2, results.length, "exactly two alarms exist"); + browser.test.assertEq("master alarm", results[0].name, "first alarm has the expected name"); + browser.test.assertEq("child alarm", results[1].name, "second alarm has the expected name"); + + if (count++ === 3) { + await browser.alarms.clear("master alarm"); + await browser.alarms.clear("child alarm"); + + browser.test.notifyPass("alarm-duplicate"); + } + } else { + browser.test.fail("duplicate named alarm replaced existing alarm"); + browser.test.notifyFail("alarm-duplicate"); + } + }); + + browser.alarms.create("master alarm", {delayInMinutes: 0.025, periodInMinutes: 0.025}); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("alarm-duplicate"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_api_permissions.js b/toolkit/components/webextensions/test/xpcshell/test_ext_api_permissions.js new file mode 100644 index 000000000..d653d0e7a --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_api_permissions.js @@ -0,0 +1,64 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +let {Management} = Cu.import("resource://gre/modules/Extension.jsm", {}); +function getNextContext() { + return new Promise(resolve => { + Management.on("proxy-context-load", function listener(type, context) { + Management.off("proxy-context-load", listener); + resolve(context); + }); + }); +} + +add_task(function* test_storage_api_without_permissions() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + // Force API initialization. + void browser.storage; + }, + + manifest: { + permissions: [], + }, + }); + + let contextPromise = getNextContext(); + yield extension.startup(); + + let context = yield contextPromise; + + // Force API initialization. + void context.apiObj; + + ok(!("storage" in context.apiObj), + "The storage API should not be initialized"); + + yield extension.unload(); +}); + +add_task(function* test_storage_api_with_permissions() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + void browser.storage; + }, + + manifest: { + permissions: ["storage"], + }, + }); + + let contextPromise = getNextContext(); + yield extension.startup(); + + let context = yield contextPromise; + + // Force API initialization. + void context.apiObj; + + equal(typeof context.apiObj.storage, "object", + "The storage API should be initialized"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_apimanager.js b/toolkit/components/webextensions/test/xpcshell/test_ext_apimanager.js new file mode 100644 index 000000000..3f6672a11 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_apimanager.js @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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"; + +Cu.import("resource://gre/modules/ExtensionCommon.jsm"); + +const { + SchemaAPIManager, +} = ExtensionCommon; + +this.unknownvar = "Some module-global var"; + +var gUniqueId = 0; + +// SchemaAPIManager's loadScript uses loadSubScript to load a script. This +// requires a local (resource://) URL. So create such a temporary URL for +// testing. +function toLocalURI(code) { + let dataUrl = `data:charset=utf-8,${encodeURIComponent(code)}`; + let uniqueResPart = `need-a-local-uri-for-subscript-loading-${++gUniqueId}`; + Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler) + .setSubstitution(uniqueResPart, Services.io.newURI(dataUrl, null, null)); + return `resource://${uniqueResPart}`; +} + +add_task(function* test_global_isolation() { + let manA = new SchemaAPIManager("procA"); + let manB = new SchemaAPIManager("procB"); + + // The "global" variable should be persistent and shared. + manA.loadScript(toLocalURI`global.globalVar = 1;`); + do_check_eq(manA.global.globalVar, 1); + do_check_eq(manA.global.unknownvar, undefined); + manA.loadScript(toLocalURI`global.canSeeGlobal = global.globalVar;`); + do_check_eq(manA.global.canSeeGlobal, 1); + + // Each loadScript call should have their own scope, and global is shared. + manA.loadScript(toLocalURI`this.aVar = 1; global.thisScopeVar = aVar`); + do_check_eq(manA.global.aVar, undefined); + do_check_eq(manA.global.thisScopeVar, 1); + manA.loadScript(toLocalURI`global.differentScopeVar = this.aVar;`); + do_check_eq(manA.global.differentScopeVar, undefined); + manA.loadScript(toLocalURI`global.cantSeeOtherScope = typeof aVar;`); + do_check_eq(manA.global.cantSeeOtherScope, "undefined"); + + manB.loadScript(toLocalURI`global.tryReadOtherGlobal = global.tryagain;`); + do_check_eq(manA.global.tryReadOtherGlobal, undefined); + + // Cu.import without second argument exports to the caller's global. Let's + // verify that it does not leak to the SchemaAPIManager's global. + do_check_eq(typeof ExtensionUtils, "undefined"); // Sanity check #1. + manA.loadScript(toLocalURI`global.hasExtUtils = typeof ExtensionUtils;`); + do_check_eq(manA.global.hasExtUtils, "undefined"); // Sanity check #2 + + Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + do_check_eq(typeof ExtensionUtils, "object"); // Sanity check #3. + + manA.loadScript(toLocalURI`global.hasExtUtils = typeof ExtensionUtils;`); + do_check_eq(manA.global.hasExtUtils, "undefined"); + manB.loadScript(toLocalURI`global.hasExtUtils = typeof ExtensionUtils;`); + do_check_eq(manB.global.hasExtUtils, "undefined"); + + // Confirm that Cu.import does not leak between SchemaAPIManager globals. + manA.loadScript(toLocalURI` + Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + global.hasExtUtils = typeof ExtensionUtils; + `); + do_check_eq(manA.global.hasExtUtils, "object"); // Sanity check. + manB.loadScript(toLocalURI`global.hasExtUtils = typeof ExtensionUtils;`); + do_check_eq(manB.global.hasExtUtils, "undefined"); + + // Prototype modifications should be isolated. + manA.loadScript(toLocalURI` + Object.prototype.modifiedByA = "Prrft"; + global.fromA = {}; + `); + manA.loadScript(toLocalURI` + global.fromAagain = {}; + `); + manB.loadScript(toLocalURI` + global.fromB = {}; + `); + do_check_eq(manA.global.modifiedByA, "Prrft"); + do_check_eq(manA.global.fromA.modifiedByA, "Prrft"); + do_check_eq(manA.global.fromAagain.modifiedByA, "Prrft"); + do_check_eq(manB.global.modifiedByA, undefined); + do_check_eq(manB.global.fromB.modifiedByA, undefined); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_background_generated_load_events.js b/toolkit/components/webextensions/test/xpcshell/test_ext_background_generated_load_events.js new file mode 100644 index 000000000..26282fcb9 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_background_generated_load_events.js @@ -0,0 +1,23 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* eslint-disable mozilla/balanced-listeners */ + +add_task(function* test_DOMContentLoaded_in_generated_background_page() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + function reportListener(event) { + browser.test.sendMessage("eventname", event.type); + } + document.addEventListener("DOMContentLoaded", reportListener); + window.addEventListener("load", reportListener); + }, + }); + + yield extension.startup(); + equal("DOMContentLoaded", yield extension.awaitMessage("eventname")); + equal("load", yield extension.awaitMessage("eventname")); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_background_generated_reload.js b/toolkit/components/webextensions/test/xpcshell/test_ext_background_generated_reload.js new file mode 100644 index 000000000..4bf59b798 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_background_generated_reload.js @@ -0,0 +1,24 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_reload_generated_background_page() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + if (location.hash !== "#firstrun") { + browser.test.sendMessage("first run"); + location.hash = "#firstrun"; + browser.test.assertEq("#firstrun", location.hash); + location.reload(); + } else { + browser.test.notifyPass("second run"); + } + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("first run"); + yield extension.awaitFinish("second run"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_background_global_history.js b/toolkit/components/webextensions/test/xpcshell/test_ext_background_global_history.js new file mode 100644 index 000000000..092a9f5b3 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_background_global_history.js @@ -0,0 +1,22 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Cu.import("resource://testing-common/PlacesTestUtils.jsm"); + +add_task(function* test_global_history() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.sendMessage("background-loaded", location.href); + }, + }); + + yield extension.startup(); + + let backgroundURL = yield extension.awaitMessage("background-loaded"); + + yield extension.unload(); + + let exists = yield PlacesTestUtils.isPageInDB(backgroundURL); + ok(!exists, "Background URL should not be in history database"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_background_private_browsing.js b/toolkit/components/webextensions/test/xpcshell/test_ext_background_private_browsing.js new file mode 100644 index 000000000..8e8b5e0b0 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_background_private_browsing.js @@ -0,0 +1,40 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Cu.import("resource://gre/modules/Preferences.jsm"); + +function* testBackgroundPage(expected) { + let extension = ExtensionTestUtils.loadExtension({ + async background() { + browser.test.assertEq(window, browser.extension.getBackgroundPage(), + "Caller should be able to access itself as a background page"); + browser.test.assertEq(window, await browser.runtime.getBackgroundPage(), + "Caller should be able to access itself as a background page"); + + browser.test.sendMessage("incognito", browser.extension.inIncognitoContext); + }, + }); + + yield extension.startup(); + + let incognito = yield extension.awaitMessage("incognito"); + equal(incognito, expected.incognito, "Expected incognito value"); + + yield extension.unload(); +} + +add_task(function* test_background_incognito() { + do_print("Test background page incognito value with permanent private browsing disabled"); + + yield testBackgroundPage({incognito: false}); + + do_print("Test background page incognito value with permanent private browsing enabled"); + + Preferences.set("browser.privatebrowsing.autostart", true); + do_register_cleanup(() => { + Preferences.reset("browser.privatebrowsing.autostart"); + }); + + yield testBackgroundPage({incognito: true}); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_background_runtime_connect_params.js b/toolkit/components/webextensions/test/xpcshell/test_ext_background_runtime_connect_params.js new file mode 100644 index 000000000..426833edd --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_background_runtime_connect_params.js @@ -0,0 +1,72 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +function backgroundScript() { + let received_ports_number = 0; + + const expected_received_ports_number = 1; + + function countReceivedPorts(port) { + received_ports_number++; + + if (port.name == "check-results") { + browser.runtime.onConnect.removeListener(countReceivedPorts); + + browser.test.assertEq(expected_received_ports_number, received_ports_number, "invalid connect should not create a port"); + + browser.test.notifyPass("runtime.connect invalid params"); + } + } + + browser.runtime.onConnect.addListener(countReceivedPorts); + + let childFrame = document.createElement("iframe"); + childFrame.src = "extensionpage.html"; + document.body.appendChild(childFrame); +} + +function senderScript() { + let detected_invalid_connect_params = 0; + + const invalid_connect_params = [ + // too many params + ["fake-extensions-id", {name: "fake-conn-name"}, "unexpected third params"], + // invalid params format + [{}, {}], + ["fake-extensions-id", "invalid-connect-info-format"], + ]; + const expected_detected_invalid_connect_params = invalid_connect_params.length; + + function assertInvalidConnectParamsException(params) { + try { + browser.runtime.connect(...params); + } catch (e) { + detected_invalid_connect_params++; + browser.test.assertTrue(e.toString().indexOf("Incorrect argument types for runtime.connect.") >= 0, "exception message is correct"); + } + } + for (let params of invalid_connect_params) { + assertInvalidConnectParamsException(params); + } + browser.test.assertEq(expected_detected_invalid_connect_params, detected_invalid_connect_params, "all invalid runtime.connect params detected"); + + browser.runtime.connect(browser.runtime.id, {name: "check-results"}); +} + +let extensionData = { + background: backgroundScript, + files: { + "senderScript.js": senderScript, + "extensionpage.html": ``, + }, +}; + +add_task(function* test_backgroundRuntimeConnectParams() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + + yield extension.awaitFinish("runtime.connect invalid params"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_background_sub_windows.js b/toolkit/components/webextensions/test/xpcshell/test_ext_background_sub_windows.js new file mode 100644 index 000000000..c5f2f1332 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_background_sub_windows.js @@ -0,0 +1,45 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* testBackgroundWindow() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.log("background script executed"); + + browser.test.sendMessage("background-script-load"); + + let img = document.createElement("img"); + img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; + document.body.appendChild(img); + + img.onload = () => { + browser.test.log("image loaded"); + + let iframe = document.createElement("iframe"); + iframe.src = "about:blank?1"; + + iframe.onload = () => { + browser.test.log("iframe loaded"); + setTimeout(() => { + browser.test.notifyPass("background sub-window test done"); + }, 0); + }; + document.body.appendChild(iframe); + }; + }, + }); + + let loadCount = 0; + extension.onMessage("background-script-load", () => { + loadCount++; + }); + + yield extension.startup(); + + yield extension.awaitFinish("background sub-window test done"); + + equal(loadCount, 1, "background script loaded only once"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_background_window_properties.js b/toolkit/components/webextensions/test/xpcshell/test_ext_background_window_properties.js new file mode 100644 index 000000000..948e2913e --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_background_window_properties.js @@ -0,0 +1,34 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* testBackgroundWindowProperties() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + let expectedValues = { + screenX: 0, + screenY: 0, + outerWidth: 0, + outerHeight: 0, + }; + + for (let k in window) { + try { + if (k in expectedValues) { + browser.test.assertEq(expectedValues[k], window[k], + `should return the expected value for window property: ${k}`); + } else { + void window[k]; + } + } catch (e) { + browser.test.assertEq(null, e, `unexpected exception accessing window property: ${k}`); + } + } + + browser.test.notifyPass("background.testWindowProperties.done"); + }, + }); + yield extension.startup(); + yield extension.awaitFinish("background.testWindowProperties.done"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_contexts.js b/toolkit/components/webextensions/test/xpcshell/test_ext_contexts.js new file mode 100644 index 000000000..56a14e189 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_contexts.js @@ -0,0 +1,190 @@ +"use strict"; + +const global = this; + +Cu.import("resource://gre/modules/Timer.jsm"); + +Cu.import("resource://gre/modules/ExtensionCommon.jsm"); +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +var { + BaseContext, +} = ExtensionCommon; + +var { + EventManager, + SingletonEventManager, +} = ExtensionUtils; + +class StubContext extends BaseContext { + constructor() { + let fakeExtension = {id: "test@web.extension"}; + super("testEnv", fakeExtension); + this.sandbox = Cu.Sandbox(global); + } + + get cloneScope() { + return this.sandbox; + } +} + + +add_task(function* test_post_unload_promises() { + let context = new StubContext(); + + let fail = result => { + ok(false, `Unexpected callback: ${result}`); + }; + + // Make sure promises resolve normally prior to unload. + let promises = [ + context.wrapPromise(Promise.resolve()), + context.wrapPromise(Promise.reject({message: ""})).catch(() => {}), + ]; + + yield Promise.all(promises); + + // Make sure promises that resolve after unload do not trigger + // resolution handlers. + + context.wrapPromise(Promise.resolve("resolved")) + .then(fail); + + context.wrapPromise(Promise.reject({message: "rejected"})) + .then(fail, fail); + + context.unload(); + + // The `setTimeout` ensures that we return to the event loop after + // promise resolution, which means we're guaranteed to return after + // any micro-tasks that get enqueued by the resolution handlers above. + yield new Promise(resolve => setTimeout(resolve, 0)); +}); + + +add_task(function* test_post_unload_listeners() { + let context = new StubContext(); + + let fireEvent; + let onEvent = new EventManager(context, "onEvent", fire => { + fireEvent = fire; + return () => {}; + }); + + let fireSingleton; + let onSingleton = new SingletonEventManager(context, "onSingleton", callback => { + fireSingleton = () => { + Promise.resolve().then(callback); + }; + return () => {}; + }); + + let fail = event => { + ok(false, `Unexpected event: ${event}`); + }; + + // Check that event listeners aren't called after they've been removed. + onEvent.addListener(fail); + onSingleton.addListener(fail); + + let promises = [ + new Promise(resolve => onEvent.addListener(resolve)), + new Promise(resolve => onSingleton.addListener(resolve)), + ]; + + fireEvent("onEvent"); + fireSingleton("onSingleton"); + + // Both `fireEvent` calls are dispatched asynchronously, so they won't + // have fired by this point. The `fail` listeners that we remove now + // should not be called, even though the events have already been + // enqueued. + onEvent.removeListener(fail); + onSingleton.removeListener(fail); + + // Wait for the remaining listeners to be called, which should always + // happen after the `fail` listeners would normally be called. + yield Promise.all(promises); + + // Check that event listeners aren't called after the context has + // unloaded. + onEvent.addListener(fail); + onSingleton.addListener(fail); + + // The EventManager `fire` callback always dispatches events + // asynchronously, so we need to test that any pending event callbacks + // aren't fired after the context unloads. We also need to test that + // any `fire` calls that happen *after* the context is unloaded also + // do not trigger callbacks. + fireEvent("onEvent"); + Promise.resolve("onEvent").then(fireEvent); + + fireSingleton("onSingleton"); + Promise.resolve("onSingleton").then(fireSingleton); + + context.unload(); + + // The `setTimeout` ensures that we return to the event loop after + // promise resolution, which means we're guaranteed to return after + // any micro-tasks that get enqueued by the resolution handlers above. + yield new Promise(resolve => setTimeout(resolve, 0)); +}); + +class Context extends BaseContext { + constructor(principal) { + let fakeExtension = {id: "test@web.extension"}; + super("testEnv", fakeExtension); + Object.defineProperty(this, "principal", { + value: principal, + configurable: true, + }); + this.sandbox = Cu.Sandbox(principal, {wantXrays: false}); + } + + get cloneScope() { + return this.sandbox; + } +} + +let ssm = Services.scriptSecurityManager; +const PRINCIPAL1 = ssm.createCodebasePrincipalFromOrigin("http://www.example.org"); +const PRINCIPAL2 = ssm.createCodebasePrincipalFromOrigin("http://www.somethingelse.org"); + +// Test that toJSON() works in the json sandbox +add_task(function* test_stringify_toJSON() { + let context = new Context(PRINCIPAL1); + let obj = Cu.evalInSandbox("({hidden: true, toJSON() { return {visible: true}; } })", context.sandbox); + + let stringified = context.jsonStringify(obj); + let expected = JSON.stringify({visible: true}); + equal(stringified, expected, "Stringified object with toJSON() method is as expected"); +}); + +// Test that stringifying in inaccessible property throws +add_task(function* test_stringify_inaccessible() { + let context = new Context(PRINCIPAL1); + let sandbox = context.sandbox; + let sandbox2 = Cu.Sandbox(PRINCIPAL2); + + Cu.waiveXrays(sandbox).subobj = Cu.evalInSandbox("({ subobject: true })", sandbox2); + let obj = Cu.evalInSandbox("({ local: true, nested: subobj })", sandbox); + Assert.throws(() => { + context.jsonStringify(obj); + }); +}); + +add_task(function* test_stringify_accessible() { + // Test that an accessible property from another global is included + let principal = Cu.getObjectPrincipal(Cu.Sandbox([PRINCIPAL1, PRINCIPAL2])); + let context = new Context(principal); + let sandbox = context.sandbox; + let sandbox2 = Cu.Sandbox(PRINCIPAL2); + + Cu.waiveXrays(sandbox).subobj = Cu.evalInSandbox("({ subobject: true })", sandbox2); + let obj = Cu.evalInSandbox("({ local: true, nested: subobj })", sandbox); + let stringified = context.jsonStringify(obj); + + let expected = JSON.stringify({local: true, nested: {subobject: true}}); + equal(stringified, expected, "Stringified object with accessible property is as expected"); +}); + diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_downloads.js b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads.js new file mode 100644 index 000000000..058b9b18c --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads.js @@ -0,0 +1,76 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_downloads_api_namespace_and_permissions() { + function backgroundScript() { + browser.test.assertTrue(!!browser.downloads, "`downloads` API is present."); + browser.test.assertTrue(!!browser.downloads.FilenameConflictAction, + "`downloads.FilenameConflictAction` enum is present."); + browser.test.assertTrue(!!browser.downloads.InterruptReason, + "`downloads.InterruptReason` enum is present."); + browser.test.assertTrue(!!browser.downloads.DangerType, + "`downloads.DangerType` enum is present."); + browser.test.assertTrue(!!browser.downloads.State, + "`downloads.State` enum is present."); + browser.test.notifyPass("downloads tests"); + } + + let extensionData = { + background: backgroundScript, + manifest: { + permissions: ["downloads", "downloads.open", "downloads.shelf"], + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + yield extension.awaitFinish("downloads tests"); + yield extension.unload(); +}); + +add_task(function* test_downloads_open_permission() { + function backgroundScript() { + browser.test.assertFalse("open" in browser.downloads, + "`downloads.open` permission is required."); + browser.test.notifyPass("downloads tests"); + } + + let extensionData = { + background: backgroundScript, + manifest: { + permissions: ["downloads"], + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + yield extension.awaitFinish("downloads tests"); + yield extension.unload(); +}); + +add_task(function* test_downloads_open() { + async function backgroundScript() { + await browser.test.assertRejects( + browser.downloads.open(10), + "Invalid download id 10", + "The error is informative."); + + browser.test.notifyPass("downloads tests"); + + // TODO: Once downloads.{pause,cancel,resume} lands (bug 1245602) test that this gives a good + // error when called with an incompleted download. + } + + let extensionData = { + background: backgroundScript, + manifest: { + permissions: ["downloads", "downloads.open"], + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + yield extension.awaitFinish("downloads tests"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js new file mode 100644 index 000000000..37ddd4d7c --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_download.js @@ -0,0 +1,354 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* global OS */ + +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/Downloads.jsm"); + +const gServer = createHttpServer(); +gServer.registerDirectory("/data/", do_get_file("data")); + +const WINDOWS = AppConstants.platform == "win"; + +const BASE = `http://localhost:${gServer.identity.primaryPort}/data`; +const FILE_NAME = "file_download.txt"; +const FILE_URL = BASE + "/" + FILE_NAME; +const FILE_NAME_UNIQUE = "file_download(1).txt"; +const FILE_LEN = 46; + +let downloadDir; + +function setup() { + downloadDir = FileUtils.getDir("TmpD", ["downloads"]); + downloadDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_print(`Using download directory ${downloadDir.path}`); + + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, downloadDir); + + do_register_cleanup(() => { + Services.prefs.clearUserPref("browser.download.folderList"); + Services.prefs.clearUserPref("browser.download.dir"); + + let entries = downloadDir.directoryEntries; + while (entries.hasMoreElements()) { + let entry = entries.getNext().QueryInterface(Ci.nsIFile); + ok(false, `Leftover file ${entry.path} in download directory`); + entry.remove(false); + } + + downloadDir.remove(false); + }); +} + +function backgroundScript() { + let blobUrl; + browser.test.onMessage.addListener(async (msg, ...args) => { + if (msg == "download.request") { + let options = args[0]; + + if (options.blobme) { + let blob = new Blob(options.blobme); + delete options.blobme; + blobUrl = options.url = window.URL.createObjectURL(blob); + } + + try { + let id = await browser.downloads.download(options); + browser.test.sendMessage("download.done", {status: "success", id}); + } catch (error) { + browser.test.sendMessage("download.done", {status: "error", errmsg: error.message}); + } + } else if (msg == "killTheBlob") { + window.URL.revokeObjectURL(blobUrl); + blobUrl = null; + } + }); + + browser.test.sendMessage("ready"); +} + +// This function is a bit of a sledgehammer, it looks at every download +// the browser knows about and waits for all active downloads to complete. +// But we only start one at a time and only do a handful in total, so +// this lets us test download() without depending on anything else. +async function waitForDownloads() { + let list = await Downloads.getList(Downloads.ALL); + let downloads = await list.getAll(); + + let inprogress = downloads.filter(dl => !dl.stopped); + return Promise.all(inprogress.map(dl => dl.whenSucceeded())); +} + +// Create a file in the downloads directory. +function touch(filename) { + let file = downloadDir.clone(); + file.append(filename); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); +} + +// Remove a file in the downloads directory. +function remove(filename, recursive = false) { + let file = downloadDir.clone(); + file.append(filename); + file.remove(recursive); +} + +add_task(function* test_downloads() { + setup(); + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["downloads"], + }, + }); + + function download(options) { + extension.sendMessage("download.request", options); + return extension.awaitMessage("download.done"); + } + + async function testDownload(options, localFile, expectedSize, description) { + let msg = await download(options); + equal(msg.status, "success", `downloads.download() works with ${description}`); + + await waitForDownloads(); + + let localPath = downloadDir.clone(); + let parts = Array.isArray(localFile) ? localFile : [localFile]; + + parts.map(p => localPath.append(p)); + equal(localPath.fileSize, expectedSize, "Downloaded file has expected size"); + localPath.remove(false); + } + + yield extension.startup(); + yield extension.awaitMessage("ready"); + do_print("extension started"); + + // Call download() with just the url property. + yield testDownload({url: FILE_URL}, FILE_NAME, FILE_LEN, "just source"); + + // Call download() with a filename property. + yield testDownload({ + url: FILE_URL, + filename: "newpath.txt", + }, "newpath.txt", FILE_LEN, "source and filename"); + + // Call download() with a filename with subdirs. + yield testDownload({ + url: FILE_URL, + filename: "sub/dir/file", + }, ["sub", "dir", "file"], FILE_LEN, "source and filename with subdirs"); + + // Call download() with a filename with existing subdirs. + yield testDownload({ + url: FILE_URL, + filename: "sub/dir/file2", + }, ["sub", "dir", "file2"], FILE_LEN, "source and filename with existing subdirs"); + + // Only run Windows path separator test on Windows. + if (WINDOWS) { + // Call download() with a filename with Windows path separator. + yield testDownload({ + url: FILE_URL, + filename: "sub\\dir\\file3", + }, ["sub", "dir", "file3"], FILE_LEN, "filename with Windows path separator"); + } + remove("sub", true); + + // Call download(), filename with subdir, skipping parts. + yield testDownload({ + url: FILE_URL, + filename: "skip//part", + }, ["skip", "part"], FILE_LEN, "source, filename, with subdir, skipping parts"); + remove("skip", true); + + // Check conflictAction of "uniquify". + touch(FILE_NAME); + yield testDownload({ + url: FILE_URL, + conflictAction: "uniquify", + }, FILE_NAME_UNIQUE, FILE_LEN, "conflictAction=uniquify"); + // todo check that preexisting file was not modified? + remove(FILE_NAME); + + // Check conflictAction of "overwrite". + touch(FILE_NAME); + yield testDownload({ + url: FILE_URL, + conflictAction: "overwrite", + }, FILE_NAME, FILE_LEN, "conflictAction=overwrite"); + + // Try to download in invalid url + yield download({url: "this is not a valid URL"}).then(msg => { + equal(msg.status, "error", "downloads.download() fails with invalid url"); + ok(/not a valid URL/.test(msg.errmsg), "error message for invalid url is correct"); + }); + + // Try to download to an empty path. + yield download({ + url: FILE_URL, + filename: "", + }).then(msg => { + equal(msg.status, "error", "downloads.download() fails with empty filename"); + equal(msg.errmsg, "filename must not be empty", "error message for empty filename is correct"); + }); + + // Try to download to an absolute path. + const absolutePath = OS.Path.join(WINDOWS ? "\\tmp" : "/tmp", "file_download.txt"); + yield download({ + url: FILE_URL, + filename: absolutePath, + }).then(msg => { + equal(msg.status, "error", "downloads.download() fails with absolute filename"); + equal(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`); + }); + + if (WINDOWS) { + yield download({ + url: FILE_URL, + filename: "C:\\file_download.txt", + }).then(msg => { + equal(msg.status, "error", "downloads.download() fails with absolute filename"); + equal(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct"); + }); + } + + // Try to download to a relative path containing .. + yield download({ + url: FILE_URL, + filename: OS.Path.join("..", "file_download.txt"), + }).then(msg => { + equal(msg.status, "error", "downloads.download() fails with back-references"); + equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct"); + }); + + // Try to download to a long relative path containing .. + yield download({ + url: FILE_URL, + filename: OS.Path.join("foo", "..", "..", "file_download.txt"), + }).then(msg => { + equal(msg.status, "error", "downloads.download() fails with back-references"); + equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct"); + }); + + // Try to download a blob url + const BLOB_STRING = "Hello, world"; + yield testDownload({ + blobme: [BLOB_STRING], + filename: FILE_NAME, + }, FILE_NAME, BLOB_STRING.length, "blob url"); + extension.sendMessage("killTheBlob"); + + // Try to download a blob url without a given filename + yield testDownload({ + blobme: [BLOB_STRING], + }, "download", BLOB_STRING.length, "blob url with no filename"); + extension.sendMessage("killTheBlob"); + + yield extension.unload(); +}); + +add_task(function* test_download_post() { + const server = createHttpServer(); + const url = `http://localhost:${server.identity.primaryPort}/post-log`; + + let received; + server.registerPathHandler("/post-log", request => { + received = request; + }); + + // Confirm received vs. expected values. + function confirm(method, headers = {}, body) { + equal(received.method, method, "method is correct"); + + for (let name in headers) { + ok(received.hasHeader(name), `header ${name} received`); + equal(received.getHeader(name), headers[name], `header ${name} is correct`); + } + + if (body) { + const str = NetUtil.readInputStreamToString(received.bodyInputStream, + received.bodyInputStream.available()); + equal(str, body, "body is correct"); + } + } + + function background() { + browser.test.onMessage.addListener(async options => { + try { + await browser.downloads.download(options); + } catch (err) { + browser.test.sendMessage("done", {err: err.message}); + } + }); + browser.downloads.onChanged.addListener(({state}) => { + if (state && state.current === "complete") { + browser.test.sendMessage("done", {ok: true}); + } + }); + } + + const manifest = {permissions: ["downloads"]}; + const extension = ExtensionTestUtils.loadExtension({background, manifest}); + yield extension.startup(); + + function download(options) { + options.url = url; + options.conflictAction = "overwrite"; + + extension.sendMessage(options); + return extension.awaitMessage("done"); + } + + // Test method option. + let result = yield download({}); + ok(result.ok, "download works without the method option, defaults to GET"); + confirm("GET"); + + result = yield download({method: "PUT"}); + ok(!result.ok, "download rejected with PUT method"); + ok(/method: Invalid enumeration/.test(result.err), "descriptive error message"); + + result = yield download({method: "POST"}); + ok(result.ok, "download works with POST method"); + confirm("POST"); + + // Test body option values. + result = yield download({body: []}); + ok(!result.ok, "download rejected because of non-string body"); + ok(/body: Expected string/.test(result.err), "descriptive error message"); + + result = yield download({method: "POST", body: "of work"}); + ok(result.ok, "download works with POST method and body"); + confirm("POST", {"Content-Length": 7}, "of work"); + + // Test custom headers. + result = yield download({headers: [{name: "X-Custom"}]}); + ok(!result.ok, "download rejected because of missing header value"); + ok(/"value" is required/.test(result.err), "descriptive error message"); + + result = yield download({headers: [{name: "X-Custom", value: "13"}]}); + ok(result.ok, "download works with a custom header"); + confirm("GET", {"X-Custom": "13"}); + + // Test forbidden headers. + result = yield download({headers: [{name: "DNT", value: "1"}]}); + ok(!result.ok, "download rejected because of forbidden header name DNT"); + ok(/Forbidden request header/.test(result.err), "descriptive error message"); + + result = yield download({headers: [{name: "Proxy-Connection", value: "keep"}]}); + ok(!result.ok, "download rejected because of forbidden header name prefix Proxy-"); + ok(/Forbidden request header/.test(result.err), "descriptive error message"); + + result = yield download({headers: [{name: "Sec-ret", value: "13"}]}); + ok(!result.ok, "download rejected because of forbidden header name prefix Sec-"); + ok(/Forbidden request header/.test(result.err), "descriptive error message"); + + remove("post-log"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_misc.js b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_misc.js new file mode 100644 index 000000000..d08aab666 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_misc.js @@ -0,0 +1,862 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Cu.import("resource://gre/modules/Downloads.jsm"); + +const server = createHttpServer(); +server.registerDirectory("/data/", do_get_file("data")); + +const ROOT = `http://localhost:${server.identity.primaryPort}`; +const BASE = `${ROOT}/data`; +const TXT_FILE = "file_download.txt"; +const TXT_URL = BASE + "/" + TXT_FILE; + +// Keep these in sync with code in interruptible.sjs +const INT_PARTIAL_LEN = 15; +const INT_TOTAL_LEN = 31; + +const TEST_DATA = "This is 31 bytes of sample data"; +const TOTAL_LEN = TEST_DATA.length; +const PARTIAL_LEN = 15; + +// A handler to let us systematically test pausing/resuming/canceling +// of downloads. This target represents a small text file but a simple +// GET will stall after sending part of the data, to give the test code +// a chance to pause or do other operations on an in-progress download. +// A resumed download (ie, a GET with a Range: header) will allow the +// download to complete. +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + + if (request.hasHeader("Range")) { + let start, end; + let matches = request.getHeader("Range") + .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/); + if (matches != null) { + start = matches[1] ? parseInt(matches[1], 10) : 0; + end = matches[2] ? parseInt(matches[2], 10) : (TOTAL_LEN - 1); + } + + if (end == undefined || end >= TOTAL_LEN) { + response.setStatusLine(request.httpVersion, 416, "Requested Range Not Satisfiable"); + response.setHeader("Content-Range", `*/${TOTAL_LEN}`, false); + response.finish(); + return; + } + + response.setStatusLine(request.httpVersion, 206, "Partial Content"); + response.setHeader("Content-Range", `${start}-${end}/${TOTAL_LEN}`, false); + response.write(TEST_DATA.slice(start, end + 1)); + } else { + response.processAsync(); + response.setHeader("Content-Length", `${TOTAL_LEN}`, false); + response.write(TEST_DATA.slice(0, PARTIAL_LEN)); + } + + do_register_cleanup(() => { + try { + response.finish(); + } catch (e) { + // This will throw, but we don't care at this point. + } + }); +} + +server.registerPathHandler("/interruptible.html", handleRequest); + +let interruptibleCount = 0; +function getInterruptibleUrl() { + let n = interruptibleCount++; + return `${ROOT}/interruptible.html?count=${n}`; +} + +function backgroundScript() { + let events = new Set(); + let eventWaiter = null; + + browser.downloads.onCreated.addListener(data => { + events.add({type: "onCreated", data}); + if (eventWaiter) { + eventWaiter(); + } + }); + + browser.downloads.onChanged.addListener(data => { + events.add({type: "onChanged", data}); + if (eventWaiter) { + eventWaiter(); + } + }); + + browser.downloads.onErased.addListener(data => { + events.add({type: "onErased", data}); + if (eventWaiter) { + eventWaiter(); + } + }); + + // Returns a promise that will resolve when the given list of expected + // events have all been seen. By default, succeeds only if the exact list + // of expected events is seen in the given order. options.exact can be + // set to false to allow other events and options.inorder can be set to + // false to allow the events to arrive in any order. + function waitForEvents(expected, options = {}) { + function compare(a, b) { + if (typeof b == "object" && b != null) { + if (typeof a != "object") { + return false; + } + return Object.keys(b).every(fld => compare(a[fld], b[fld])); + } + return (a == b); + } + + const exact = ("exact" in options) ? options.exact : true; + const inorder = ("inorder" in options) ? options.inorder : true; + return new Promise((resolve, reject) => { + function check() { + function fail(msg) { + browser.test.fail(msg); + reject(new Error(msg)); + } + if (events.size < expected.length) { + return; + } + if (exact && expected.length < events.size) { + fail(`Got ${events.size} events but only expected ${expected.length}`); + return; + } + + let remaining = new Set(events); + if (inorder) { + for (let event of events) { + if (compare(event, expected[0])) { + expected.shift(); + remaining.delete(event); + } + } + } else { + expected = expected.filter(val => { + for (let remainingEvent of remaining) { + if (compare(remainingEvent, val)) { + remaining.delete(remainingEvent); + return false; + } + } + return true; + }); + } + + // Events that did occur have been removed from expected so if + // expected is empty, we're done. If we didn't see all the + // expected events and we're not looking for an exact match, + // then we just may not have seen the event yet, so return without + // failing and check() will be called again when a new event arrives. + if (expected.length == 0) { + events = remaining; + eventWaiter = null; + resolve(); + } else if (exact) { + fail(`Mismatched event: expecting ${JSON.stringify(expected[0])} but got ${JSON.stringify(Array.from(remaining)[0])}`); + } + } + eventWaiter = check; + check(); + }); + } + + browser.test.onMessage.addListener(async (msg, ...args) => { + let match = msg.match(/(\w+).request$/); + if (!match) { + return; + } + + let what = match[1]; + if (what == "waitForEvents") { + try { + await waitForEvents(...args); + browser.test.sendMessage("waitForEvents.done", {status: "success"}); + } catch (error) { + browser.test.sendMessage("waitForEvents.done", {status: "error", errmsg: error.message}); + } + } else if (what == "clearEvents") { + events = new Set(); + browser.test.sendMessage("clearEvents.done", {status: "success"}); + } else { + try { + let result = await browser.downloads[what](...args); + browser.test.sendMessage(`${what}.done`, {status: "success", result}); + } catch (error) { + browser.test.sendMessage(`${what}.done`, {status: "error", errmsg: error.message}); + } + } + }); + + browser.test.sendMessage("ready"); +} + +let downloadDir; +let extension; + +async function clearDownloads(callback) { + let list = await Downloads.getList(Downloads.ALL); + let downloads = await list.getAll(); + + await Promise.all(downloads.map(download => list.remove(download))); + + return downloads; +} + +function runInExtension(what, ...args) { + extension.sendMessage(`${what}.request`, ...args); + return extension.awaitMessage(`${what}.done`); +} + +// This is pretty simplistic, it looks for a progress update for a +// download of the given url in which the total bytes are exactly equal +// to the given value. Unless you know exactly how data will arrive from +// the server (eg see interruptible.sjs), it probably isn't very useful. +async function waitForProgress(url, bytes) { + let list = await Downloads.getList(Downloads.ALL); + + return new Promise(resolve => { + const view = { + onDownloadChanged(download) { + if (download.source.url == url && download.currentBytes == bytes) { + list.removeView(view); + resolve(); + } + }, + }; + list.addView(view); + }); +} + +add_task(function* setup() { + const nsIFile = Ci.nsIFile; + downloadDir = FileUtils.getDir("TmpD", ["downloads"]); + downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_print(`downloadDir ${downloadDir.path}`); + + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir); + + do_register_cleanup(() => { + Services.prefs.clearUserPref("browser.download.folderList"); + Services.prefs.clearUserPref("browser.download.dir"); + downloadDir.remove(true); + + return clearDownloads(); + }); + + yield clearDownloads().then(downloads => { + do_print(`removed ${downloads.length} pre-existing downloads from history`); + }); + + extension = ExtensionTestUtils.loadExtension({ + background: backgroundScript, + manifest: { + permissions: ["downloads"], + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("ready"); +}); + +add_task(function* test_events() { + let msg = yield runInExtension("download", {url: TXT_URL}); + equal(msg.status, "success", "download() succeeded"); + const id = msg.result; + + msg = yield runInExtension("waitForEvents", [ + {type: "onCreated", data: {id, url: TXT_URL}}, + { + type: "onChanged", + data: { + id, + state: { + previous: "in_progress", + current: "complete", + }, + }, + }, + ]); + equal(msg.status, "success", "got onCreated and onChanged events"); +}); + +add_task(function* test_cancel() { + let url = getInterruptibleUrl(); + do_print(url); + let msg = yield runInExtension("download", {url}); + equal(msg.status, "success", "download() succeeded"); + const id = msg.result; + + let progressPromise = waitForProgress(url, INT_PARTIAL_LEN); + + msg = yield runInExtension("waitForEvents", [ + {type: "onCreated", data: {id}}, + ]); + equal(msg.status, "success", "got created and changed events"); + + yield progressPromise; + do_print(`download reached ${INT_PARTIAL_LEN} bytes`); + + msg = yield runInExtension("cancel", id); + equal(msg.status, "success", "cancel() succeeded"); + + // This sequence of events is bogus (bug 1256243) + msg = yield runInExtension("waitForEvents", [ + { + type: "onChanged", + data: { + state: { + previous: "in_progress", + current: "interrupted", + }, + paused: { + previous: false, + current: true, + }, + }, + }, { + type: "onChanged", + data: { + id, + error: { + previous: null, + current: "USER_CANCELED", + }, + }, + }, { + type: "onChanged", + data: { + id, + paused: { + previous: true, + current: false, + }, + }, + }, + ]); + equal(msg.status, "success", "got onChanged events corresponding to cancel()"); + + msg = yield runInExtension("search", {error: "USER_CANCELED"}); + equal(msg.status, "success", "search() succeeded"); + equal(msg.result.length, 1, "search() found 1 download"); + equal(msg.result[0].id, id, "download.id is correct"); + equal(msg.result[0].state, "interrupted", "download.state is correct"); + equal(msg.result[0].paused, false, "download.paused is correct"); + equal(msg.result[0].canResume, false, "download.canResume is correct"); + equal(msg.result[0].error, "USER_CANCELED", "download.error is correct"); + equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); + equal(msg.result[0].exists, false, "download.exists is correct"); + + msg = yield runInExtension("pause", id); + equal(msg.status, "error", "cannot pause a canceled download"); + + msg = yield runInExtension("resume", id); + equal(msg.status, "error", "cannot resume a canceled download"); +}); + +add_task(function* test_pauseresume() { + let url = getInterruptibleUrl(); + let msg = yield runInExtension("download", {url}); + equal(msg.status, "success", "download() succeeded"); + const id = msg.result; + + let progressPromise = waitForProgress(url, INT_PARTIAL_LEN); + + msg = yield runInExtension("waitForEvents", [ + {type: "onCreated", data: {id}}, + ]); + equal(msg.status, "success", "got created and changed events"); + + yield progressPromise; + do_print(`download reached ${INT_PARTIAL_LEN} bytes`); + + msg = yield runInExtension("pause", id); + equal(msg.status, "success", "pause() succeeded"); + + msg = yield runInExtension("waitForEvents", [ + { + type: "onChanged", + data: { + id, + state: { + previous: "in_progress", + current: "interrupted", + }, + paused: { + previous: false, + current: true, + }, + canResume: { + previous: false, + current: true, + }, + }, + }, { + type: "onChanged", + data: { + id, + error: { + previous: null, + current: "USER_CANCELED", + }, + }, + }]); + equal(msg.status, "success", "got onChanged event corresponding to pause"); + + msg = yield runInExtension("search", {paused: true}); + equal(msg.status, "success", "search() succeeded"); + equal(msg.result.length, 1, "search() found 1 download"); + equal(msg.result[0].id, id, "download.id is correct"); + equal(msg.result[0].state, "interrupted", "download.state is correct"); + equal(msg.result[0].paused, true, "download.paused is correct"); + equal(msg.result[0].canResume, true, "download.canResume is correct"); + equal(msg.result[0].error, "USER_CANCELED", "download.error is correct"); + equal(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct"); + equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); + equal(msg.result[0].exists, false, "download.exists is correct"); + + msg = yield runInExtension("search", {error: "USER_CANCELED"}); + equal(msg.status, "success", "search() succeeded"); + let found = msg.result.filter(item => item.id == id); + equal(found.length, 1, "search() by error found the paused download"); + + msg = yield runInExtension("pause", id); + equal(msg.status, "error", "cannot pause an already paused download"); + + msg = yield runInExtension("resume", id); + equal(msg.status, "success", "resume() succeeded"); + + msg = yield runInExtension("waitForEvents", [ + { + type: "onChanged", + data: { + id, + state: { + previous: "interrupted", + current: "in_progress", + }, + paused: { + previous: true, + current: false, + }, + canResume: { + previous: true, + current: false, + }, + error: { + previous: "USER_CANCELED", + current: null, + }, + }, + }, + { + type: "onChanged", + data: { + id, + state: { + previous: "in_progress", + current: "complete", + }, + }, + }, + ]); + equal(msg.status, "success", "got onChanged events for resume and complete"); + + msg = yield runInExtension("search", {id}); + equal(msg.status, "success", "search() succeeded"); + equal(msg.result.length, 1, "search() found 1 download"); + equal(msg.result[0].state, "complete", "download.state is correct"); + equal(msg.result[0].paused, false, "download.paused is correct"); + equal(msg.result[0].canResume, false, "download.canResume is correct"); + equal(msg.result[0].error, null, "download.error is correct"); + equal(msg.result[0].bytesReceived, INT_TOTAL_LEN, "download.bytesReceived is correct"); + equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); + equal(msg.result[0].exists, true, "download.exists is correct"); + + msg = yield runInExtension("pause", id); + equal(msg.status, "error", "cannot pause a completed download"); + + msg = yield runInExtension("resume", id); + equal(msg.status, "error", "cannot resume a completed download"); +}); + +add_task(function* test_pausecancel() { + let url = getInterruptibleUrl(); + let msg = yield runInExtension("download", {url}); + equal(msg.status, "success", "download() succeeded"); + const id = msg.result; + + let progressPromise = waitForProgress(url, INT_PARTIAL_LEN); + + msg = yield runInExtension("waitForEvents", [ + {type: "onCreated", data: {id}}, + ]); + equal(msg.status, "success", "got created and changed events"); + + yield progressPromise; + do_print(`download reached ${INT_PARTIAL_LEN} bytes`); + + msg = yield runInExtension("pause", id); + equal(msg.status, "success", "pause() succeeded"); + + msg = yield runInExtension("waitForEvents", [ + { + type: "onChanged", + data: { + id, + state: { + previous: "in_progress", + current: "interrupted", + }, + paused: { + previous: false, + current: true, + }, + canResume: { + previous: false, + current: true, + }, + }, + }, { + type: "onChanged", + data: { + id, + error: { + previous: null, + current: "USER_CANCELED", + }, + }, + }]); + equal(msg.status, "success", "got onChanged event corresponding to pause"); + + msg = yield runInExtension("search", {paused: true}); + equal(msg.status, "success", "search() succeeded"); + equal(msg.result.length, 1, "search() found 1 download"); + equal(msg.result[0].id, id, "download.id is correct"); + equal(msg.result[0].state, "interrupted", "download.state is correct"); + equal(msg.result[0].paused, true, "download.paused is correct"); + equal(msg.result[0].canResume, true, "download.canResume is correct"); + equal(msg.result[0].error, "USER_CANCELED", "download.error is correct"); + equal(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct"); + equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); + equal(msg.result[0].exists, false, "download.exists is correct"); + + msg = yield runInExtension("search", {error: "USER_CANCELED"}); + equal(msg.status, "success", "search() succeeded"); + let found = msg.result.filter(item => item.id == id); + equal(found.length, 1, "search() by error found the paused download"); + + msg = yield runInExtension("cancel", id); + equal(msg.status, "success", "cancel() succeeded"); + + msg = yield runInExtension("waitForEvents", [ + { + type: "onChanged", + data: { + id, + paused: { + previous: true, + current: false, + }, + canResume: { + previous: true, + current: false, + }, + }, + }, + ]); + equal(msg.status, "success", "got onChanged event for cancel"); + + msg = yield runInExtension("search", {id}); + equal(msg.status, "success", "search() succeeded"); + equal(msg.result.length, 1, "search() found 1 download"); + equal(msg.result[0].state, "interrupted", "download.state is correct"); + equal(msg.result[0].paused, false, "download.paused is correct"); + equal(msg.result[0].canResume, false, "download.canResume is correct"); + equal(msg.result[0].error, "USER_CANCELED", "download.error is correct"); + equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct"); + equal(msg.result[0].exists, false, "download.exists is correct"); +}); + +add_task(function* test_pause_resume_cancel_badargs() { + let BAD_ID = 1000; + + let msg = yield runInExtension("pause", BAD_ID); + equal(msg.status, "error", "pause() failed with a bad download id"); + ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive"); + + msg = yield runInExtension("resume", BAD_ID); + equal(msg.status, "error", "resume() failed with a bad download id"); + ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive"); + + msg = yield runInExtension("cancel", BAD_ID); + equal(msg.status, "error", "cancel() failed with a bad download id"); + ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive"); +}); + +add_task(function* test_file_removal() { + let msg = yield runInExtension("download", {url: TXT_URL}); + equal(msg.status, "success", "download() succeeded"); + const id = msg.result; + + msg = yield runInExtension("waitForEvents", [ + {type: "onCreated", data: {id, url: TXT_URL}}, + { + type: "onChanged", + data: { + id, + state: { + previous: "in_progress", + current: "complete", + }, + }, + }, + ]); + + equal(msg.status, "success", "got onCreated and onChanged events"); + + msg = yield runInExtension("removeFile", id); + equal(msg.status, "success", "removeFile() succeeded"); + + msg = yield runInExtension("removeFile", id); + equal(msg.status, "error", "removeFile() fails since the file was already removed."); + ok(/file doesn't exist/.test(msg.errmsg), "removeFile() failed on removed file."); + + msg = yield runInExtension("removeFile", 1000); + ok(/Invalid download id/.test(msg.errmsg), "removeFile() failed due to non-existent id"); +}); + +add_task(function* test_removal_of_incomplete_download() { + let url = getInterruptibleUrl(); + let msg = yield runInExtension("download", {url}); + equal(msg.status, "success", "download() succeeded"); + const id = msg.result; + + let progressPromise = waitForProgress(url, INT_PARTIAL_LEN); + + msg = yield runInExtension("waitForEvents", [ + {type: "onCreated", data: {id}}, + ]); + equal(msg.status, "success", "got created and changed events"); + + yield progressPromise; + do_print(`download reached ${INT_PARTIAL_LEN} bytes`); + + msg = yield runInExtension("pause", id); + equal(msg.status, "success", "pause() succeeded"); + + msg = yield runInExtension("waitForEvents", [ + { + type: "onChanged", + data: { + id, + state: { + previous: "in_progress", + current: "interrupted", + }, + paused: { + previous: false, + current: true, + }, + canResume: { + previous: false, + current: true, + }, + }, + }, { + type: "onChanged", + data: { + id, + error: { + previous: null, + current: "USER_CANCELED", + }, + }, + }]); + equal(msg.status, "success", "got onChanged event corresponding to pause"); + + msg = yield runInExtension("removeFile", id); + equal(msg.status, "error", "removeFile() on paused download failed"); + + ok(/Cannot remove incomplete download/.test(msg.errmsg), "removeFile() failed due to download being incomplete"); + + msg = yield runInExtension("resume", id); + equal(msg.status, "success", "resume() succeeded"); + + msg = yield runInExtension("waitForEvents", [ + { + type: "onChanged", + data: { + id, + state: { + previous: "interrupted", + current: "in_progress", + }, + paused: { + previous: true, + current: false, + }, + canResume: { + previous: true, + current: false, + }, + error: { + previous: "USER_CANCELED", + current: null, + }, + }, + }, + { + type: "onChanged", + data: { + id, + state: { + previous: "in_progress", + current: "complete", + }, + }, + }, + ]); + equal(msg.status, "success", "got onChanged events for resume and complete"); + + msg = yield runInExtension("removeFile", id); + equal(msg.status, "success", "removeFile() succeeded following completion of resumed download."); +}); + +// Test erase(). We don't do elaborate testing of the query handling +// since it uses the exact same engine as search() which is tested +// more thoroughly in test_chrome_ext_downloads_search.html +add_task(function* test_erase() { + yield clearDownloads(); + + yield runInExtension("clearEvents"); + + function* download() { + let msg = yield runInExtension("download", {url: TXT_URL}); + equal(msg.status, "success", "download succeeded"); + let id = msg.result; + + msg = yield runInExtension("waitForEvents", [{ + type: "onChanged", data: {id, state: {current: "complete"}}, + }], {exact: false}); + equal(msg.status, "success", "download finished"); + + return id; + } + + let ids = {}; + ids.dl1 = yield download(); + ids.dl2 = yield download(); + ids.dl3 = yield download(); + + let msg = yield runInExtension("search", {}); + equal(msg.status, "success", "search succeded"); + equal(msg.result.length, 3, "search found 3 downloads"); + + msg = yield runInExtension("clearEvents"); + + msg = yield runInExtension("erase", {id: ids.dl1}); + equal(msg.status, "success", "erase by id succeeded"); + + msg = yield runInExtension("waitForEvents", [ + {type: "onErased", data: ids.dl1}, + ]); + equal(msg.status, "success", "received onErased event"); + + msg = yield runInExtension("search", {}); + equal(msg.status, "success", "search succeded"); + equal(msg.result.length, 2, "search found 2 downloads"); + + msg = yield runInExtension("erase", {}); + equal(msg.status, "success", "erase everything succeeded"); + + msg = yield runInExtension("waitForEvents", [ + {type: "onErased", data: ids.dl2}, + {type: "onErased", data: ids.dl3}, + ], {inorder: false}); + equal(msg.status, "success", "received 2 onErased events"); + + msg = yield runInExtension("search", {}); + equal(msg.status, "success", "search succeded"); + equal(msg.result.length, 0, "search found 0 downloads"); +}); + +function loadImage(img, data) { + return new Promise((resolve) => { + img.src = data; + img.onload = resolve; + }); +} + +add_task(function* test_getFileIcon() { + let webNav = Services.appShell.createWindowlessBrowser(false); + let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell); + + let system = Services.scriptSecurityManager.getSystemPrincipal(); + docShell.createAboutBlankContentViewer(system); + + let img = webNav.document.createElement("img"); + + let msg = yield runInExtension("download", {url: TXT_URL}); + equal(msg.status, "success", "download() succeeded"); + const id = msg.result; + + msg = yield runInExtension("getFileIcon", id); + equal(msg.status, "success", "getFileIcon() succeeded"); + yield loadImage(img, msg.result); + equal(img.height, 32, "returns an icon with the right height"); + equal(img.width, 32, "returns an icon with the right width"); + + msg = yield runInExtension("waitForEvents", [ + {type: "onCreated", data: {id, url: TXT_URL}}, + {type: "onChanged"}, + ]); + equal(msg.status, "success", "got events"); + + msg = yield runInExtension("getFileIcon", id); + equal(msg.status, "success", "getFileIcon() succeeded"); + yield loadImage(img, msg.result); + equal(img.height, 32, "returns an icon with the right height after download"); + equal(img.width, 32, "returns an icon with the right width after download"); + + msg = yield runInExtension("getFileIcon", id + 100); + equal(msg.status, "error", "getFileIcon() failed"); + ok(msg.errmsg.includes("Invalid download id"), "download id is invalid"); + + msg = yield runInExtension("getFileIcon", id, {size: 127}); + equal(msg.status, "success", "getFileIcon() succeeded"); + yield loadImage(img, msg.result); + equal(img.height, 127, "returns an icon with the right custom height"); + equal(img.width, 127, "returns an icon with the right custom width"); + + msg = yield runInExtension("getFileIcon", id, {size: 1}); + equal(msg.status, "success", "getFileIcon() succeeded"); + yield loadImage(img, msg.result); + equal(img.height, 1, "returns an icon with the right custom height"); + equal(img.width, 1, "returns an icon with the right custom width"); + + msg = yield runInExtension("getFileIcon", id, {size: "foo"}); + equal(msg.status, "error", "getFileIcon() fails"); + ok(msg.errmsg.includes("Error processing size"), "size is not a number"); + + msg = yield runInExtension("getFileIcon", id, {size: 0}); + equal(msg.status, "error", "getFileIcon() fails"); + ok(msg.errmsg.includes("Error processing size"), "size is too small"); + + msg = yield runInExtension("getFileIcon", id, {size: 128}); + equal(msg.status, "error", "getFileIcon() fails"); + ok(msg.errmsg.includes("Error processing size"), "size is too big"); + + webNav.close(); +}); + +add_task(function* cleanup() { + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_search.js b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_search.js new file mode 100644 index 000000000..4caa82456 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_downloads_search.js @@ -0,0 +1,402 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Cu.import("resource://gre/modules/Downloads.jsm"); + +const server = createHttpServer(); +server.registerDirectory("/data/", do_get_file("data")); + +const BASE = `http://localhost:${server.identity.primaryPort}/data`; +const TXT_FILE = "file_download.txt"; +const TXT_URL = BASE + "/" + TXT_FILE; +const TXT_LEN = 46; +const HTML_FILE = "file_download.html"; +const HTML_URL = BASE + "/" + HTML_FILE; +const HTML_LEN = 117; +const BIG_LEN = 1000; // something bigger both TXT_LEN and HTML_LEN + +function backgroundScript() { + let complete = new Map(); + + function waitForComplete(id) { + if (complete.has(id)) { + return complete.get(id).promise; + } + + let promise = new Promise(resolve => { + complete.set(id, {resolve}); + }); + complete.get(id).promise = promise; + return promise; + } + + browser.downloads.onChanged.addListener(change => { + if (change.state && change.state.current == "complete") { + // Make sure we have a promise. + waitForComplete(change.id); + complete.get(change.id).resolve(); + } + }); + + browser.test.onMessage.addListener(async (msg, ...args) => { + if (msg == "download.request") { + try { + let id = await browser.downloads.download(args[0]); + browser.test.sendMessage("download.done", {status: "success", id}); + } catch (error) { + browser.test.sendMessage("download.done", {status: "error", errmsg: error.message}); + } + } else if (msg == "search.request") { + try { + let downloads = await browser.downloads.search(args[0]); + browser.test.sendMessage("search.done", {status: "success", downloads}); + } catch (error) { + browser.test.sendMessage("search.done", {status: "error", errmsg: error.message}); + } + } else if (msg == "waitForComplete.request") { + await waitForComplete(args[0]); + browser.test.sendMessage("waitForComplete.done"); + } + }); + + browser.test.sendMessage("ready"); +} + +async function clearDownloads(callback) { + let list = await Downloads.getList(Downloads.ALL); + let downloads = await list.getAll(); + + await Promise.all(downloads.map(download => list.remove(download))); + + return downloads; +} + +add_task(function* test_search() { + const nsIFile = Ci.nsIFile; + let downloadDir = FileUtils.getDir("TmpD", ["downloads"]); + downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_print(`downloadDir ${downloadDir.path}`); + + function downloadPath(filename) { + let path = downloadDir.clone(); + path.append(filename); + return path.path; + } + + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir); + + do_register_cleanup(async () => { + Services.prefs.clearUserPref("browser.download.folderList"); + Services.prefs.clearUserPref("browser.download.dir"); + await cleanupDir(downloadDir); + await clearDownloads(); + }); + + yield clearDownloads().then(downloads => { + do_print(`removed ${downloads.length} pre-existing downloads from history`); + }); + + let extension = ExtensionTestUtils.loadExtension({ + background: backgroundScript, + manifest: { + permissions: ["downloads"], + }, + }); + + async function download(options) { + extension.sendMessage("download.request", options); + let result = await extension.awaitMessage("download.done"); + + if (result.status == "success") { + do_print(`wait for onChanged event to indicate ${result.id} is complete`); + extension.sendMessage("waitForComplete.request", result.id); + + await extension.awaitMessage("waitForComplete.done"); + } + + return result; + } + + function search(query) { + extension.sendMessage("search.request", query); + return extension.awaitMessage("search.done"); + } + + yield extension.startup(); + yield extension.awaitMessage("ready"); + + // Do some downloads... + const time1 = new Date(); + + let downloadIds = {}; + let msg = yield download({url: TXT_URL}); + equal(msg.status, "success", "download() succeeded"); + downloadIds.txt1 = msg.id; + + const TXT_FILE2 = "NewFile.txt"; + msg = yield download({url: TXT_URL, filename: TXT_FILE2}); + equal(msg.status, "success", "download() succeeded"); + downloadIds.txt2 = msg.id; + + const time2 = new Date(); + + msg = yield download({url: HTML_URL}); + equal(msg.status, "success", "download() succeeded"); + downloadIds.html1 = msg.id; + + const HTML_FILE2 = "renamed.html"; + msg = yield download({url: HTML_URL, filename: HTML_FILE2}); + equal(msg.status, "success", "download() succeeded"); + downloadIds.html2 = msg.id; + + const time3 = new Date(); + + // Search for each individual download and check + // the corresponding DownloadItem. + function* checkDownloadItem(id, expect) { + let item = yield search({id}); + equal(item.status, "success", "search() succeeded"); + equal(item.downloads.length, 1, "search() found exactly 1 download"); + + Object.keys(expect).forEach(function(field) { + equal(item.downloads[0][field], expect[field], `DownloadItem.${field} is correct"`); + }); + } + yield checkDownloadItem(downloadIds.txt1, { + url: TXT_URL, + filename: downloadPath(TXT_FILE), + mime: "text/plain", + state: "complete", + bytesReceived: TXT_LEN, + totalBytes: TXT_LEN, + fileSize: TXT_LEN, + exists: true, + }); + + yield checkDownloadItem(downloadIds.txt2, { + url: TXT_URL, + filename: downloadPath(TXT_FILE2), + mime: "text/plain", + state: "complete", + bytesReceived: TXT_LEN, + totalBytes: TXT_LEN, + fileSize: TXT_LEN, + exists: true, + }); + + yield checkDownloadItem(downloadIds.html1, { + url: HTML_URL, + filename: downloadPath(HTML_FILE), + mime: "text/html", + state: "complete", + bytesReceived: HTML_LEN, + totalBytes: HTML_LEN, + fileSize: HTML_LEN, + exists: true, + }); + + yield checkDownloadItem(downloadIds.html2, { + url: HTML_URL, + filename: downloadPath(HTML_FILE2), + mime: "text/html", + state: "complete", + bytesReceived: HTML_LEN, + totalBytes: HTML_LEN, + fileSize: HTML_LEN, + exists: true, + }); + + function* checkSearch(query, expected, description, exact) { + let item = yield search(query); + equal(item.status, "success", "search() succeeded"); + equal(item.downloads.length, expected.length, `search() for ${description} found exactly ${expected.length} downloads`); + + let receivedIds = item.downloads.map(i => i.id); + if (exact) { + receivedIds.forEach((id, idx) => { + equal(id, downloadIds[expected[idx]], `search() for ${description} returned ${expected[idx]} in position ${idx}`); + }); + } else { + Object.keys(downloadIds).forEach(key => { + const id = downloadIds[key]; + const thisExpected = expected.includes(key); + equal(receivedIds.includes(id), thisExpected, + `search() for ${description} ${thisExpected ? "includes" : "does not include"} ${key}`); + }); + } + } + + // Check that search with an invalid id returns nothing. + // NB: for now ids are not persistent and we start numbering them at 1 + // so a sufficiently large number will be unused. + const INVALID_ID = 1000; + yield checkSearch({id: INVALID_ID}, [], "invalid id"); + + // Check that search on url works. + yield checkSearch({url: TXT_URL}, ["txt1", "txt2"], "url"); + + // Check that regexp on url works. + const HTML_REGEX = "[downlad]{8}\.html+$"; + yield checkSearch({urlRegex: HTML_REGEX}, ["html1", "html2"], "url regexp"); + + // Check that compatible url+regexp works + yield checkSearch({url: HTML_URL, urlRegex: HTML_REGEX}, ["html1", "html2"], "compatible url+urlRegex"); + + // Check that incompatible url+regexp works + yield checkSearch({url: TXT_URL, urlRegex: HTML_REGEX}, [], "incompatible url+urlRegex"); + + // Check that search on filename works. + yield checkSearch({filename: downloadPath(TXT_FILE)}, ["txt1"], "filename"); + + // Check that regexp on filename works. + yield checkSearch({filenameRegex: HTML_REGEX}, ["html1"], "filename regex"); + + // Check that compatible filename+regexp works + yield checkSearch({filename: downloadPath(HTML_FILE), filenameRegex: HTML_REGEX}, ["html1"], "compatible filename+filename regex"); + + // Check that incompatible filename+regexp works + yield checkSearch({filename: downloadPath(TXT_FILE), filenameRegex: HTML_REGEX}, [], "incompatible filename+filename regex"); + + // Check that simple positive search terms work. + yield checkSearch({query: ["file_download"]}, ["txt1", "txt2", "html1", "html2"], + "term file_download"); + yield checkSearch({query: ["NewFile"]}, ["txt2"], "term NewFile"); + + // Check that positive search terms work case-insensitive. + yield checkSearch({query: ["nEwfILe"]}, ["txt2"], "term nEwfiLe"); + + // Check that negative search terms work. + yield checkSearch({query: ["-txt"]}, ["html1", "html2"], "term -txt"); + + // Check that positive and negative search terms together work. + yield checkSearch({query: ["html", "-renamed"]}, ["html1"], "postive and negative terms"); + + function* checkSearchWithDate(query, expected, description) { + const fields = Object.keys(query); + if (fields.length != 1 || !(query[fields[0]] instanceof Date)) { + throw new Error("checkSearchWithDate expects exactly one Date field"); + } + const field = fields[0]; + const date = query[field]; + + let newquery = {}; + + // Check as a Date + newquery[field] = date; + yield checkSearch(newquery, expected, `${description} as Date`); + + // Check as numeric milliseconds + newquery[field] = date.valueOf(); + yield checkSearch(newquery, expected, `${description} as numeric ms`); + + // Check as stringified milliseconds + newquery[field] = date.valueOf().toString(); + yield checkSearch(newquery, expected, `${description} as string ms`); + + // Check as ISO string + newquery[field] = date.toISOString(); + yield checkSearch(newquery, expected, `${description} as iso string`); + } + + // Check startedBefore + yield checkSearchWithDate({startedBefore: time1}, [], "before time1"); + yield checkSearchWithDate({startedBefore: time2}, ["txt1", "txt2"], "before time2"); + yield checkSearchWithDate({startedBefore: time3}, ["txt1", "txt2", "html1", "html2"], "before time3"); + + // Check startedAfter + yield checkSearchWithDate({startedAfter: time1}, ["txt1", "txt2", "html1", "html2"], "after time1"); + yield checkSearchWithDate({startedAfter: time2}, ["html1", "html2"], "after time2"); + yield checkSearchWithDate({startedAfter: time3}, [], "after time3"); + + // Check simple search on totalBytes + yield checkSearch({totalBytes: TXT_LEN}, ["txt1", "txt2"], "totalBytes"); + yield checkSearch({totalBytes: HTML_LEN}, ["html1", "html2"], "totalBytes"); + + // Check simple test on totalBytes{Greater,Less} + // (NB: TXT_LEN < HTML_LEN < BIG_LEN) + yield checkSearch({totalBytesGreater: 0}, ["txt1", "txt2", "html1", "html2"], "totalBytesGreater than 0"); + yield checkSearch({totalBytesGreater: TXT_LEN}, ["html1", "html2"], `totalBytesGreater than ${TXT_LEN}`); + yield checkSearch({totalBytesGreater: HTML_LEN}, [], `totalBytesGreater than ${HTML_LEN}`); + yield checkSearch({totalBytesLess: TXT_LEN}, [], `totalBytesLess than ${TXT_LEN}`); + yield checkSearch({totalBytesLess: HTML_LEN}, ["txt1", "txt2"], `totalBytesLess than ${HTML_LEN}`); + yield checkSearch({totalBytesLess: BIG_LEN}, ["txt1", "txt2", "html1", "html2"], `totalBytesLess than ${BIG_LEN}`); + + // Check good combinations of totalBytes*. + yield checkSearch({totalBytes: HTML_LEN, totalBytesGreater: TXT_LEN}, ["html1", "html2"], "totalBytes and totalBytesGreater"); + yield checkSearch({totalBytes: TXT_LEN, totalBytesLess: HTML_LEN}, ["txt1", "txt2"], "totalBytes and totalBytesGreater"); + yield checkSearch({totalBytes: HTML_LEN, totalBytesLess: BIG_LEN, totalBytesGreater: 0}, ["html1", "html2"], "totalBytes and totalBytesLess and totalBytesGreater"); + + // Check bad combination of totalBytes*. + yield checkSearch({totalBytesLess: TXT_LEN, totalBytesGreater: HTML_LEN}, [], "bad totalBytesLess, totalBytesGreater combination"); + yield checkSearch({totalBytes: TXT_LEN, totalBytesGreater: HTML_LEN}, [], "bad totalBytes, totalBytesGreater combination"); + yield checkSearch({totalBytes: HTML_LEN, totalBytesLess: TXT_LEN}, [], "bad totalBytes, totalBytesLess combination"); + + // Check mime. + yield checkSearch({mime: "text/plain"}, ["txt1", "txt2"], "mime text/plain"); + yield checkSearch({mime: "text/html"}, ["html1", "html2"], "mime text/htmlplain"); + yield checkSearch({mime: "video/webm"}, [], "mime video/webm"); + + // Check fileSize. + yield checkSearch({fileSize: TXT_LEN}, ["txt1", "txt2"], "fileSize"); + yield checkSearch({fileSize: HTML_LEN}, ["html1", "html2"], "fileSize"); + + // Fields like bytesReceived, paused, state, exists are meaningful + // for downloads that are in progress but have not yet completed. + // todo: add tests for these when we have better support for in-progress + // downloads (e.g., after pause(), resume() and cancel() are implemented) + + // Check multiple query properties. + // We could make this testing arbitrarily complicated... + // We already tested combining fields with obvious interactions above + // (e.g., filename and filenameRegex or startTime and startedBefore/After) + // so now just throw as many fields as we can at a single search and + // make sure a simple case still works. + yield checkSearch({ + url: TXT_URL, + urlRegex: "download", + filename: downloadPath(TXT_FILE), + filenameRegex: "download", + query: ["download"], + startedAfter: time1.valueOf().toString(), + startedBefore: time2.valueOf().toString(), + totalBytes: TXT_LEN, + totalBytesGreater: 0, + totalBytesLess: BIG_LEN, + mime: "text/plain", + fileSize: TXT_LEN, + }, ["txt1"], "many properties"); + + // Check simple orderBy (forward and backward). + yield checkSearch({orderBy: ["startTime"]}, ["txt1", "txt2", "html1", "html2"], "orderBy startTime", true); + yield checkSearch({orderBy: ["-startTime"]}, ["html2", "html1", "txt2", "txt1"], "orderBy -startTime", true); + + // Check orderBy with multiple fields. + // NB: TXT_URL and HTML_URL differ only in extension and .html precedes .txt + yield checkSearch({orderBy: ["url", "-startTime"]}, ["html2", "html1", "txt2", "txt1"], "orderBy with multiple fields", true); + + // Check orderBy with limit. + yield checkSearch({orderBy: ["url"], limit: 1}, ["html1"], "orderBy with limit", true); + + // Check bad arguments. + function* checkBadSearch(query, pattern, description) { + let item = yield search(query); + equal(item.status, "error", "search() failed"); + ok(pattern.test(item.errmsg), `error message for ${description} was correct (${item.errmsg}).`); + } + + yield checkBadSearch("myquery", /Incorrect argument type/, "query is not an object"); + yield checkBadSearch({bogus: "boo"}, /Unexpected property/, "query contains an unknown field"); + yield checkBadSearch({query: "query string"}, /Expected array/, "query.query is a string"); + yield checkBadSearch({startedBefore: "i am not a time"}, /Type error/, "query.startedBefore is not a valid time"); + yield checkBadSearch({startedAfter: "i am not a time"}, /Type error/, "query.startedAfter is not a valid time"); + yield checkBadSearch({endedBefore: "i am not a time"}, /Type error/, "query.endedBefore is not a valid time"); + yield checkBadSearch({endedAfter: "i am not a time"}, /Type error/, "query.endedAfter is not a valid time"); + yield checkBadSearch({urlRegex: "["}, /Invalid urlRegex/, "query.urlRegexp is not a valid regular expression"); + yield checkBadSearch({filenameRegex: "["}, /Invalid filenameRegex/, "query.filenameRegexp is not a valid regular expression"); + yield checkBadSearch({orderBy: "startTime"}, /Expected array/, "query.orderBy is not an array"); + yield checkBadSearch({orderBy: ["bogus"]}, /Invalid orderBy field/, "query.orderBy references a non-existent field"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_experiments.js b/toolkit/components/webextensions/test/xpcshell/test_ext_experiments.js new file mode 100644 index 000000000..bc6bfcd68 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_experiments.js @@ -0,0 +1,175 @@ +"use strict"; + +/* globals browser */ + +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); + +function promiseAddonStartup() { + const {Management} = Cu.import("resource://gre/modules/Extension.jsm"); + + return new Promise(resolve => { + let listener = (evt, extension) => { + Management.off("startup", listener); + resolve(extension); + }; + + Management.on("startup", listener); + }); +} + +add_task(function* setup() { + yield ExtensionTestUtils.startAddonManager(); +}); + +add_task(function* test_experiments_api() { + let apiAddonFile = Extension.generateZipFile({ + "install.rdf": ` + + + + + + + + + `, + + "api.js": String.raw` + Components.utils.import("resource://gre/modules/Services.jsm"); + + Services.obs.notifyObservers(null, "webext-api-loaded", ""); + + class API extends ExtensionAPI { + getAPI(context) { + return { + meh: { + hello(text) { + Services.obs.notifyObservers(null, "webext-api-hello", text); + } + } + } + } + } + `, + + "schema.json": [ + { + "namespace": "meh", + "description": "All full of meh.", + "permissions": ["experiments.meh"], + "functions": [ + { + "name": "hello", + "type": "function", + "description": "Hates you. This is all.", + "parameters": [ + {"type": "string", "name": "text"}, + ], + }, + ], + }, + ], + }); + + let addonFile = Extension.generateXPI({ + manifest: { + applications: {gecko: {id: "meh@web.extension"}}, + permissions: ["experiments.meh"], + }, + + background() { + // The test code below checks that hello() is called at the right + // time with the string "Here I am". Verify that the api schema is + // being correctly interpreted by calling hello() with bad arguments + // and only calling hello() with the magic string if the call with + // bad arguments throws. + try { + browser.meh.hello("I should not see this", "since two arguments are bad"); + } catch (err) { + browser.meh.hello("Here I am"); + } + }, + }); + + let boringAddonFile = Extension.generateXPI({ + manifest: { + applications: {gecko: {id: "boring@web.extension"}}, + }, + background() { + if (browser.meh) { + browser.meh.hello("Here I should not be"); + } + }, + }); + + do_register_cleanup(() => { + for (let file of [apiAddonFile, addonFile, boringAddonFile]) { + Services.obs.notifyObservers(file, "flush-cache-entry", null); + file.remove(false); + } + }); + + + let resolveHello; + let observer = (subject, topic, data) => { + if (topic == "webext-api-loaded") { + ok(!!resolveHello, "Should not see API loaded until dependent extension loads"); + } else if (topic == "webext-api-hello") { + resolveHello(data); + } + }; + + Services.obs.addObserver(observer, "webext-api-loaded", false); + Services.obs.addObserver(observer, "webext-api-hello", false); + do_register_cleanup(() => { + Services.obs.removeObserver(observer, "webext-api-loaded"); + Services.obs.removeObserver(observer, "webext-api-hello"); + }); + + + // Install API add-on. + let apiAddon = yield AddonManager.installTemporaryAddon(apiAddonFile); + + let {APIs} = Cu.import("resource://gre/modules/ExtensionManagement.jsm", {}); + ok(APIs.apis.has("meh"), "Should have meh API."); + + + // Install boring WebExtension add-on. + let boringAddon = yield AddonManager.installTemporaryAddon(boringAddonFile); + yield promiseAddonStartup(); + + + // Install interesting WebExtension add-on. + let promise = new Promise(resolve => { + resolveHello = resolve; + }); + + let addon = yield AddonManager.installTemporaryAddon(addonFile); + yield promiseAddonStartup(); + + let hello = yield promise; + equal(hello, "Here I am", "Should get hello from add-on"); + + // Cleanup. + apiAddon.uninstall(); + + boringAddon.userDisabled = true; + yield new Promise(do_execute_soon); + + equal(addon.appDisabled, true, "Add-on should be app-disabled after its dependency is removed."); + + addon.uninstall(); + boringAddon.uninstall(); +}); + diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_extension.js b/toolkit/components/webextensions/test/xpcshell/test_ext_extension.js new file mode 100644 index 000000000..f18845f6a --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_extension.js @@ -0,0 +1,55 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_is_allowed_incognito_access() { + async function background() { + let allowed = await browser.extension.isAllowedIncognitoAccess(); + + browser.test.assertEq(true, allowed, "isAllowedIncognitoAccess is true"); + browser.test.notifyPass("isAllowedIncognitoAccess"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: {}, + }); + + yield extension.startup(); + yield extension.awaitFinish("isAllowedIncognitoAccess"); + yield extension.unload(); +}); + +add_task(function* test_in_incognito_context_false() { + function background() { + browser.test.assertEq(false, browser.extension.inIncognitoContext, "inIncognitoContext returned false"); + browser.test.notifyPass("inIncognitoContext"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: {}, + }); + + yield extension.startup(); + yield extension.awaitFinish("inIncognitoContext"); + yield extension.unload(); +}); + +add_task(function* test_is_allowed_file_scheme_access() { + async function background() { + let allowed = await browser.extension.isAllowedFileSchemeAccess(); + + browser.test.assertEq(false, allowed, "isAllowedFileSchemeAccess is false"); + browser.test.notifyPass("isAllowedFileSchemeAccess"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: {}, + }); + + yield extension.startup(); + yield extension.awaitFinish("isAllowedFileSchemeAccess"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_idle.js b/toolkit/components/webextensions/test/xpcshell/test_ext_idle.js new file mode 100644 index 000000000..89bcac217 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_idle.js @@ -0,0 +1,202 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Cu.import("resource://testing-common/MockRegistrar.jsm"); + +let idleService = { + _observers: new Set(), + _activity: { + addCalls: [], + removeCalls: [], + observerFires: [], + }, + _reset: function() { + this._observers.clear(); + this._activity.addCalls = []; + this._activity.removeCalls = []; + this._activity.observerFires = []; + }, + _fireObservers: function(state) { + for (let observer of this._observers.values()) { + observer.observe(observer, state, null); + this._activity.observerFires.push(state); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIIdleService]), + idleTime: 19999, + addIdleObserver: function(observer, time) { + this._observers.add(observer); + this._activity.addCalls.push(time); + }, + removeIdleObserver: function(observer, time) { + this._observers.delete(observer); + this._activity.removeCalls.push(time); + }, +}; + +function checkActivity(expectedActivity) { + let {expectedAdd, expectedRemove, expectedFires} = expectedActivity; + let {addCalls, removeCalls, observerFires} = idleService._activity; + equal(expectedAdd.length, addCalls.length, "idleService.addIdleObserver was called the expected number of times"); + equal(expectedRemove.length, removeCalls.length, "idleService.removeIdleObserver was called the expected number of times"); + equal(expectedFires.length, observerFires.length, "idle observer was fired the expected number of times"); + deepEqual(addCalls, expectedAdd, "expected interval passed to idleService.addIdleObserver"); + deepEqual(removeCalls, expectedRemove, "expected interval passed to idleService.removeIdleObserver"); + deepEqual(observerFires, expectedFires, "expected topic passed to idle observer"); +} + +add_task(function* setup() { + let fakeIdleService = MockRegistrar.register("@mozilla.org/widget/idleservice;1", idleService); + do_register_cleanup(() => { + MockRegistrar.unregister(fakeIdleService); + }); +}); + +add_task(function* testQueryStateActive() { + function background() { + browser.idle.queryState(20).then(status => { + browser.test.assertEq("active", status, "Idle status is active"); + browser.test.notifyPass("idle"); + }, + err => { + browser.test.fail(`Error: ${err} :: ${err.stack}`); + browser.test.notifyFail("idle"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["idle"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("idle"); + yield extension.unload(); +}); + +add_task(function* testQueryStateIdle() { + function background() { + browser.idle.queryState(15).then(status => { + browser.test.assertEq("idle", status, "Idle status is idle"); + browser.test.notifyPass("idle"); + }, + err => { + browser.test.fail(`Error: ${err} :: ${err.stack}`); + browser.test.notifyFail("idle"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["idle"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("idle"); + yield extension.unload(); +}); + +add_task(function* testOnlySetDetectionInterval() { + function background() { + browser.idle.setDetectionInterval(99); + browser.test.sendMessage("detectionIntervalSet"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["idle"], + }, + }); + + idleService._reset(); + yield extension.startup(); + yield extension.awaitMessage("detectionIntervalSet"); + idleService._fireObservers("idle"); + checkActivity({expectedAdd: [], expectedRemove: [], expectedFires: []}); + yield extension.unload(); +}); + +add_task(function* testSetDetectionIntervalBeforeAddingListener() { + function background() { + browser.idle.setDetectionInterval(99); + browser.idle.onStateChanged.addListener(newState => { + browser.test.assertEq("idle", newState, "listener fired with the expected state"); + browser.test.sendMessage("listenerFired"); + }); + browser.test.sendMessage("listenerAdded"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["idle"], + }, + }); + + idleService._reset(); + yield extension.startup(); + yield extension.awaitMessage("listenerAdded"); + idleService._fireObservers("idle"); + yield extension.awaitMessage("listenerFired"); + checkActivity({expectedAdd: [99], expectedRemove: [], expectedFires: ["idle"]}); + yield extension.unload(); +}); + +add_task(function* testSetDetectionIntervalAfterAddingListener() { + function background() { + browser.idle.onStateChanged.addListener(newState => { + browser.test.assertEq("idle", newState, "listener fired with the expected state"); + browser.test.sendMessage("listenerFired"); + }); + browser.idle.setDetectionInterval(99); + browser.test.sendMessage("detectionIntervalSet"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["idle"], + }, + }); + + idleService._reset(); + yield extension.startup(); + yield extension.awaitMessage("detectionIntervalSet"); + idleService._fireObservers("idle"); + yield extension.awaitMessage("listenerFired"); + checkActivity({expectedAdd: [60, 99], expectedRemove: [60], expectedFires: ["idle"]}); + yield extension.unload(); +}); + +add_task(function* testOnlyAddingListener() { + function background() { + browser.idle.onStateChanged.addListener(newState => { + browser.test.assertEq("active", newState, "listener fired with the expected state"); + browser.test.sendMessage("listenerFired"); + }); + browser.test.sendMessage("listenerAdded"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["idle"], + }, + }); + + idleService._reset(); + yield extension.startup(); + yield extension.awaitMessage("listenerAdded"); + idleService._fireObservers("active"); + yield extension.awaitMessage("listenerFired"); + // check that "idle-daily" topic does not cause a listener to fire + idleService._fireObservers("idle-daily"); + checkActivity({expectedAdd: [60], expectedRemove: [], expectedFires: ["active", "idle-daily"]}); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_json_parser.js b/toolkit/components/webextensions/test/xpcshell/test_ext_json_parser.js new file mode 100644 index 000000000..652f41315 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_json_parser.js @@ -0,0 +1,37 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_json_parser() { + const ID = "json@test.web.extension"; + + let xpi = Extension.generateXPI({ + files: { + "manifest.json": String.raw`{ + // This is a manifest. + "applications": {"gecko": {"id": "${ID}"}}, + "name": "This \" is // not a comment", + "version": "0.1\\" // , "description": "This is not a description" + }`, + }, + }); + + let expectedManifest = { + "applications": {"gecko": {"id": ID}}, + "name": "This \" is // not a comment", + "version": "0.1\\", + }; + + let fileURI = Services.io.newFileURI(xpi); + let uri = NetUtil.newURI(`jar:${fileURI.spec}!/`); + + let extension = new ExtensionData(uri); + + yield extension.readManifest(); + + Assert.deepEqual(extension.rawManifest, expectedManifest, + "Manifest with correctly-filtered comments"); + + Services.obs.notifyObservers(xpi, "flush-cache-entry", null); + xpi.remove(false); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_context.js b/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_context.js new file mode 100644 index 000000000..63d5361a1 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_context.js @@ -0,0 +1,168 @@ +"use strict"; + +/* globals browser */ + +Cu.import("resource://gre/modules/Extension.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const {LegacyExtensionContext} = Cu.import("resource://gre/modules/LegacyExtensionsUtils.jsm"); + +/** + * This test case ensures that LegacyExtensionContext instances: + * - expose the expected API object and can join the messaging + * of a webextension given its addon id + * - the exposed API object can receive a port related to a `runtime.connect` + * Port created in the webextension's background page + * - the received Port instance can exchange messages with the background page + * - the received Port receive a disconnect event when the webextension is + * shutting down + */ +add_task(function* test_legacy_extension_context() { + function background() { + let bgURL = window.location.href; + + let extensionInfo = { + bgURL, + // Extract the assigned uuid from the background page url. + uuid: window.location.hostname, + }; + + browser.test.sendMessage("webextension-ready", extensionInfo); + + let port; + + browser.test.onMessage.addListener(async msg => { + if (msg == "do-send-message") { + let reply = await browser.runtime.sendMessage("webextension -> legacy_extension message"); + + browser.test.assertEq("legacy_extension -> webextension reply", reply, + "Got the expected message from the LegacyExtensionContext"); + browser.test.sendMessage("got-reply-message"); + } else if (msg == "do-connect") { + port = browser.runtime.connect(); + + port.onMessage.addListener(portMsg => { + browser.test.assertEq("legacy_extension -> webextension port message", portMsg, + "Got the expected message from the LegacyExtensionContext"); + port.postMessage("webextension -> legacy_extension port message"); + }); + } else if (msg == "do-disconnect") { + port.disconnect(); + } + }); + } + + let extensionData = { + background, + }; + + let extension = Extension.generate(extensionData); + + let waitForExtensionInfo = new Promise((resolve, reject) => { + extension.on("test-message", function testMessageListener(kind, msg, ...args) { + if (msg != "webextension-ready") { + reject(new Error(`Got an unexpected test-message: ${msg}`)); + } else { + extension.off("test-message", testMessageListener); + resolve(args[0]); + } + }); + }); + + // Connect to the target extension as an external context + // using the given custom sender info. + let legacyContext; + + extension.on("startup", function onStartup() { + extension.off("startup", onStartup); + legacyContext = new LegacyExtensionContext(extension); + extension.callOnClose({ + close: () => legacyContext.unload(), + }); + }); + + yield extension.startup(); + + let extensionInfo = yield waitForExtensionInfo; + + equal(legacyContext.envType, "legacy_extension", + "LegacyExtensionContext instance has the expected type"); + + ok(legacyContext.api, "Got the expected API object"); + ok(legacyContext.api.browser, "Got the expected browser property"); + + let waitMessage = new Promise(resolve => { + const {browser} = legacyContext.api; + browser.runtime.onMessage.addListener((singleMsg, msgSender) => { + resolve({singleMsg, msgSender}); + + // Send a reply to the sender. + return Promise.resolve("legacy_extension -> webextension reply"); + }); + }); + + extension.testMessage("do-send-message"); + + let {singleMsg, msgSender} = yield waitMessage; + equal(singleMsg, "webextension -> legacy_extension message", + "Got the expected message"); + ok(msgSender, "Got a message sender object"); + + equal(msgSender.id, extensionInfo.uuid, "The sender has the expected id property"); + equal(msgSender.url, extensionInfo.bgURL, "The sender has the expected url property"); + + // Wait confirmation that the reply has been received. + yield new Promise((resolve, reject) => { + extension.on("test-message", function testMessageListener(kind, msg, ...args) { + if (msg != "got-reply-message") { + reject(new Error(`Got an unexpected test-message: ${msg}`)); + } else { + extension.off("test-message", testMessageListener); + resolve(); + } + }); + }); + + let waitConnectPort = new Promise(resolve => { + let {browser} = legacyContext.api; + browser.runtime.onConnect.addListener(port => { + resolve(port); + }); + }); + + extension.testMessage("do-connect"); + + let port = yield waitConnectPort; + + ok(port, "Got the Port API object"); + ok(port.sender, "The port has a sender property"); + equal(port.sender.id, extensionInfo.uuid, + "The port sender has the expected id property"); + equal(port.sender.url, extensionInfo.bgURL, + "The port sender has the expected url property"); + + let waitPortMessage = new Promise(resolve => { + port.onMessage.addListener((msg) => { + resolve(msg); + }); + }); + + port.postMessage("legacy_extension -> webextension port message"); + + let msg = yield waitPortMessage; + + equal(msg, "webextension -> legacy_extension port message", + "LegacyExtensionContext received the expected message from the webextension"); + + let waitForDisconnect = new Promise(resolve => { + port.onDisconnect.addListener(resolve); + }); + + extension.testMessage("do-disconnect"); + + yield waitForDisconnect; + + do_print("Got the disconnect event on unload"); + + yield extension.shutdown(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_embedding.js b/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_embedding.js new file mode 100644 index 000000000..ea5d78524 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_embedding.js @@ -0,0 +1,188 @@ +"use strict"; + +/* globals browser */ + +Cu.import("resource://gre/modules/LegacyExtensionsUtils.jsm"); + +// Import EmbeddedExtensionManager to be able to check that the +// tacked instances are cleared after the embedded extension shutdown. +const { + EmbeddedExtensionManager, +} = Cu.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {}); + +/** + * This test case ensures that the LegacyExtensionsUtils.EmbeddedExtension: + * - load the embedded webextension resources from a "/webextension/" dir + * inside the XPI. + * - EmbeddedExtension.prototype.api returns an API object which exposes + * a working `runtime.onConnect` event object (e.g. the API can receive a port + * when the embedded webextension is started and it can exchange messages + * with the background page). + * - EmbeddedExtension.prototype.startup/shutdown methods manage the embedded + * webextension lifecycle as expected. + */ +add_task(function* test_embedded_webextension_utils() { + function backgroundScript() { + let port = browser.runtime.connect(); + + port.onMessage.addListener((msg) => { + if (msg == "legacy_extension -> webextension") { + port.postMessage("webextension -> legacy_extension"); + port.disconnect(); + } + }); + } + + const id = "@test.embedded.web.extension"; + + // Extensions.generateXPI is used here (and in the other hybrid addons tests in this same + // test dir) to be able to generate an xpi with the directory layout that we expect from + // an hybrid legacy+webextension addon (where all the embedded webextension resources are + // loaded from a 'webextension/' directory). + let fakeHybridAddonFile = Extension.generateZipFile({ + "webextension/manifest.json": { + applications: {gecko: {id}}, + name: "embedded webextension name", + manifest_version: 2, + version: "1.0", + background: { + scripts: ["bg.js"], + }, + }, + "webextension/bg.js": `new ${backgroundScript}`, + }); + + // Remove the generated xpi file and flush the its jar cache + // on cleanup. + do_register_cleanup(() => { + Services.obs.notifyObservers(fakeHybridAddonFile, "flush-cache-entry", null); + fakeHybridAddonFile.remove(false); + }); + + let fileURI = Services.io.newFileURI(fakeHybridAddonFile); + let resourceURI = Services.io.newURI(`jar:${fileURI.spec}!/`, null, null); + + let embeddedExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({ + id, resourceURI, + }); + + ok(embeddedExtension, "Got the embeddedExtension object"); + + equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1, + "Got the expected number of tracked embedded extension instances"); + + do_print("waiting embeddedExtension.startup"); + let embeddedExtensionAPI = yield embeddedExtension.startup(); + ok(embeddedExtensionAPI, "Got the embeddedExtensionAPI object"); + + let waitConnectPort = new Promise(resolve => { + let {browser} = embeddedExtensionAPI; + browser.runtime.onConnect.addListener(port => { + resolve(port); + }); + }); + + let port = yield waitConnectPort; + + ok(port, "Got the Port API object"); + + let waitPortMessage = new Promise(resolve => { + port.onMessage.addListener((msg) => { + resolve(msg); + }); + }); + + port.postMessage("legacy_extension -> webextension"); + + let msg = yield waitPortMessage; + + equal(msg, "webextension -> legacy_extension", + "LegacyExtensionContext received the expected message from the webextension"); + + let waitForDisconnect = new Promise(resolve => { + port.onDisconnect.addListener(resolve); + }); + + do_print("Wait for the disconnect port event"); + yield waitForDisconnect; + do_print("Got the disconnect port event"); + + yield embeddedExtension.shutdown(); + + equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0, + "EmbeddedExtension instances has been untracked from the EmbeddedExtensionManager"); +}); + +function* createManifestErrorTestCase(id, xpi, expectedError) { + // Remove the generated xpi file and flush the its jar cache + // on cleanup. + do_register_cleanup(() => { + Services.obs.notifyObservers(xpi, "flush-cache-entry", null); + xpi.remove(false); + }); + + let fileURI = Services.io.newFileURI(xpi); + let resourceURI = Services.io.newURI(`jar:${fileURI.spec}!/`, null, null); + + let embeddedExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({ + id, resourceURI, + }); + + yield Assert.rejects(embeddedExtension.startup(), expectedError, + "embedded extension startup rejected"); + + // Shutdown a "never-started" addon with an embedded webextension should not + // raise any exception, and if it does this test will fail. + yield embeddedExtension.shutdown(); +} + +add_task(function* test_startup_error_empty_manifest() { + const id = "empty-manifest@test.embedded.web.extension"; + const files = { + "webextension/manifest.json": ``, + }; + const expectedError = "(NS_BASE_STREAM_CLOSED)"; + + let fakeHybridAddonFile = Extension.generateZipFile(files); + + yield createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError); +}); + +add_task(function* test_startup_error_invalid_json_manifest() { + const id = "invalid-json-manifest@test.embedded.web.extension"; + const files = { + "webextension/manifest.json": `{ "name": }`, + }; + const expectedError = "JSON.parse:"; + + let fakeHybridAddonFile = Extension.generateZipFile(files); + + yield createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError); +}); + +add_task(function* test_startup_error_blocking_validation_errors() { + const id = "blocking-manifest-validation-error@test.embedded.web.extension"; + const files = { + "webextension/manifest.json": { + name: "embedded webextension name", + manifest_version: 2, + version: "1.0", + background: { + scripts: {}, + }, + }, + }; + + function expectedError(actual) { + if (actual.errors && actual.errors.length == 1 && + actual.errors[0].startsWith("Reading manifest:")) { + return true; + } + + return false; + } + + let fakeHybridAddonFile = Extension.generateZipFile(files); + + yield createManifestErrorTestCase(id, fakeHybridAddonFile, expectedError); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_localStorage.js b/toolkit/components/webextensions/test/xpcshell/test_ext_localStorage.js new file mode 100644 index 000000000..0f0b41085 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_localStorage.js @@ -0,0 +1,50 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +function backgroundScript() { + let hasRun = localStorage.getItem("has-run"); + let result; + if (!hasRun) { + localStorage.setItem("has-run", "yup"); + localStorage.setItem("test-item", "item1"); + result = "item1"; + } else { + let data = localStorage.getItem("test-item"); + if (data == "item1") { + localStorage.setItem("test-item", "item2"); + result = "item2"; + } else if (data == "item2") { + localStorage.removeItem("test-item"); + result = "deleted"; + } else if (!data) { + localStorage.clear(); + result = "cleared"; + } + } + browser.test.sendMessage("result", result); + browser.test.notifyPass("localStorage"); +} + +const ID = "test-webextension@mozilla.com"; +let extensionData = { + manifest: {applications: {gecko: {id: ID}}}, + background: backgroundScript, +}; + +add_task(function* test_localStorage() { + const RESULTS = ["item1", "item2", "deleted", "cleared", "item1"]; + + for (let expected of RESULTS) { + let extension = ExtensionTestUtils.loadExtension(extensionData); + + yield extension.startup(); + + let actual = yield extension.awaitMessage("result"); + + yield extension.awaitFinish("localStorage"); + yield extension.unload(); + + equal(actual, expected, "got expected localStorage data"); + } +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_management.js b/toolkit/components/webextensions/test/xpcshell/test_ext_management.js new file mode 100644 index 000000000..b19554a57 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_management.js @@ -0,0 +1,20 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_management_schema() { + function background() { + browser.test.assertTrue(browser.management, "browser.management API exists"); + browser.test.notifyPass("management-schema"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["management"], + }, + background: `(${background})()`, + }); + yield extension.startup(); + yield extension.awaitFinish("management-schema"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_management_uninstall_self.js b/toolkit/components/webextensions/test/xpcshell/test_ext_management_uninstall_self.js new file mode 100644 index 000000000..7d80a9c23 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_management_uninstall_self.js @@ -0,0 +1,135 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Cu.import("resource://gre/modules/AddonManager.jsm"); +Cu.import("resource://testing-common/AddonTestUtils.jsm"); +Cu.import("resource://testing-common/MockRegistrar.jsm"); + +const {promiseAddonByID} = AddonTestUtils; +const id = "uninstall_self_test@tests.mozilla.com"; + +const manifest = { + applications: { + gecko: { + id, + }, + }, + name: "test extension name", + version: "1.0", +}; + +const waitForUninstalled = () => new Promise(resolve => { + const listener = { + onUninstalled: (addon) => { + equal(addon.id, id, "The expected add-on has been uninstalled"); + AddonManager.getAddonByID(addon.id, checkedAddon => { + equal(checkedAddon, null, "Add-on no longer exists"); + AddonManager.removeAddonListener(listener); + resolve(); + }); + }, + }; + AddonManager.addAddonListener(listener); +}); + +let promptService = { + _response: null, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]), + confirmEx: function(...args) { + this._confirmExArgs = args; + return this._response; + }, +}; + +add_task(function* setup() { + let fakePromptService = MockRegistrar.register("@mozilla.org/embedcomp/prompt-service;1", promptService); + do_register_cleanup(() => { + MockRegistrar.unregister(fakePromptService); + }); + yield ExtensionTestUtils.startAddonManager(); +}); + +add_task(function* test_management_uninstall_no_prompt() { + function background() { + browser.test.onMessage.addListener(msg => { + browser.management.uninstallSelf(); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest, + background, + useAddonManager: "temporary", + }); + + yield extension.startup(); + let addon = yield promiseAddonByID(id); + notEqual(addon, null, "Add-on is installed"); + extension.sendMessage("uninstall"); + yield waitForUninstalled(); + yield extension.markUnloaded(); + Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null); +}); + +add_task(function* test_management_uninstall_prompt_uninstall() { + promptService._response = 0; + + function background() { + browser.test.onMessage.addListener(msg => { + browser.management.uninstallSelf({showConfirmDialog: true}); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest, + background, + useAddonManager: "temporary", + }); + + yield extension.startup(); + let addon = yield promiseAddonByID(id); + notEqual(addon, null, "Add-on is installed"); + extension.sendMessage("uninstall"); + yield waitForUninstalled(); + yield extension.markUnloaded(); + + // Test localization strings + equal(promptService._confirmExArgs[1], `Uninstall ${manifest.name}`); + equal(promptService._confirmExArgs[2], + `The extension “${manifest.name}†is requesting to be uninstalled. What would you like to do?`); + equal(promptService._confirmExArgs[4], "Uninstall"); + equal(promptService._confirmExArgs[5], "Keep Installed"); + Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null); +}); + +add_task(function* test_management_uninstall_prompt_keep() { + promptService._response = 1; + + function background() { + browser.test.onMessage.addListener(async msg => { + await browser.test.assertRejects( + browser.management.uninstallSelf({showConfirmDialog: true}), + "User cancelled uninstall of extension", + "Expected rejection when user declines uninstall"); + + browser.test.sendMessage("uninstall-rejected"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest, + background, + useAddonManager: "temporary", + }); + + yield extension.startup(); + let addon = yield promiseAddonByID(id); + notEqual(addon, null, "Add-on is installed"); + extension.sendMessage("uninstall"); + yield extension.awaitMessage("uninstall-rejected"); + addon = yield promiseAddonByID(id); + notEqual(addon, null, "Add-on remains installed"); + yield extension.unload(); + Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_content_security_policy.js b/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_content_security_policy.js new file mode 100644 index 000000000..2b0084980 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_content_security_policy.js @@ -0,0 +1,30 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + + +add_task(function* test_manifest_csp() { + let normalized = yield ExtensionTestUtils.normalizeManifest({ + "content_security_policy": "script-src 'self'; object-src 'none'", + }); + + equal(normalized.error, undefined, "Should not have an error"); + equal(normalized.errors.length, 0, "Should not have warnings"); + equal(normalized.value.content_security_policy, + "script-src 'self'; object-src 'none'", + "Should have the expected poilcy string"); + + + normalized = yield ExtensionTestUtils.normalizeManifest({ + "content_security_policy": "object-src 'none'", + }); + + equal(normalized.error, undefined, "Should not have an error"); + + Assert.deepEqual(normalized.errors, + ["Error processing content_security_policy: SyntaxError: Policy is missing a required \u2018script-src\u2019 directive"], + "Should have the expected warning"); + + equal(normalized.value.content_security_policy, null, + "Invalid policy string should be omitted"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_incognito.js b/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_incognito.js new file mode 100644 index 000000000..94649692e --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_incognito.js @@ -0,0 +1,27 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + + +add_task(function* test_manifest_incognito() { + let normalized = yield ExtensionTestUtils.normalizeManifest({ + "incognito": "spanning", + }); + + equal(normalized.error, undefined, "Should not have an error"); + equal(normalized.errors.length, 0, "Should not have warnings"); + equal(normalized.value.incognito, + "spanning", + "Should have the expected incognito string"); + + normalized = yield ExtensionTestUtils.normalizeManifest({ + "incognito": "split", + }); + + equal(normalized.error, undefined, "Should not have an error"); + Assert.deepEqual(normalized.errors, + ['Error processing incognito: Invalid enumeration value "split"'], + "Should have the expected warning"); + equal(normalized.value.incognito, null, + "Invalid incognito string should be omitted"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_minimum_chrome_version.js b/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_minimum_chrome_version.js new file mode 100644 index 000000000..fad5661bb --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_manifest_minimum_chrome_version.js @@ -0,0 +1,13 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + + +add_task(function* test_manifest_minimum_chrome_version() { + let normalized = yield ExtensionTestUtils.normalizeManifest({ + "minimum_chrome_version": "42", + }); + + equal(normalized.error, undefined, "Should not have an error"); + equal(normalized.errors.length, 0, "Should not have warnings"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging.js b/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging.js new file mode 100644 index 000000000..5a6b628f5 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging.js @@ -0,0 +1,514 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* globals chrome */ + +const PREF_MAX_READ = "webextensions.native-messaging.max-input-message-bytes"; +const PREF_MAX_WRITE = "webextensions.native-messaging.max-output-message-bytes"; + +const ECHO_BODY = String.raw` + import struct + import sys + + while True: + rawlen = sys.stdin.read(4) + if len(rawlen) == 0: + sys.exit(0) + msglen = struct.unpack('@I', rawlen)[0] + msg = sys.stdin.read(msglen) + + sys.stdout.write(struct.pack('@I', msglen)) + sys.stdout.write(msg) +`; + +const INFO_BODY = String.raw` + import json + import os + import struct + import sys + + msg = json.dumps({"args": sys.argv, "cwd": os.getcwd()}) + sys.stdout.write(struct.pack('@I', len(msg))) + sys.stdout.write(msg) + sys.exit(0) +`; + +const STDERR_LINES = ["hello stderr", "this should be a separate line"]; +let STDERR_MSG = STDERR_LINES.join("\\n"); + +const STDERR_BODY = String.raw` + import sys + sys.stderr.write("${STDERR_MSG}") +`; + +const SCRIPTS = [ + { + name: "echo", + description: "a native app that echoes back messages it receives", + script: ECHO_BODY.replace(/^ {2}/gm, ""), + }, + { + name: "info", + description: "a native app that gives some info about how it was started", + script: INFO_BODY.replace(/^ {2}/gm, ""), + }, + { + name: "stderr", + description: "a native app that writes to stderr and then exits", + script: STDERR_BODY.replace(/^ {2}/gm, ""), + }, +]; + +add_task(function* setup() { + yield setupHosts(SCRIPTS); +}); + +// Test the basic operation of native messaging with a simple +// script that echoes back whatever message is sent to it. +add_task(function* test_happy_path() { + function background() { + let port = browser.runtime.connectNative("echo"); + port.onMessage.addListener(msg => { + browser.test.sendMessage("message", msg); + }); + browser.test.onMessage.addListener((what, payload) => { + if (what == "send") { + if (payload._json) { + let json = payload._json; + payload.toJSON = () => json; + delete payload._json; + } + port.postMessage(payload); + } + }); + browser.test.sendMessage("ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("ready"); + const tests = [ + { + data: "this is a string", + what: "simple string", + }, + { + data: "Это юникода", + what: "unicode string", + }, + { + data: {test: "hello"}, + what: "simple object", + }, + { + data: { + what: "An object with a few properties", + number: 123, + bool: true, + nested: {what: "another object"}, + }, + what: "object with several properties", + }, + + { + data: { + ignoreme: true, + _json: {data: "i have a tojson method"}, + }, + expected: {data: "i have a tojson method"}, + what: "object with toJSON() method", + }, + ]; + for (let test of tests) { + extension.sendMessage("send", test.data); + let response = yield extension.awaitMessage("message"); + let expected = test.expected || test.data; + deepEqual(response, expected, `Echoed a message of type ${test.what}`); + } + + let procCount = yield getSubprocessCount(); + equal(procCount, 1, "subprocess is still running"); + let exitPromise = waitForSubprocessExit(); + yield extension.unload(); + yield exitPromise; +}); + +if (AppConstants.platform == "win") { + // "relative.echo" has a relative path in the host manifest. + add_task(function* test_relative_path() { + function background() { + let port = browser.runtime.connectNative("relative.echo"); + let MSG = "test relative echo path"; + port.onMessage.addListener(msg => { + browser.test.assertEq(MSG, msg, "Got expected message back"); + browser.test.sendMessage("done"); + }); + port.postMessage(MSG); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("done"); + + let procCount = yield getSubprocessCount(); + equal(procCount, 1, "subprocess is still running"); + let exitPromise = waitForSubprocessExit(); + yield extension.unload(); + yield exitPromise; + }); +} + +// Test sendNativeMessage() +add_task(function* test_sendNativeMessage() { + async function background() { + let MSG = {test: "hello world"}; + + // Check error handling + await browser.test.assertRejects( + browser.runtime.sendNativeMessage("nonexistent", MSG), + /Attempt to postMessage on disconnected port/, + "sendNativeMessage() to a nonexistent app failed"); + + // Check regular message exchange + let reply = await browser.runtime.sendNativeMessage("echo", MSG); + + let expected = JSON.stringify(MSG); + let received = JSON.stringify(reply); + browser.test.assertEq(expected, received, "Received echoed native message"); + + browser.test.sendMessage("finished"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("finished"); + + // With sendNativeMessage(), the subprocess should be disconnected + // after exchanging a single message. + yield waitForSubprocessExit(); + + yield extension.unload(); +}); + +// Test calling Port.disconnect() +add_task(function* test_disconnect() { + function background() { + let port = browser.runtime.connectNative("echo"); + port.onMessage.addListener((msg, msgPort) => { + browser.test.assertEq(port, msgPort, "onMessage handler should receive the port as the second argument"); + browser.test.sendMessage("message", msg); + }); + port.onDisconnect.addListener(msgPort => { + browser.test.fail("onDisconnect should not be called for disconnect()"); + }); + browser.test.onMessage.addListener((what, payload) => { + if (what == "send") { + if (payload._json) { + let json = payload._json; + payload.toJSON = () => json; + delete payload._json; + } + port.postMessage(payload); + } else if (what == "disconnect") { + try { + port.disconnect(); + browser.test.sendMessage("disconnect-result", {success: true}); + } catch (err) { + browser.test.sendMessage("disconnect-result", { + success: false, + errmsg: err.message, + }); + } + } + }); + browser.test.sendMessage("ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("ready"); + + extension.sendMessage("send", "test"); + let response = yield extension.awaitMessage("message"); + equal(response, "test", "Echoed a string"); + + let procCount = yield getSubprocessCount(); + equal(procCount, 1, "subprocess is running"); + + extension.sendMessage("disconnect"); + response = yield extension.awaitMessage("disconnect-result"); + equal(response.success, true, "disconnect succeeded"); + + do_print("waiting for subprocess to exit"); + yield waitForSubprocessExit(); + procCount = yield getSubprocessCount(); + equal(procCount, 0, "subprocess is no longer running"); + + extension.sendMessage("disconnect"); + response = yield extension.awaitMessage("disconnect-result"); + equal(response.success, true, "second call to disconnect silently ignored"); + + yield extension.unload(); +}); + +// Test the limit on message size for writing +add_task(function* test_write_limit() { + Services.prefs.setIntPref(PREF_MAX_WRITE, 10); + function clearPref() { + Services.prefs.clearUserPref(PREF_MAX_WRITE); + } + do_register_cleanup(clearPref); + + function background() { + const PAYLOAD = "0123456789A"; + let port = browser.runtime.connectNative("echo"); + try { + port.postMessage(PAYLOAD); + browser.test.sendMessage("result", null); + } catch (ex) { + browser.test.sendMessage("result", ex.message); + } + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + + let errmsg = yield extension.awaitMessage("result"); + notEqual(errmsg, null, "native postMessage() failed for overly large message"); + + yield extension.unload(); + yield waitForSubprocessExit(); + + clearPref(); +}); + +// Test the limit on message size for reading +add_task(function* test_read_limit() { + Services.prefs.setIntPref(PREF_MAX_READ, 10); + function clearPref() { + Services.prefs.clearUserPref(PREF_MAX_READ); + } + do_register_cleanup(clearPref); + + function background() { + const PAYLOAD = "0123456789A"; + let port = browser.runtime.connectNative("echo"); + port.onDisconnect.addListener(msgPort => { + browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument"); + browser.test.assertEq("Native application tried to send a message of 13 bytes, which exceeds the limit of 10 bytes.", port.error && port.error.message); + browser.test.sendMessage("result", "disconnected"); + }); + port.onMessage.addListener(msg => { + browser.test.sendMessage("result", "message"); + }); + port.postMessage(PAYLOAD); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + + let result = yield extension.awaitMessage("result"); + equal(result, "disconnected", "native port disconnected on receiving large message"); + + yield extension.unload(); + yield waitForSubprocessExit(); + + clearPref(); +}); + +// Test that an extension without the nativeMessaging permission cannot +// use native messaging. +add_task(function* test_ext_permission() { + function background() { + browser.test.assertFalse("connectNative" in chrome.runtime, "chrome.runtime.connectNative does not exist without nativeMessaging permission"); + browser.test.assertFalse("connectNative" in browser.runtime, "browser.runtime.connectNative does not exist without nativeMessaging permission"); + browser.test.assertFalse("sendNativeMessage" in chrome.runtime, "chrome.runtime.sendNativeMessage does not exist without nativeMessaging permission"); + browser.test.assertFalse("sendNativeMessage" in browser.runtime, "browser.runtime.sendNativeMessage does not exist without nativeMessaging permission"); + browser.test.sendMessage("finished"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: {}, + }); + + yield extension.startup(); + yield extension.awaitMessage("finished"); + yield extension.unload(); +}); + +// Test that an extension that is not listed in allowed_extensions for +// a native application cannot use that application. +add_task(function* test_app_permission() { + function background() { + let port = browser.runtime.connectNative("echo"); + port.onDisconnect.addListener(msgPort => { + browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument"); + browser.test.assertEq("This extension does not have permission to use native application echo (or the application is not installed)", port.error && port.error.message); + browser.test.sendMessage("result", "disconnected"); + }); + port.onMessage.addListener(msg => { + browser.test.sendMessage("result", "message"); + }); + port.postMessage({test: "test"}); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["nativeMessaging"], + }, + }, "somethingelse@tests.mozilla.org"); + + yield extension.startup(); + + let result = yield extension.awaitMessage("result"); + equal(result, "disconnected", "connectNative() failed without native app permission"); + + yield extension.unload(); + + let procCount = yield getSubprocessCount(); + equal(procCount, 0, "No child process was started"); +}); + +// Test that the command-line arguments and working directory for the +// native application are as expected. +add_task(function* test_child_process() { + function background() { + let port = browser.runtime.connectNative("info"); + port.onMessage.addListener(msg => { + browser.test.sendMessage("result", msg); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + + let msg = yield extension.awaitMessage("result"); + equal(msg.args.length, 2, "Received one command line argument"); + equal(msg.args[1], getPath("info.json"), "Command line argument is the path to the native host manifest"); + equal(msg.cwd.replace(/^\/private\//, "/"), tmpDir.path, + "Working directory is the directory containing the native appliation"); + + let exitPromise = waitForSubprocessExit(); + yield extension.unload(); + yield exitPromise; +}); + +add_task(function* test_stderr() { + function background() { + let port = browser.runtime.connectNative("stderr"); + port.onDisconnect.addListener(msgPort => { + browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument"); + browser.test.assertEq(null, port.error, "Normal application exit is not an error"); + browser.test.sendMessage("finished"); + }); + } + + let {messages} = yield promiseConsoleOutput(function* () { + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("finished"); + yield extension.unload(); + + yield waitForSubprocessExit(); + }); + + let lines = STDERR_LINES.map(line => messages.findIndex(msg => msg.message.includes(line))); + notEqual(lines[0], -1, "Saw first line of stderr output on the console"); + notEqual(lines[1], -1, "Saw second line of stderr output on the console"); + notEqual(lines[0], lines[1], "Stderr output lines are separated in the console"); +}); + +// Test that calling connectNative() multiple times works +// (bug 1313980 was a previous regression in this area) +add_task(function* test_multiple_connects() { + async function background() { + function once() { + return new Promise(resolve => { + let MSG = "hello"; + let port = browser.runtime.connectNative("echo"); + + port.onMessage.addListener(msg => { + browser.test.assertEq(MSG, msg, "Got expected message back"); + port.disconnect(); + resolve(); + }); + port.postMessage(MSG); + }); + } + + await once(); + await once(); + browser.test.notifyPass("multiple-connect"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("multiple-connect"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging_perf.js b/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging_perf.js new file mode 100644 index 000000000..693f67dde --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging_perf.js @@ -0,0 +1,128 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry", + "resource://testing-common/MockRegistry.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); + +Cu.import("resource://gre/modules/Subprocess.jsm"); + +const MAX_ROUND_TRIP_TIME_MS = AppConstants.DEBUG || AppConstants.ASAN ? 36 : 18; +const MAX_RETRIES = 5; + + +const ECHO_BODY = String.raw` + import struct + import sys + + while True: + rawlen = sys.stdin.read(4) + if len(rawlen) == 0: + sys.exit(0) + + msglen = struct.unpack('@I', rawlen)[0] + msg = sys.stdin.read(msglen) + + sys.stdout.write(struct.pack('@I', msglen)) + sys.stdout.write(msg) +`; + +const SCRIPTS = [ + { + name: "echo", + description: "A native app that echoes back messages it receives", + script: ECHO_BODY.replace(/^ {2}/gm, ""), + }, +]; + +add_task(function* setup() { + yield setupHosts(SCRIPTS); +}); + +add_task(function* test_round_trip_perf() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.onMessage.addListener(msg => { + if (msg != "run-tests") { + return; + } + + let port = browser.runtime.connectNative("echo"); + + function next() { + port.postMessage({ + "Lorem": { + "ipsum": { + "dolor": [ + "sit amet", + "consectetur adipiscing elit", + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + ], + "Ut enim": [ + "ad minim veniam", + "quis nostrud exercitation ullamco", + "laboris nisi ut aliquip ex ea commodo consequat.", + ], + "Duis": [ + "aute irure dolor in reprehenderit in", + "voluptate velit esse cillum dolore eu", + "fugiat nulla pariatur.", + ], + "Excepteur": [ + "sint occaecat cupidatat non proident", + "sunt in culpa qui officia deserunt", + "mollit anim id est laborum.", + ], + }, + }, + }); + } + + const COUNT = 1000; + let now; + function finish() { + let roundTripTime = (Date.now() - now) / COUNT; + + port.disconnect(); + browser.test.sendMessage("result", roundTripTime); + } + + let count = 0; + port.onMessage.addListener(() => { + if (count == 0) { + // Skip the first round, since it includes the time it takes + // the app to start up. + now = Date.now(); + } + + if (count++ <= COUNT) { + next(); + } else { + finish(); + } + }); + + next(); + }); + }, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + + let roundTripTime = Infinity; + for (let i = 0; i < MAX_RETRIES && roundTripTime > MAX_ROUND_TRIP_TIME_MS; i++) { + extension.sendMessage("run-tests"); + roundTripTime = yield extension.awaitMessage("result"); + } + + yield extension.unload(); + + ok(roundTripTime <= MAX_ROUND_TRIP_TIME_MS, + `Expected round trip time (${roundTripTime}ms) to be less than ${MAX_ROUND_TRIP_TIME_MS}ms`); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging_unresponsive.js b/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging_unresponsive.js new file mode 100644 index 000000000..a75a1d49d --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_native_messaging_unresponsive.js @@ -0,0 +1,82 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const WONTDIE_BODY = String.raw` + import signal + import struct + import sys + import time + + signal.signal(signal.SIGTERM, signal.SIG_IGN) + + def spin(): + while True: + try: + signal.pause() + except AttributeError: + time.sleep(5) + + while True: + rawlen = sys.stdin.read(4) + if len(rawlen) == 0: + spin() + + msglen = struct.unpack('@I', rawlen)[0] + msg = sys.stdin.read(msglen) + + sys.stdout.write(struct.pack('@I', msglen)) + sys.stdout.write(msg) +`; + +const SCRIPTS = [ + { + name: "wontdie", + description: "a native app that does not exit when stdin closes or on SIGTERM", + script: WONTDIE_BODY.replace(/^ {2}/gm, ""), + }, +]; + +add_task(function* setup() { + yield setupHosts(SCRIPTS); +}); + + +// Test that an unresponsive native application still gets killed eventually +add_task(function* test_unresponsive_native_app() { + // XXX expose GRACEFUL_SHUTDOWN_TIME as a pref and reduce it + // just for this test? + + function background() { + let port = browser.runtime.connectNative("wontdie"); + + const MSG = "echo me"; + // bounce a message to make sure the process actually starts + port.onMessage.addListener(msg => { + browser.test.assertEq(msg, MSG, "Received echoed message"); + browser.test.sendMessage("ready"); + }); + port.postMessage(MSG); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + applications: {gecko: {id: ID}}, + permissions: ["nativeMessaging"], + }, + }); + + yield extension.startup(); + yield extension.awaitMessage("ready"); + + let procCount = yield getSubprocessCount(); + equal(procCount, 1, "subprocess is running"); + + let exitPromise = waitForSubprocessExit(); + yield extension.unload(); + yield exitPromise; + + procCount = yield getSubprocessCount(); + equal(procCount, 0, "subprocess was succesfully killed"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_onmessage_removelistener.js b/toolkit/components/webextensions/test/xpcshell/test_ext_onmessage_removelistener.js new file mode 100644 index 000000000..6f8b553fc --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_onmessage_removelistener.js @@ -0,0 +1,30 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +function backgroundScript() { + function listener() { + browser.test.notifyFail("listener should not be invoked"); + } + + browser.runtime.onMessage.addListener(listener); + browser.runtime.onMessage.removeListener(listener); + browser.runtime.sendMessage("hello"); + + // Make sure that, if we somehow fail to remove the listener, then we'll run + // the listener before the test is marked as passing. + setTimeout(function() { + browser.test.notifyPass("onmessage_removelistener"); + }, 0); +} + +let extensionData = { + background: backgroundScript, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + yield extension.awaitFinish("onmessage_removelistener"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_connect_no_receiver.js b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_connect_no_receiver.js new file mode 100644 index 000000000..2a1342cde --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_connect_no_receiver.js @@ -0,0 +1,23 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_connect_without_listener() { + function background() { + let port = browser.runtime.connect(); + port.onDisconnect.addListener(() => { + browser.test.assertEq("Could not establish connection. Receiving end does not exist.", port.error && port.error.message); + browser.test.notifyPass("port.onDisconnect was called"); + }); + } + let extensionData = { + background, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + + yield extension.awaitFinish("port.onDisconnect was called"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_getBrowserInfo.js b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_getBrowserInfo.js new file mode 100644 index 000000000..a280206fa --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_getBrowserInfo.js @@ -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"; + +add_task(function* setup() { + ExtensionTestUtils.mockAppInfo(); +}); + +add_task(function* test_getBrowserInfo() { + async function background() { + let info = await browser.runtime.getBrowserInfo(); + + browser.test.assertEq(info.name, "XPCShell", "name is valid"); + browser.test.assertEq(info.vendor, "Mozilla", "vendor is Mozilla"); + browser.test.assertEq(info.version, "48", "version is correct"); + browser.test.assertEq(info.buildID, "20160315", "buildID is correct"); + + browser.test.notifyPass("runtime.getBrowserInfo"); + } + + const extension = ExtensionTestUtils.loadExtension({background}); + yield extension.startup(); + yield extension.awaitFinish("runtime.getBrowserInfo"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_getPlatformInfo.js b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_getPlatformInfo.js new file mode 100644 index 000000000..29bad0c10 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_getPlatformInfo.js @@ -0,0 +1,25 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +function backgroundScript() { + browser.runtime.getPlatformInfo(info => { + let validOSs = ["mac", "win", "android", "cros", "linux", "openbsd"]; + let validArchs = ["arm", "x86-32", "x86-64"]; + + browser.test.assertTrue(validOSs.indexOf(info.os) != -1, "OS is valid"); + browser.test.assertTrue(validArchs.indexOf(info.arch) != -1, "Architecture is valid"); + browser.test.notifyPass("runtime.getPlatformInfo"); + }); +} + +let extensionData = { + background: backgroundScript, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + yield extension.awaitFinish("runtime.getPlatformInfo"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js new file mode 100644 index 000000000..fa6461412 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js @@ -0,0 +1,337 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +XPCOMUtils.defineLazyGetter(this, "Management", () => { + const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {}); + return Management; +}); + +const { + createAppInfo, + createTempWebExtensionFile, + promiseAddonByID, + promiseAddonEvent, + promiseCompleteAllInstalls, + promiseFindAddonUpdates, + promiseRestartManager, + promiseShutdownManager, + promiseStartupManager, +} = AddonTestUtils; + +AddonTestUtils.init(this); + +// Allow for unsigned addons. +AddonTestUtils.overrideCertDB(); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42"); + +function awaitEvent(eventName) { + return new Promise(resolve => { + let listener = (_eventName, ...args) => { + if (_eventName === eventName) { + Management.off(eventName, listener); + resolve(...args); + } + }; + + Management.on(eventName, listener); + }); +} + +function background() { + let onInstalledDetails = null; + let onStartupFired = false; + + browser.runtime.onInstalled.addListener(details => { + onInstalledDetails = details; + }); + + browser.runtime.onStartup.addListener(() => { + onStartupFired = true; + }); + + browser.test.onMessage.addListener(message => { + if (message === "get-on-installed-details") { + onInstalledDetails = onInstalledDetails || {fired: false}; + browser.test.sendMessage("on-installed-details", onInstalledDetails); + } else if (message === "did-on-startup-fire") { + browser.test.sendMessage("on-startup-fired", onStartupFired); + } else if (message === "reload-extension") { + browser.runtime.reload(); + } + }); + + browser.runtime.onUpdateAvailable.addListener(details => { + browser.test.sendMessage("reloading"); + browser.runtime.reload(); + }); +} + +function* expectEvents(extension, {onStartupFired, onInstalledFired, onInstalledReason}) { + extension.sendMessage("get-on-installed-details"); + let details = yield extension.awaitMessage("on-installed-details"); + if (onInstalledFired) { + equal(details.reason, onInstalledReason, "runtime.onInstalled fired with the correct reason"); + } else { + equal(details.fired, onInstalledFired, "runtime.onInstalled should not have fired"); + } + + extension.sendMessage("did-on-startup-fire"); + let fired = yield extension.awaitMessage("on-startup-fired"); + equal(fired, onStartupFired, `Expected runtime.onStartup to ${onStartupFired ? "" : "not "} fire`); +} + +add_task(function* test_should_fire_on_addon_update() { + const EXTENSION_ID = "test_runtime_on_installed_addon_update@tests.mozilla.org"; + + const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; + + // The test extension uses an insecure update url. + Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + + const testServer = createHttpServer(); + const port = testServer.identity.primaryPort; + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + "version": "1.0", + "applications": { + "gecko": { + "id": EXTENSION_ID, + "update_url": `http://localhost:${port}/test_update.json`, + }, + }, + }, + background, + }); + + testServer.registerPathHandler("/test_update.json", (request, response) => { + response.write(`{ + "addons": { + "${EXTENSION_ID}": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:${port}/addons/test_runtime_on_installed-2.0.xpi" + } + ] + } + } + }`); + }); + + let webExtensionFile = createTempWebExtensionFile({ + manifest: { + version: "2.0", + applications: { + gecko: { + id: EXTENSION_ID, + }, + }, + }, + background, + }); + + testServer.registerFile("/addons/test_runtime_on_installed-2.0.xpi", webExtensionFile); + + yield promiseStartupManager(); + + yield extension.startup(); + + yield expectEvents(extension, { + onStartupFired: false, + onInstalledFired: true, + onInstalledReason: "install", + }); + + let addon = yield promiseAddonByID(EXTENSION_ID); + equal(addon.version, "1.0", "The installed addon has the correct version"); + + let update = yield promiseFindAddonUpdates(addon); + let install = update.updateAvailable; + + let promiseInstalled = promiseAddonEvent("onInstalled"); + yield promiseCompleteAllInstalls([install]); + + yield extension.awaitMessage("reloading"); + + let startupPromise = awaitEvent("ready"); + + let [updated_addon] = yield promiseInstalled; + equal(updated_addon.version, "2.0", "The updated addon has the correct version"); + + extension.extension = yield startupPromise; + extension.attachListeners(); + + yield expectEvents(extension, { + onStartupFired: false, + onInstalledFired: true, + onInstalledReason: "update", + }); + + yield extension.unload(); + + yield updated_addon.uninstall(); + yield promiseShutdownManager(); +}); + +add_task(function* test_should_fire_on_browser_update() { + const EXTENSION_ID = "test_runtime_on_installed_browser_update@tests.mozilla.org"; + + yield promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + "version": "1.0", + "applications": { + "gecko": { + "id": EXTENSION_ID, + }, + }, + }, + background, + }); + + yield extension.startup(); + + yield expectEvents(extension, { + onStartupFired: false, + onInstalledFired: true, + onInstalledReason: "install", + }); + + let startupPromise = awaitEvent("ready"); + yield promiseRestartManager("1"); + extension.extension = yield startupPromise; + extension.attachListeners(); + + yield expectEvents(extension, { + onStartupFired: true, + onInstalledFired: false, + }); + + // Update the browser. + startupPromise = awaitEvent("ready"); + yield promiseRestartManager("2"); + extension.extension = yield startupPromise; + extension.attachListeners(); + + yield expectEvents(extension, { + onStartupFired: true, + onInstalledFired: true, + onInstalledReason: "browser_update", + }); + + // Restart the browser. + startupPromise = awaitEvent("ready"); + yield promiseRestartManager("2"); + extension.extension = yield startupPromise; + extension.attachListeners(); + + yield expectEvents(extension, { + onStartupFired: true, + onInstalledFired: false, + }); + + // Update the browser again. + startupPromise = awaitEvent("ready"); + yield promiseRestartManager("3"); + extension.extension = yield startupPromise; + extension.attachListeners(); + + yield expectEvents(extension, { + onStartupFired: true, + onInstalledFired: true, + onInstalledReason: "browser_update", + }); + + yield extension.unload(); + + yield promiseShutdownManager(); +}); + +add_task(function* test_should_not_fire_on_reload() { + const EXTENSION_ID = "test_runtime_on_installed_reload@tests.mozilla.org"; + + yield promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + "version": "1.0", + "applications": { + "gecko": { + "id": EXTENSION_ID, + }, + }, + }, + background, + }); + + yield extension.startup(); + + yield expectEvents(extension, { + onStartupFired: false, + onInstalledFired: true, + onInstalledReason: "install", + }); + + let startupPromise = awaitEvent("ready"); + extension.sendMessage("reload-extension"); + extension.extension = yield startupPromise; + extension.attachListeners(); + + yield expectEvents(extension, { + onStartupFired: false, + onInstalledFired: false, + }); + + yield extension.unload(); + yield promiseShutdownManager(); +}); + +add_task(function* test_should_not_fire_on_restart() { + const EXTENSION_ID = "test_runtime_on_installed_restart@tests.mozilla.org"; + + yield promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + "version": "1.0", + "applications": { + "gecko": { + "id": EXTENSION_ID, + }, + }, + }, + background, + }); + + yield extension.startup(); + + yield expectEvents(extension, { + onStartupFired: false, + onInstalledFired: true, + onInstalledReason: "install", + }); + + let addon = yield promiseAddonByID(EXTENSION_ID); + addon.userDisabled = true; + + let startupPromise = awaitEvent("ready"); + addon.userDisabled = false; + extension.extension = yield startupPromise; + extension.attachListeners(); + + yield expectEvents(extension, { + onStartupFired: false, + onInstalledFired: false, + }); + + yield extension.markUnloaded(); + yield promiseShutdownManager(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage.js b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage.js new file mode 100644 index 000000000..fec8e13dd --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage.js @@ -0,0 +1,79 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* tabsSendMessageReply() { + function background() { + browser.runtime.onMessage.addListener((msg, sender, respond) => { + if (msg == "respond-now") { + respond(msg); + } else if (msg == "respond-soon") { + setTimeout(() => { respond(msg); }, 0); + return true; + } else if (msg == "respond-promise") { + return Promise.resolve(msg); + } else if (msg == "respond-never") { + return; + } else if (msg == "respond-error") { + return Promise.reject(new Error(msg)); + } else if (msg == "throw-error") { + throw new Error(msg); + } + }); + + browser.runtime.onMessage.addListener((msg, sender, respond) => { + if (msg == "respond-now") { + respond("hello"); + } else if (msg == "respond-now-2") { + respond(msg); + } + }); + + let childFrame = document.createElement("iframe"); + childFrame.src = "extensionpage.html"; + document.body.appendChild(childFrame); + } + + function senderScript() { + Promise.all([ + browser.runtime.sendMessage("respond-now"), + browser.runtime.sendMessage("respond-now-2"), + new Promise(resolve => browser.runtime.sendMessage("respond-soon", resolve)), + browser.runtime.sendMessage("respond-promise"), + browser.runtime.sendMessage("respond-never"), + new Promise(resolve => { + browser.runtime.sendMessage("respond-never", response => { resolve(response); }); + }), + + browser.runtime.sendMessage("respond-error").catch(error => Promise.resolve({error})), + browser.runtime.sendMessage("throw-error").catch(error => Promise.resolve({error})), + ]).then(([respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondNever2, respondError, throwError]) => { + browser.test.assertEq("respond-now", respondNow, "Got the expected immediate response"); + browser.test.assertEq("respond-now-2", respondNow2, "Got the expected immediate response from the second listener"); + browser.test.assertEq("respond-soon", respondSoon, "Got the expected delayed response"); + browser.test.assertEq("respond-promise", respondPromise, "Got the expected promise response"); + browser.test.assertEq(undefined, respondNever, "Got the expected no-response resolution"); + browser.test.assertEq(undefined, respondNever2, "Got the expected no-response resolution"); + + browser.test.assertEq("respond-error", respondError.error.message, "Got the expected error response"); + browser.test.assertEq("throw-error", throwError.error.message, "Got the expected thrown error response"); + + browser.test.notifyPass("sendMessage"); + }).catch(e => { + browser.test.fail(`Error: ${e} :: ${e.stack}`); + browser.test.notifyFail("sendMessage"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + files: { + "senderScript.js": senderScript, + "extensionpage.html": ``, + }, + }); + + yield extension.startup(); + yield extension.awaitFinish("sendMessage"); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js new file mode 100644 index 000000000..f1a8d5a36 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js @@ -0,0 +1,59 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_sendMessage_error() { + async function background() { + let circ = {}; + circ.circ = circ; + let testCases = [ + // [arguments, expected error string], + [[], "runtime.sendMessage's message argument is missing"], + [[null, null, null, null], "runtime.sendMessage's last argument is not a function"], + [[null, null, 1], "runtime.sendMessage's options argument is invalid"], + [[1, null, null], "runtime.sendMessage's extensionId argument is invalid"], + [[null, null, null, null, null], "runtime.sendMessage received too many arguments"], + + // Even when the parameters are accepted, we still expect an error + // because there is no onMessage listener. + [[null, null, null], "Could not establish connection. Receiving end does not exist."], + + // Structural cloning doesn't work with DOM but we fall back + // JSON serialization, so we don't expect another error. + [[null, location, null], "Could not establish connection. Receiving end does not exist."], + + // Structured cloning supports cyclic self-references. + [[null, [circ, location], null], "cyclic object value"], + // JSON serialization does not support cyclic references. + [[null, circ, null], "Could not establish connection. Receiving end does not exist."], + // (the last two tests shows whether sendMessage is implemented as structured cloning). + ]; + + // Repeat all tests with the undefined value instead of null. + for (let [args, expectedError] of testCases.slice()) { + args = args.map(arg => arg === null ? undefined : arg); + testCases.push([args, expectedError]); + } + + for (let [args, expectedError] of testCases) { + let description = `runtime.sendMessage(${args.map(String).join(", ")})`; + + await browser.test.assertRejects( + browser.runtime.sendMessage(...args), + expectedError, + `expected error message for ${description}`); + } + + browser.test.notifyPass("sendMessage parameter validation"); + } + let extensionData = { + background, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + + yield extension.awaitFinish("sendMessage parameter validation"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_no_receiver.js b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_no_receiver.js new file mode 100644 index 000000000..f906333d2 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_no_receiver.js @@ -0,0 +1,54 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_sendMessage_without_listener() { + async function background() { + await browser.test.assertRejects( + browser.runtime.sendMessage("msg"), + "Could not establish connection. Receiving end does not exist.", + "sendMessage callback was invoked"); + + browser.test.notifyPass("sendMessage callback was invoked"); + } + let extensionData = { + background, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + + yield extension.awaitFinish("sendMessage callback was invoked"); + + yield extension.unload(); +}); + +add_task(function* test_chrome_sendMessage_without_listener() { + function background() { + /* globals chrome */ + browser.test.assertEq(null, chrome.runtime.lastError, "no lastError before call"); + let retval = chrome.runtime.sendMessage("msg"); + browser.test.assertEq(null, chrome.runtime.lastError, "no lastError after call"); + browser.test.assertEq(undefined, retval, "return value of chrome.runtime.sendMessage without callback"); + + let isAsyncCall = false; + retval = chrome.runtime.sendMessage("msg", reply => { + browser.test.assertEq(undefined, reply, "no reply"); + browser.test.assertTrue(isAsyncCall, "chrome.runtime.sendMessage's callback must be called asynchronously"); + browser.test.assertEq(undefined, retval, "return value of chrome.runtime.sendMessage with callback"); + browser.test.assertEq("Could not establish connection. Receiving end does not exist.", chrome.runtime.lastError.message); + browser.test.notifyPass("finished chrome.runtime.sendMessage"); + }); + isAsyncCall = true; + } + let extensionData = { + background, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + + yield extension.awaitFinish("finished chrome.runtime.sendMessage"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_self.js b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_self.js new file mode 100644 index 000000000..e4f5e951f --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_runtime_sendMessage_self.js @@ -0,0 +1,51 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ + +"use strict"; + +add_task(function* test_sendMessage_to_self_should_not_trigger_onMessage() { + async function background() { + browser.runtime.onMessage.addListener(msg => { + browser.test.assertEq("msg from child", msg); + browser.test.notifyPass("sendMessage did not call same-frame onMessage"); + }); + + browser.test.onMessage.addListener(msg => { + browser.test.assertEq("sendMessage with a listener in another frame", msg); + browser.runtime.sendMessage("should only reach another frame"); + }); + + await browser.test.assertRejects( + browser.runtime.sendMessage("should not trigger same-frame onMessage"), + "Could not establish connection. Receiving end does not exist."); + + let anotherFrame = document.createElement("iframe"); + anotherFrame.src = browser.extension.getURL("extensionpage.html"); + document.body.appendChild(anotherFrame); + } + + function lastScript() { + browser.runtime.onMessage.addListener(msg => { + browser.test.assertEq("should only reach another frame", msg); + browser.runtime.sendMessage("msg from child"); + }); + browser.test.sendMessage("sendMessage callback called"); + } + + let extensionData = { + background, + files: { + "lastScript.js": lastScript, + "extensionpage.html": ``, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + + yield extension.awaitMessage("sendMessage callback called"); + extension.sendMessage("sendMessage with a listener in another frame"); + yield extension.awaitFinish("sendMessage did not call same-frame onMessage"); + + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_schemas.js b/toolkit/components/webextensions/test/xpcshell/test_ext_schemas.js new file mode 100644 index 000000000..d838be5b5 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_schemas.js @@ -0,0 +1,1427 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/Schemas.jsm"); +Components.utils.import("resource://gre/modules/BrowserUtils.jsm"); +Components.utils.import("resource://gre/modules/ExtensionCommon.jsm"); + +let {LocalAPIImplementation, SchemaAPIInterface} = ExtensionCommon; + +let json = [ + {namespace: "testing", + + properties: { + PROP1: {value: 20}, + prop2: {type: "string"}, + prop3: { + $ref: "submodule", + }, + prop4: { + $ref: "submodule", + unsupported: true, + }, + }, + + types: [ + { + id: "type1", + type: "string", + "enum": ["value1", "value2", "value3"], + }, + + { + id: "type2", + type: "object", + properties: { + prop1: {type: "integer"}, + prop2: {type: "array", items: {"$ref": "type1"}}, + }, + }, + + { + id: "basetype1", + type: "object", + properties: { + prop1: {type: "string"}, + }, + }, + + { + id: "basetype2", + choices: [ + {type: "integer"}, + ], + }, + + { + $extend: "basetype1", + properties: { + prop2: {type: "string"}, + }, + }, + + { + $extend: "basetype2", + choices: [ + {type: "string"}, + ], + }, + + { + id: "submodule", + type: "object", + functions: [ + { + name: "sub_foo", + type: "function", + parameters: [], + returns: "integer", + }, + ], + }, + ], + + functions: [ + { + name: "foo", + type: "function", + parameters: [ + {name: "arg1", type: "integer", optional: true, default: 99}, + {name: "arg2", type: "boolean", optional: true}, + ], + }, + + { + name: "bar", + type: "function", + parameters: [ + {name: "arg1", type: "integer", optional: true}, + {name: "arg2", type: "boolean"}, + ], + }, + + { + name: "baz", + type: "function", + parameters: [ + {name: "arg1", type: "object", properties: { + prop1: {type: "string"}, + prop2: {type: "integer", optional: true}, + prop3: {type: "integer", unsupported: true}, + }}, + ], + }, + + { + name: "qux", + type: "function", + parameters: [ + {name: "arg1", "$ref": "type1"}, + ], + }, + + { + name: "quack", + type: "function", + parameters: [ + {name: "arg1", "$ref": "type2"}, + ], + }, + + { + name: "quora", + type: "function", + parameters: [ + {name: "arg1", type: "function"}, + ], + }, + + { + name: "quileute", + type: "function", + parameters: [ + {name: "arg1", type: "integer", optional: true}, + {name: "arg2", type: "integer"}, + ], + }, + + { + name: "queets", + type: "function", + unsupported: true, + parameters: [], + }, + + { + name: "quintuplets", + type: "function", + parameters: [ + {name: "obj", type: "object", properties: [], additionalProperties: {type: "integer"}}, + ], + }, + + { + name: "quasar", + type: "function", + parameters: [ + {name: "abc", type: "object", properties: { + func: {type: "function", parameters: [ + {name: "x", type: "integer"}, + ]}, + }}, + ], + }, + + { + name: "quosimodo", + type: "function", + parameters: [ + {name: "xyz", type: "object", additionalProperties: {type: "any"}}, + ], + }, + + { + name: "patternprop", + type: "function", + parameters: [ + { + name: "obj", + type: "object", + properties: {"prop1": {type: "string", pattern: "^\\d+$"}}, + patternProperties: { + "(?i)^prop\\d+$": {type: "string"}, + "^foo\\d+$": {type: "string"}, + }, + }, + ], + }, + + { + name: "pattern", + type: "function", + parameters: [ + {name: "arg", type: "string", pattern: "(?i)^[0-9a-f]+$"}, + ], + }, + + { + name: "format", + type: "function", + parameters: [ + { + name: "arg", + type: "object", + properties: { + url: {type: "string", "format": "url", "optional": true}, + relativeUrl: {type: "string", "format": "relativeUrl", "optional": true}, + strictRelativeUrl: {type: "string", "format": "strictRelativeUrl", "optional": true}, + }, + }, + ], + }, + + { + name: "formatDate", + type: "function", + parameters: [ + { + name: "arg", + type: "object", + properties: { + date: {type: "string", format: "date", optional: true}, + }, + }, + ], + }, + + { + name: "deep", + type: "function", + parameters: [ + { + name: "arg", + type: "object", + properties: { + foo: { + type: "object", + properties: { + bar: { + type: "array", + items: { + type: "object", + properties: { + baz: { + type: "object", + properties: { + required: {type: "integer"}, + optional: {type: "string", optional: true}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + ], + }, + + { + name: "errors", + type: "function", + parameters: [ + { + name: "arg", + type: "object", + properties: { + warn: { + type: "string", + pattern: "^\\d+$", + optional: true, + onError: "warn", + }, + ignore: { + type: "string", + pattern: "^\\d+$", + optional: true, + onError: "ignore", + }, + default: { + type: "string", + pattern: "^\\d+$", + optional: true, + }, + }, + }, + ], + }, + + { + name: "localize", + type: "function", + parameters: [ + { + name: "arg", + type: "object", + properties: { + foo: {type: "string", "preprocess": "localize", "optional": true}, + bar: {type: "string", "optional": true}, + url: {type: "string", "preprocess": "localize", "format": "url", "optional": true}, + }, + }, + ], + }, + + { + name: "extended1", + type: "function", + parameters: [ + {name: "val", $ref: "basetype1"}, + ], + }, + + { + name: "extended2", + type: "function", + parameters: [ + {name: "val", $ref: "basetype2"}, + ], + }, + ], + + events: [ + { + name: "onFoo", + type: "function", + }, + + { + name: "onBar", + type: "function", + extraParameters: [{ + name: "filter", + type: "integer", + optional: true, + default: 1, + }], + }, + ], + }, + { + namespace: "foreign", + properties: { + foreignRef: {$ref: "testing.submodule"}, + }, + }, + { + namespace: "inject", + properties: { + PROP1: {value: "should inject"}, + }, + }, + { + namespace: "do-not-inject", + properties: { + PROP1: {value: "should not inject"}, + }, + }, +]; + +let tallied = null; + +function tally(kind, ns, name, args) { + tallied = [kind, ns, name, args]; +} + +function verify(...args) { + do_check_eq(JSON.stringify(tallied), JSON.stringify(args)); + tallied = null; +} + +let talliedErrors = []; + +function checkErrors(errors) { + do_check_eq(talliedErrors.length, errors.length, "Got expected number of errors"); + for (let [i, error] of errors.entries()) { + do_check_true(i in talliedErrors && talliedErrors[i].includes(error), + `${JSON.stringify(error)} is a substring of error ${JSON.stringify(talliedErrors[i])}`); + } + + talliedErrors.length = 0; +} + +let permissions = new Set(); + +class TallyingAPIImplementation extends SchemaAPIInterface { + constructor(namespace, name) { + super(); + this.namespace = namespace; + this.name = name; + } + + callFunction(args) { + tally("call", this.namespace, this.name, args); + } + + callFunctionNoReturn(args) { + tally("call", this.namespace, this.name, args); + } + + getProperty() { + tally("get", this.namespace, this.name); + } + + setProperty(value) { + tally("set", this.namespace, this.name, value); + } + + addListener(listener, args) { + tally("addListener", this.namespace, this.name, [listener, args]); + } + + removeListener(listener) { + tally("removeListener", this.namespace, this.name, [listener]); + } + + hasListener(listener) { + tally("hasListener", this.namespace, this.name, [listener]); + } +} + +let wrapper = { + url: "moz-extension://b66e3509-cdb3-44f6-8eb8-c8b39b3a1d27/", + + checkLoadURL(url) { + return !url.startsWith("chrome:"); + }, + + preprocessors: { + localize(value, context) { + return value.replace(/__MSG_(.*?)__/g, (m0, m1) => `${m1.toUpperCase()}`); + }, + }, + + logError(message) { + talliedErrors.push(message); + }, + + hasPermission(permission) { + return permissions.has(permission); + }, + + shouldInject(ns) { + return ns != "do-not-inject"; + }, + + getImplementation(namespace, name) { + return new TallyingAPIImplementation(namespace, name); + }, +}; + +add_task(function* () { + let url = "data:," + JSON.stringify(json); + yield Schemas.load(url); + + let root = {}; + tallied = null; + Schemas.inject(root, wrapper); + do_check_eq(tallied, null); + + do_check_eq(root.testing.PROP1, 20, "simple value property"); + do_check_eq(root.testing.type1.VALUE1, "value1", "enum type"); + do_check_eq(root.testing.type1.VALUE2, "value2", "enum type"); + + do_check_eq("inject" in root, true, "namespace 'inject' should be injected"); + do_check_eq("do-not-inject" in root, false, "namespace 'do-not-inject' should not be injected"); + + root.testing.foo(11, true); + verify("call", "testing", "foo", [11, true]); + + root.testing.foo(true); + verify("call", "testing", "foo", [99, true]); + + root.testing.foo(null, true); + verify("call", "testing", "foo", [99, true]); + + root.testing.foo(undefined, true); + verify("call", "testing", "foo", [99, true]); + + root.testing.foo(11); + verify("call", "testing", "foo", [11, null]); + + Assert.throws(() => root.testing.bar(11), + /Incorrect argument types/, + "should throw without required arg"); + + Assert.throws(() => root.testing.bar(11, true, 10), + /Incorrect argument types/, + "should throw with too many arguments"); + + root.testing.bar(true); + verify("call", "testing", "bar", [null, true]); + + root.testing.baz({prop1: "hello", prop2: 22}); + verify("call", "testing", "baz", [{prop1: "hello", prop2: 22}]); + + root.testing.baz({prop1: "hello"}); + verify("call", "testing", "baz", [{prop1: "hello", prop2: null}]); + + root.testing.baz({prop1: "hello", prop2: null}); + verify("call", "testing", "baz", [{prop1: "hello", prop2: null}]); + + Assert.throws(() => root.testing.baz({prop2: 12}), + /Property "prop1" is required/, + "should throw without required property"); + + Assert.throws(() => root.testing.baz({prop1: "hi", prop3: 12}), + /Property "prop3" is unsupported by Firefox/, + "should throw with unsupported property"); + + Assert.throws(() => root.testing.baz({prop1: "hi", prop4: 12}), + /Unexpected property "prop4"/, + "should throw with unexpected property"); + + Assert.throws(() => root.testing.baz({prop1: 12}), + /Expected string instead of 12/, + "should throw with wrong type"); + + root.testing.qux("value2"); + verify("call", "testing", "qux", ["value2"]); + + Assert.throws(() => root.testing.qux("value4"), + /Invalid enumeration value "value4"/, + "should throw for invalid enum value"); + + root.testing.quack({prop1: 12, prop2: ["value1", "value3"]}); + verify("call", "testing", "quack", [{prop1: 12, prop2: ["value1", "value3"]}]); + + Assert.throws(() => root.testing.quack({prop1: 12, prop2: ["value1", "value3", "value4"]}), + /Invalid enumeration value "value4"/, + "should throw for invalid array type"); + + function f() {} + root.testing.quora(f); + do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["call", "testing", "quora"])); + do_check_eq(tallied[3][0], f); + tallied = null; + + let g = () => 0; + root.testing.quora(g); + do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["call", "testing", "quora"])); + do_check_eq(tallied[3][0], g); + tallied = null; + + root.testing.quileute(10); + verify("call", "testing", "quileute", [null, 10]); + + Assert.throws(() => root.testing.queets(), + /queets is not a function/, + "should throw for unsupported functions"); + + root.testing.quintuplets({a: 10, b: 20, c: 30}); + verify("call", "testing", "quintuplets", [{a: 10, b: 20, c: 30}]); + + Assert.throws(() => root.testing.quintuplets({a: 10, b: 20, c: 30, d: "hi"}), + /Expected integer instead of "hi"/, + "should throw for wrong additionalProperties type"); + + root.testing.quasar({func: f}); + do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["call", "testing", "quasar"])); + do_check_eq(tallied[3][0].func, f); + tallied = null; + + root.testing.quosimodo({a: 10, b: 20, c: 30}); + verify("call", "testing", "quosimodo", [{a: 10, b: 20, c: 30}]); + tallied = null; + + Assert.throws(() => root.testing.quosimodo(10), + /Incorrect argument types/, + "should throw for wrong type"); + + root.testing.patternprop({prop1: "12", prop2: "42", Prop3: "43", foo1: "x"}); + verify("call", "testing", "patternprop", [{prop1: "12", prop2: "42", Prop3: "43", foo1: "x"}]); + tallied = null; + + root.testing.patternprop({prop1: "12"}); + verify("call", "testing", "patternprop", [{prop1: "12"}]); + tallied = null; + + Assert.throws(() => root.testing.patternprop({prop1: "12", foo1: null}), + /Expected string instead of null/, + "should throw for wrong property type"); + + Assert.throws(() => root.testing.patternprop({prop1: "xx", prop2: "yy"}), + /String "xx" must match \/\^\\d\+\$\//, + "should throw for wrong property type"); + + Assert.throws(() => root.testing.patternprop({prop1: "12", prop2: 42}), + /Expected string instead of 42/, + "should throw for wrong property type"); + + Assert.throws(() => root.testing.patternprop({prop1: "12", prop2: null}), + /Expected string instead of null/, + "should throw for wrong property type"); + + Assert.throws(() => root.testing.patternprop({prop1: "12", propx: "42"}), + /Unexpected property "propx"/, + "should throw for unexpected property"); + + Assert.throws(() => root.testing.patternprop({prop1: "12", Foo1: "x"}), + /Unexpected property "Foo1"/, + "should throw for unexpected property"); + + root.testing.pattern("DEADbeef"); + verify("call", "testing", "pattern", ["DEADbeef"]); + tallied = null; + + Assert.throws(() => root.testing.pattern("DEADcow"), + /String "DEADcow" must match \/\^\[0-9a-f\]\+\$\/i/, + "should throw for non-match"); + + root.testing.format({url: "http://foo/bar", + relativeUrl: "http://foo/bar"}); + verify("call", "testing", "format", [{url: "http://foo/bar", + relativeUrl: "http://foo/bar", + strictRelativeUrl: null}]); + tallied = null; + + root.testing.format({relativeUrl: "foo.html", strictRelativeUrl: "foo.html"}); + verify("call", "testing", "format", [{url: null, + relativeUrl: `${wrapper.url}foo.html`, + strictRelativeUrl: `${wrapper.url}foo.html`}]); + tallied = null; + + for (let format of ["url", "relativeUrl"]) { + Assert.throws(() => root.testing.format({[format]: "chrome://foo/content/"}), + /Access denied/, + "should throw for access denied"); + } + + for (let urlString of ["//foo.html", "http://foo/bar.html"]) { + Assert.throws(() => root.testing.format({strictRelativeUrl: urlString}), + /must be a relative URL/, + "should throw for non-relative URL"); + } + + const dates = [ + "2016-03-04", + "2016-03-04T08:00:00Z", + "2016-03-04T08:00:00.000Z", + "2016-03-04T08:00:00-08:00", + "2016-03-04T08:00:00.000-08:00", + "2016-03-04T08:00:00+08:00", + "2016-03-04T08:00:00.000+08:00", + "2016-03-04T08:00:00+0800", + "2016-03-04T08:00:00-0800", + ]; + dates.forEach(str => { + root.testing.formatDate({date: str}); + verify("call", "testing", "formatDate", [{date: str}]); + }); + + // Make sure that a trivial change to a valid date invalidates it. + dates.forEach(str => { + Assert.throws(() => root.testing.formatDate({date: "0" + str}), + /Invalid date string/, + "should throw for invalid iso date string"); + Assert.throws(() => root.testing.formatDate({date: str + "0"}), + /Invalid date string/, + "should throw for invalid iso date string"); + }); + + const badDates = [ + "I do not look anything like a date string", + "2016-99-99", + "2016-03-04T25:00:00Z", + ]; + badDates.forEach(str => { + Assert.throws(() => root.testing.formatDate({date: str}), + /Invalid date string/, + "should throw for invalid iso date string"); + }); + + root.testing.deep({foo: {bar: [{baz: {required: 12, optional: "42"}}]}}); + verify("call", "testing", "deep", [{foo: {bar: [{baz: {required: 12, optional: "42"}}]}}]); + tallied = null; + + Assert.throws(() => root.testing.deep({foo: {bar: [{baz: {optional: "42"}}]}}), + /Type error for parameter arg \(Error processing foo\.bar\.0\.baz: Property "required" is required\) for testing\.deep/, + "should throw with the correct object path"); + + Assert.throws(() => root.testing.deep({foo: {bar: [{baz: {required: 12, optional: 42}}]}}), + /Type error for parameter arg \(Error processing foo\.bar\.0\.baz\.optional: Expected string instead of 42\) for testing\.deep/, + "should throw with the correct object path"); + + + talliedErrors.length = 0; + + root.testing.errors({warn: "0123", ignore: "0123", default: "0123"}); + verify("call", "testing", "errors", [{warn: "0123", ignore: "0123", default: "0123"}]); + checkErrors([]); + + root.testing.errors({warn: "0123", ignore: "x123", default: "0123"}); + verify("call", "testing", "errors", [{warn: "0123", ignore: null, default: "0123"}]); + checkErrors([]); + + root.testing.errors({warn: "x123", ignore: "0123", default: "0123"}); + verify("call", "testing", "errors", [{warn: null, ignore: "0123", default: "0123"}]); + checkErrors([ + 'String "x123" must match /^\\d+$/', + ]); + + + root.testing.onFoo.addListener(f); + do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["addListener", "testing", "onFoo"])); + do_check_eq(tallied[3][0], f); + do_check_eq(JSON.stringify(tallied[3][1]), JSON.stringify([])); + tallied = null; + + root.testing.onFoo.removeListener(f); + do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["removeListener", "testing", "onFoo"])); + do_check_eq(tallied[3][0], f); + tallied = null; + + root.testing.onFoo.hasListener(f); + do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["hasListener", "testing", "onFoo"])); + do_check_eq(tallied[3][0], f); + tallied = null; + + Assert.throws(() => root.testing.onFoo.addListener(10), + /Invalid listener/, + "addListener with non-function should throw"); + + root.testing.onBar.addListener(f, 10); + do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["addListener", "testing", "onBar"])); + do_check_eq(tallied[3][0], f); + do_check_eq(JSON.stringify(tallied[3][1]), JSON.stringify([10])); + tallied = null; + + root.testing.onBar.addListener(f); + do_check_eq(JSON.stringify(tallied.slice(0, -1)), JSON.stringify(["addListener", "testing", "onBar"])); + do_check_eq(tallied[3][0], f); + do_check_eq(JSON.stringify(tallied[3][1]), JSON.stringify([1])); + tallied = null; + + Assert.throws(() => root.testing.onBar.addListener(f, "hi"), + /Incorrect argument types/, + "addListener with wrong extra parameter should throw"); + + let target = {prop1: 12, prop2: ["value1", "value3"]}; + let proxy = new Proxy(target, {}); + Assert.throws(() => root.testing.quack(proxy), + /Expected a plain JavaScript object, got a Proxy/, + "should throw when passing a Proxy"); + + if (Symbol.toStringTag) { + let stringTarget = {prop1: 12, prop2: ["value1", "value3"]}; + stringTarget[Symbol.toStringTag] = () => "[object Object]"; + let stringProxy = new Proxy(stringTarget, {}); + Assert.throws(() => root.testing.quack(stringProxy), + /Expected a plain JavaScript object, got a Proxy/, + "should throw when passing a Proxy"); + } + + + root.testing.localize({foo: "__MSG_foo__", bar: "__MSG_foo__", url: "__MSG_http://example.com/__"}); + verify("call", "testing", "localize", [{foo: "FOO", bar: "__MSG_foo__", url: "http://example.com/"}]); + tallied = null; + + + Assert.throws(() => root.testing.localize({url: "__MSG_/foo/bar__"}), + /\/FOO\/BAR is not a valid URL\./, + "should throw for invalid URL"); + + + root.testing.extended1({prop1: "foo", prop2: "bar"}); + verify("call", "testing", "extended1", [{prop1: "foo", prop2: "bar"}]); + tallied = null; + + Assert.throws(() => root.testing.extended1({prop1: "foo", prop2: 12}), + /Expected string instead of 12/, + "should throw for wrong property type"); + + Assert.throws(() => root.testing.extended1({prop1: "foo"}), + /Property "prop2" is required/, + "should throw for missing property"); + + Assert.throws(() => root.testing.extended1({prop1: "foo", prop2: "bar", prop3: "xxx"}), + /Unexpected property "prop3"/, + "should throw for extra property"); + + + root.testing.extended2("foo"); + verify("call", "testing", "extended2", ["foo"]); + tallied = null; + + root.testing.extended2(12); + verify("call", "testing", "extended2", [12]); + tallied = null; + + Assert.throws(() => root.testing.extended2(true), + /Incorrect argument types/, + "should throw for wrong argument type"); + + root.testing.prop3.sub_foo(); + verify("call", "testing.prop3", "sub_foo", []); + tallied = null; + + Assert.throws(() => root.testing.prop4.sub_foo(), + /root.testing.prop4 is undefined/, + "should throw for unsupported submodule"); + + root.foreign.foreignRef.sub_foo(); + verify("call", "foreign.foreignRef", "sub_foo", []); + tallied = null; +}); + +let deprecatedJson = [ + {namespace: "deprecated", + + properties: { + accessor: { + type: "string", + writable: true, + deprecated: "This is not the property you are looking for", + }, + }, + + types: [ + { + "id": "Type", + "type": "string", + }, + ], + + functions: [ + { + name: "property", + type: "function", + parameters: [ + { + name: "arg", + type: "object", + properties: { + foo: { + type: "string", + }, + }, + additionalProperties: { + type: "any", + deprecated: "Unknown property", + }, + }, + ], + }, + + { + name: "value", + type: "function", + parameters: [ + { + name: "arg", + choices: [ + { + type: "integer", + }, + { + type: "string", + deprecated: "Please use an integer, not ${value}", + }, + ], + }, + ], + }, + + { + name: "choices", + type: "function", + parameters: [ + { + name: "arg", + deprecated: "You have no choices", + choices: [ + { + type: "integer", + }, + ], + }, + ], + }, + + { + name: "ref", + type: "function", + parameters: [ + { + name: "arg", + choices: [ + { + $ref: "Type", + deprecated: "Deprecated alias", + }, + ], + }, + ], + }, + + { + name: "method", + type: "function", + deprecated: "Do not call this method", + parameters: [ + ], + }, + ], + + events: [ + { + name: "onDeprecated", + type: "function", + deprecated: "This event does not work", + }, + ], + }, +]; + +add_task(function* testDeprecation() { + let url = "data:," + JSON.stringify(deprecatedJson); + yield Schemas.load(url); + + let root = {}; + Schemas.inject(root, wrapper); + + talliedErrors.length = 0; + + + root.deprecated.property({foo: "bar", xxx: "any", yyy: "property"}); + verify("call", "deprecated", "property", [{foo: "bar", xxx: "any", yyy: "property"}]); + checkErrors([ + "Error processing xxx: Unknown property", + "Error processing yyy: Unknown property", + ]); + + root.deprecated.value(12); + verify("call", "deprecated", "value", [12]); + checkErrors([]); + + root.deprecated.value("12"); + verify("call", "deprecated", "value", ["12"]); + checkErrors(["Please use an integer, not \"12\""]); + + root.deprecated.choices(12); + verify("call", "deprecated", "choices", [12]); + checkErrors(["You have no choices"]); + + root.deprecated.ref("12"); + verify("call", "deprecated", "ref", ["12"]); + checkErrors(["Deprecated alias"]); + + root.deprecated.method(); + verify("call", "deprecated", "method", []); + checkErrors(["Do not call this method"]); + + + void root.deprecated.accessor; + verify("get", "deprecated", "accessor", null); + checkErrors(["This is not the property you are looking for"]); + + root.deprecated.accessor = "x"; + verify("set", "deprecated", "accessor", "x"); + checkErrors(["This is not the property you are looking for"]); + + + root.deprecated.onDeprecated.addListener(() => {}); + checkErrors(["This event does not work"]); + + root.deprecated.onDeprecated.removeListener(() => {}); + checkErrors(["This event does not work"]); + + root.deprecated.onDeprecated.hasListener(() => {}); + checkErrors(["This event does not work"]); +}); + + +let choicesJson = [ + {namespace: "choices", + + types: [ + ], + + functions: [ + { + name: "meh", + type: "function", + parameters: [ + { + name: "arg", + choices: [ + { + type: "string", + enum: ["foo", "bar", "baz"], + }, + { + type: "string", + pattern: "florg.*meh", + }, + { + type: "integer", + minimum: 12, + maximum: 42, + }, + ], + }, + ], + }, + + { + name: "foo", + type: "function", + parameters: [ + { + name: "arg", + choices: [ + { + type: "object", + properties: { + blurg: { + type: "string", + unsupported: true, + optional: true, + }, + }, + additionalProperties: { + type: "string", + }, + }, + { + type: "string", + }, + { + type: "array", + minItems: 2, + maxItems: 3, + items: { + type: "integer", + }, + }, + ], + }, + ], + }, + + { + name: "bar", + type: "function", + parameters: [ + { + name: "arg", + choices: [ + { + type: "object", + properties: { + baz: { + type: "string", + }, + }, + }, + { + type: "array", + items: { + type: "integer", + }, + }, + ], + }, + ], + }, + ]}, +]; + +add_task(function* testChoices() { + let url = "data:," + JSON.stringify(choicesJson); + yield Schemas.load(url); + + let root = {}; + Schemas.inject(root, wrapper); + + talliedErrors.length = 0; + + Assert.throws(() => root.choices.meh("frog"), + /Value must either: be one of \["foo", "bar", "baz"\], match the pattern \/florg\.\*meh\/, or be an integer value/); + + Assert.throws(() => root.choices.meh(4), + /be a string value, or be at least 12/); + + Assert.throws(() => root.choices.meh(43), + /be a string value, or be no greater than 42/); + + + Assert.throws(() => root.choices.foo([]), + /be an object value, be a string value, or have at least 2 items/); + + Assert.throws(() => root.choices.foo([1, 2, 3, 4]), + /be an object value, be a string value, or have at most 3 items/); + + Assert.throws(() => root.choices.foo({foo: 12}), + /.foo must be a string value, be a string value, or be an array value/); + + Assert.throws(() => root.choices.foo({blurg: "foo"}), + /not contain an unsupported "blurg" property, be a string value, or be an array value/); + + + Assert.throws(() => root.choices.bar({}), + /contain the required "baz" property, or be an array value/); + + Assert.throws(() => root.choices.bar({baz: "x", quux: "y"}), + /not contain an unexpected "quux" property, or be an array value/); + + Assert.throws(() => root.choices.bar({baz: "x", quux: "y", foo: "z"}), + /not contain the unexpected properties \[foo, quux\], or be an array value/); +}); + + +let permissionsJson = [ + {namespace: "noPerms", + + types: [], + + functions: [ + { + name: "noPerms", + type: "function", + parameters: [], + }, + + { + name: "fooPerm", + type: "function", + permissions: ["foo"], + parameters: [], + }, + ]}, + + {namespace: "fooPerm", + + permissions: ["foo"], + + types: [], + + functions: [ + { + name: "noPerms", + type: "function", + parameters: [], + }, + + { + name: "fooBarPerm", + type: "function", + permissions: ["foo.bar"], + parameters: [], + }, + ]}, +]; + +add_task(function* testPermissions() { + let url = "data:," + JSON.stringify(permissionsJson); + yield Schemas.load(url); + + let root = {}; + Schemas.inject(root, wrapper); + + equal(typeof root.noPerms, "object", "noPerms namespace should exist"); + equal(typeof root.noPerms.noPerms, "function", "noPerms.noPerms method should exist"); + + ok(!("fooPerm" in root.noPerms), "noPerms.fooPerm should not method exist"); + + ok(!("fooPerm" in root), "fooPerm namespace should not exist"); + + + do_print('Add "foo" permission'); + permissions.add("foo"); + + root = {}; + Schemas.inject(root, wrapper); + + equal(typeof root.noPerms, "object", "noPerms namespace should exist"); + equal(typeof root.noPerms.noPerms, "function", "noPerms.noPerms method should exist"); + equal(typeof root.noPerms.fooPerm, "function", "noPerms.fooPerm method should exist"); + + equal(typeof root.fooPerm, "object", "fooPerm namespace should exist"); + equal(typeof root.fooPerm.noPerms, "function", "noPerms.noPerms method should exist"); + + ok(!("fooBarPerm" in root.fooPerm), "fooPerm.fooBarPerm method should not exist"); + + + do_print('Add "foo.bar" permission'); + permissions.add("foo.bar"); + + root = {}; + Schemas.inject(root, wrapper); + + equal(typeof root.noPerms, "object", "noPerms namespace should exist"); + equal(typeof root.noPerms.noPerms, "function", "noPerms.noPerms method should exist"); + equal(typeof root.noPerms.fooPerm, "function", "noPerms.fooPerm method should exist"); + + equal(typeof root.fooPerm, "object", "fooPerm namespace should exist"); + equal(typeof root.fooPerm.noPerms, "function", "noPerms.noPerms method should exist"); + equal(typeof root.fooPerm.fooBarPerm, "function", "noPerms.fooBarPerm method should exist"); +}); + +let nestedNamespaceJson = [ + { + "namespace": "nested.namespace", + "types": [ + { + "id": "CustomType", + "type": "object", + "events": [ + { + "name": "onEvent", + }, + ], + "properties": { + "url": { + "type": "string", + }, + }, + "functions": [ + { + "name": "functionOnCustomType", + "type": "function", + "parameters": [ + { + "name": "title", + "type": "string", + }, + ], + }, + ], + }, + ], + "properties": { + "instanceOfCustomType": { + "$ref": "CustomType", + }, + }, + "functions": [ + { + "name": "create", + "type": "function", + "parameters": [ + { + "name": "title", + "type": "string", + }, + ], + }, + ], + }, +]; + +add_task(function* testNestedNamespace() { + let url = "data:," + JSON.stringify(nestedNamespaceJson); + + yield Schemas.load(url); + + let root = {}; + Schemas.inject(root, wrapper); + + talliedErrors.length = 0; + + ok(root.nested, "The root object contains the first namespace level"); + ok(root.nested.namespace, "The first level object contains the second namespace level"); + + ok(root.nested.namespace.create, "Got the expected function in the nested namespace"); + do_check_eq(typeof root.nested.namespace.create, "function", + "The property is a function as expected"); + + let {instanceOfCustomType} = root.nested.namespace; + + ok(instanceOfCustomType, + "Got the expected instance of the CustomType defined in the schema"); + ok(instanceOfCustomType.functionOnCustomType, + "Got the expected method in the CustomType instance"); + + // TODO: test support events and properties in a SubModuleType defined in the schema, + // once implemented, e.g.: + // + // ok(instanceOfCustomType.url, + // "Got the expected property defined in the CustomType instance) + // + // ok(instanceOfCustomType.onEvent && + // instanceOfCustomType.onEvent.addListener && + // typeof instanceOfCustomType.onEvent.addListener == "function", + // "Got the expected event defined in the CustomType instance"); +}); + +add_task(function* testLocalAPIImplementation() { + let countGet2 = 0; + let countProp3 = 0; + let countProp3SubFoo = 0; + + let testingApiObj = { + get PROP1() { + // PROP1 is a schema-defined constant. + throw new Error("Unexpected get PROP1"); + }, + get prop2() { + ++countGet2; + return "prop2 val"; + }, + get prop3() { + throw new Error("Unexpected get prop3"); + }, + set prop3(v) { + // prop3 is a submodule, defined as a function, so the API should not pass + // through assignment to prop3. + throw new Error("Unexpected set prop3"); + }, + }; + let submoduleApiObj = { + get sub_foo() { + ++countProp3; + return () => { + return ++countProp3SubFoo; + }; + }, + }; + + let localWrapper = { + shouldInject(ns) { + return ns == "testing" || ns == "testing.prop3"; + }, + getImplementation(ns, name) { + do_check_true(ns == "testing" || ns == "testing.prop3"); + if (ns == "testing.prop3" && name == "sub_foo") { + // It is fine to use `null` here because we don't call async functions. + return new LocalAPIImplementation(submoduleApiObj, name, null); + } + // It is fine to use `null` here because we don't call async functions. + return new LocalAPIImplementation(testingApiObj, name, null); + }, + }; + + let root = {}; + Schemas.inject(root, localWrapper); + do_check_eq(countGet2, 0); + do_check_eq(countProp3, 0); + do_check_eq(countProp3SubFoo, 0); + + do_check_eq(root.testing.PROP1, 20); + + do_check_eq(root.testing.prop2, "prop2 val"); + do_check_eq(countGet2, 1); + + do_check_eq(root.testing.prop2, "prop2 val"); + do_check_eq(countGet2, 2); + + do_print(JSON.stringify(root.testing)); + do_check_eq(root.testing.prop3.sub_foo(), 1); + do_check_eq(countProp3, 1); + do_check_eq(countProp3SubFoo, 1); + + do_check_eq(root.testing.prop3.sub_foo(), 2); + do_check_eq(countProp3, 2); + do_check_eq(countProp3SubFoo, 2); + + root.testing.prop3.sub_foo = () => { return "overwritten"; }; + do_check_eq(root.testing.prop3.sub_foo(), "overwritten"); + + root.testing.prop3 = {sub_foo() { return "overwritten again"; }}; + do_check_eq(root.testing.prop3.sub_foo(), "overwritten again"); + do_check_eq(countProp3SubFoo, 2); +}); + + +let defaultsJson = [ + {namespace: "defaultsJson", + + types: [], + + functions: [ + { + name: "defaultFoo", + type: "function", + parameters: [ + {name: "arg", type: "object", optional: true, properties: { + prop1: {type: "integer", optional: true}, + }, default: {prop1: 1}}, + ], + returns: { + type: "object", + }, + }, + ]}, +]; + +add_task(function* testDefaults() { + let url = "data:," + JSON.stringify(defaultsJson); + yield Schemas.load(url); + + let testingApiObj = { + defaultFoo: function(arg) { + if (Object.keys(arg) != "prop1") { + throw new Error(`Received the expected default object, default: ${JSON.stringify(arg)}`); + } + arg.newProp = 1; + return arg; + }, + }; + + let localWrapper = { + shouldInject(ns) { + return true; + }, + getImplementation(ns, name) { + return new LocalAPIImplementation(testingApiObj, name, null); + }, + }; + + let root = {}; + Schemas.inject(root, localWrapper); + + deepEqual(root.defaultsJson.defaultFoo(), {prop1: 1, newProp: 1}); + deepEqual(root.defaultsJson.defaultFoo({prop1: 2}), {prop1: 2, newProp: 1}); + deepEqual(root.defaultsJson.defaultFoo(), {prop1: 1, newProp: 1}); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_allowed_contexts.js b/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_allowed_contexts.js new file mode 100644 index 000000000..606459764 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_allowed_contexts.js @@ -0,0 +1,147 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/Schemas.jsm"); + +let schemaJson = [ + { + namespace: "noAllowedContexts", + properties: { + prop1: {type: "object"}, + prop2: {type: "object", allowedContexts: ["test_zero", "test_one"]}, + prop3: {type: "number", value: 1}, + prop4: {type: "number", value: 1, allowedContexts: ["numeric_one"]}, + }, + }, + { + namespace: "defaultContexts", + defaultContexts: ["test_two"], + properties: { + prop1: {type: "object"}, + prop2: {type: "object", allowedContexts: ["test_three"]}, + prop3: {type: "number", value: 1}, + prop4: {type: "number", value: 1, allowedContexts: ["numeric_two"]}, + }, + }, + { + namespace: "withAllowedContexts", + allowedContexts: ["test_four"], + properties: { + prop1: {type: "object"}, + prop2: {type: "object", allowedContexts: ["test_five"]}, + prop3: {type: "number", value: 1}, + prop4: {type: "number", value: 1, allowedContexts: ["numeric_three"]}, + }, + }, + { + namespace: "withAllowedContextsAndDefault", + allowedContexts: ["test_six"], + defaultContexts: ["test_seven"], + properties: { + prop1: {type: "object"}, + prop2: {type: "object", allowedContexts: ["test_eight"]}, + prop3: {type: "number", value: 1}, + prop4: {type: "number", value: 1, allowedContexts: ["numeric_four"]}, + }, + }, + { + namespace: "with_submodule", + defaultContexts: ["test_nine"], + types: [{ + id: "subtype", + type: "object", + functions: [{ + name: "noAllowedContexts", + type: "function", + parameters: [], + }, { + name: "allowedContexts", + allowedContexts: ["test_ten"], + type: "function", + parameters: [], + }], + }], + properties: { + prop1: {$ref: "subtype"}, + prop2: {$ref: "subtype", allowedContexts: ["test_eleven"]}, + }, + }, +]; +add_task(function* testRestrictions() { + let url = "data:," + JSON.stringify(schemaJson); + yield Schemas.load(url); + let results = {}; + let localWrapper = { + shouldInject(ns, name, allowedContexts) { + name = name === null ? ns : ns + "." + name; + results[name] = allowedContexts.join(","); + return true; + }, + getImplementation() { + // The actual implementation is not significant for this test. + // Let's take this opportunity to see if schema generation is free of + // exceptions even when somehow getImplementation does not return an + // implementation. + }, + }; + + let root = {}; + Schemas.inject(root, localWrapper); + + function verify(path, expected) { + let obj = root; + for (let thing of path.split(".")) { + try { + obj = obj[thing]; + } catch (e) { + // Blech. + } + } + + let result = results[path]; + equal(result, expected); + } + + verify("noAllowedContexts", ""); + verify("noAllowedContexts.prop1", ""); + verify("noAllowedContexts.prop2", "test_zero,test_one"); + verify("noAllowedContexts.prop3", ""); + verify("noAllowedContexts.prop4", "numeric_one"); + + verify("defaultContexts", ""); + verify("defaultContexts.prop1", "test_two"); + verify("defaultContexts.prop2", "test_three"); + verify("defaultContexts.prop3", "test_two"); + verify("defaultContexts.prop4", "numeric_two"); + + verify("withAllowedContexts", "test_four"); + verify("withAllowedContexts.prop1", ""); + verify("withAllowedContexts.prop2", "test_five"); + verify("withAllowedContexts.prop3", ""); + verify("withAllowedContexts.prop4", "numeric_three"); + + verify("withAllowedContextsAndDefault", "test_six"); + verify("withAllowedContextsAndDefault.prop1", "test_seven"); + verify("withAllowedContextsAndDefault.prop2", "test_eight"); + verify("withAllowedContextsAndDefault.prop3", "test_seven"); + verify("withAllowedContextsAndDefault.prop4", "numeric_four"); + + verify("with_submodule", ""); + verify("with_submodule.prop1", "test_nine"); + verify("with_submodule.prop1.noAllowedContexts", "test_nine"); + verify("with_submodule.prop1.allowedContexts", "test_ten"); + verify("with_submodule.prop2", "test_eleven"); + // Note: test_nine inherits allowed contexts from the namespace, not from + // submodule. There is no "defaultContexts" for submodule types to not + // complicate things. + verify("with_submodule.prop1.noAllowedContexts", "test_nine"); + verify("with_submodule.prop1.allowedContexts", "test_ten"); + + // This is a constant, so it does not matter that getImplementation does not + // return an implementation since the API injector should take care of it. + equal(root.noAllowedContexts.prop3, 1); + + Assert.throws(() => root.noAllowedContexts.prop1, + /undefined/, + "Should throw when the implementation is absent."); +}); + diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_api_injection.js b/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_api_injection.js new file mode 100644 index 000000000..36d88d722 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_api_injection.js @@ -0,0 +1,102 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/ExtensionCommon.jsm"); +Components.utils.import("resource://gre/modules/Schemas.jsm"); + +let { + BaseContext, + SchemaAPIManager, +} = ExtensionCommon; + +let nestedNamespaceJson = [ + { + "namespace": "backgroundAPI.testnamespace", + "functions": [ + { + "name": "create", + "type": "function", + "parameters": [ + { + "name": "title", + "type": "string", + }, + ], + "returns": { + "type": "string", + }, + }, + ], + }, + { + "namespace": "noBackgroundAPI.testnamespace", + "functions": [ + { + "name": "create", + "type": "function", + "parameters": [ + { + "name": "title", + "type": "string", + }, + ], + }, + ], + }, +]; + +let global = this; +class StubContext extends BaseContext { + constructor() { + let fakeExtension = {id: "test@web.extension"}; + super("addon_child", fakeExtension); + this.sandbox = Cu.Sandbox(global); + this.viewType = "background"; + } + + get cloneScope() { + return this.sandbox; + } +} + +add_task(function* testSchemaAPIInjection() { + let url = "data:," + JSON.stringify(nestedNamespaceJson); + + // Load the schema of the fake APIs. + yield Schemas.load(url); + + let apiManager = new SchemaAPIManager("addon"); + + // Register an API that will skip the background page. + apiManager.registerSchemaAPI("noBackgroundAPI.testnamespace", "addon_child", context => { + // This API should not be available in this context, return null so that + // the schema wrapper is removed as well. + return null; + }); + + // Register an API that will skip any but the background page. + apiManager.registerSchemaAPI("backgroundAPI.testnamespace", "addon_child", context => { + if (context.viewType === "background") { + return { + backgroundAPI: { + testnamespace: { + create(title) { + return title; + }, + }, + }, + }; + } + + // This API should not be available in this context, return null so that + // the schema wrapper is removed as well. + return null; + }); + + let context = new StubContext(); + let browserObj = {}; + apiManager.generateAPIs(context, browserObj); + + do_check_eq(browserObj.noBackgroundAPI, undefined); + const res = browserObj.backgroundAPI.testnamespace.create("param-value"); + do_check_eq(res, "param-value"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_async.js b/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_async.js new file mode 100644 index 000000000..6397d1f96 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_schemas_async.js @@ -0,0 +1,232 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/ExtensionCommon.jsm"); +Components.utils.import("resource://gre/modules/Schemas.jsm"); + +let {BaseContext, LocalAPIImplementation} = ExtensionCommon; + +let schemaJson = [ + { + namespace: "testnamespace", + functions: [{ + name: "one_required", + type: "function", + parameters: [{ + name: "first", + type: "function", + parameters: [], + }], + }, { + name: "one_optional", + type: "function", + parameters: [{ + name: "first", + type: "function", + parameters: [], + optional: true, + }], + }, { + name: "async_required", + type: "function", + async: "first", + parameters: [{ + name: "first", + type: "function", + parameters: [], + }], + }, { + name: "async_optional", + type: "function", + async: "first", + parameters: [{ + name: "first", + type: "function", + parameters: [], + optional: true, + }], + }], + }, +]; + +const global = this; +class StubContext extends BaseContext { + constructor() { + let fakeExtension = {id: "test@web.extension"}; + super("testEnv", fakeExtension); + this.sandbox = Cu.Sandbox(global); + } + + get cloneScope() { + return this.sandbox; + } + + get principal() { + return Cu.getObjectPrincipal(this.sandbox); + } +} + +let context; + +function generateAPIs(extraWrapper, apiObj) { + context = new StubContext(); + let localWrapper = { + shouldInject() { + return true; + }, + getImplementation(namespace, name) { + return new LocalAPIImplementation(apiObj, name, context); + }, + }; + Object.assign(localWrapper, extraWrapper); + + let root = {}; + Schemas.inject(root, localWrapper); + return root.testnamespace; +} + +add_task(function* testParameterValidation() { + yield Schemas.load("data:," + JSON.stringify(schemaJson)); + + let testnamespace; + function assertThrows(name, ...args) { + Assert.throws(() => testnamespace[name](...args), + /Incorrect argument types/, + `Expected testnamespace.${name}(${args.map(String).join(", ")}) to throw.`); + } + function assertNoThrows(name, ...args) { + try { + testnamespace[name](...args); + } catch (e) { + do_print(`testnamespace.${name}(${args.map(String).join(", ")}) unexpectedly threw.`); + throw new Error(e); + } + } + let cb = () => {}; + + for (let isChromeCompat of [true, false]) { + do_print(`Testing API validation with isChromeCompat=${isChromeCompat}`); + testnamespace = generateAPIs({ + isChromeCompat, + }, { + one_required() {}, + one_optional() {}, + async_required() {}, + async_optional() {}, + }); + + assertThrows("one_required"); + assertThrows("one_required", null); + assertNoThrows("one_required", cb); + assertThrows("one_required", cb, null); + assertThrows("one_required", cb, cb); + + assertNoThrows("one_optional"); + assertNoThrows("one_optional", null); + assertNoThrows("one_optional", cb); + assertThrows("one_optional", cb, null); + assertThrows("one_optional", cb, cb); + + // Schema-based validation happens before an async method is called, so + // errors should be thrown synchronously. + + // The parameter was declared as required, but there was also an "async" + // attribute with the same value as the parameter name, so the callback + // parameter is actually optional. + assertNoThrows("async_required"); + assertNoThrows("async_required", null); + assertNoThrows("async_required", cb); + assertThrows("async_required", cb, null); + assertThrows("async_required", cb, cb); + + assertNoThrows("async_optional"); + assertNoThrows("async_optional", null); + assertNoThrows("async_optional", cb); + assertThrows("async_optional", cb, null); + assertThrows("async_optional", cb, cb); + } +}); + +add_task(function* testAsyncResults() { + yield Schemas.load("data:," + JSON.stringify(schemaJson)); + function* runWithCallback(func) { + do_print(`Calling testnamespace.${func.name}, expecting callback with result`); + return yield new Promise(resolve => { + let result = "uninitialized value"; + let returnValue = func(reply => { + result = reply; + resolve(result); + }); + // When a callback is given, the return value must be missing. + do_check_eq(returnValue, undefined); + // Callback must be called asynchronously. + do_check_eq(result, "uninitialized value"); + }); + } + + function* runFailCallback(func) { + do_print(`Calling testnamespace.${func.name}, expecting callback with error`); + return yield new Promise(resolve => { + func(reply => { + do_check_eq(reply, undefined); + resolve(context.lastError.message); // eslint-disable-line no-undef + }); + }); + } + + for (let isChromeCompat of [true, false]) { + do_print(`Testing API invocation with isChromeCompat=${isChromeCompat}`); + let testnamespace = generateAPIs({ + isChromeCompat, + }, { + async_required(cb) { + do_check_eq(cb, undefined); + return Promise.resolve(1); + }, + async_optional(cb) { + do_check_eq(cb, undefined); + return Promise.resolve(2); + }, + }); + if (!isChromeCompat) { // No promises for chrome. + do_print("testnamespace.async_required should be a Promise"); + let promise = testnamespace.async_required(); + do_check_true(promise instanceof context.cloneScope.Promise); + do_check_eq(yield promise, 1); + + do_print("testnamespace.async_optional should be a Promise"); + promise = testnamespace.async_optional(); + do_check_true(promise instanceof context.cloneScope.Promise); + do_check_eq(yield promise, 2); + } + + do_check_eq(yield* runWithCallback(testnamespace.async_required), 1); + do_check_eq(yield* runWithCallback(testnamespace.async_optional), 2); + + let otherSandbox = Cu.Sandbox(null, {}); + let errorFactories = [ + msg => { throw new context.cloneScope.Error(msg); }, + msg => context.cloneScope.Promise.reject({message: msg}), + msg => Cu.evalInSandbox(`throw new Error("${msg}")`, otherSandbox), + msg => Cu.evalInSandbox(`Promise.reject({message: "${msg}"})`, otherSandbox), + ]; + for (let makeError of errorFactories) { + do_print(`Testing callback/promise with error caused by: ${makeError}`); + testnamespace = generateAPIs({ + isChromeCompat, + }, { + async_required() { return makeError("ONE"); }, + async_optional() { return makeError("TWO"); }, + }); + + if (!isChromeCompat) { // No promises for chrome. + yield Assert.rejects(testnamespace.async_required(), /ONE/, + "should reject testnamespace.async_required()").catch(() => {}); + yield Assert.rejects(testnamespace.async_optional(), /TWO/, + "should reject testnamespace.async_optional()").catch(() => {}); + } + + do_check_eq(yield* runFailCallback(testnamespace.async_required), "ONE"); + do_check_eq(yield* runFailCallback(testnamespace.async_optional), "TWO"); + } + } +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_simple.js b/toolkit/components/webextensions/test/xpcshell/test_ext_simple.js new file mode 100644 index 000000000..91b10354c --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_simple.js @@ -0,0 +1,69 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(function* test_simple() { + let extensionData = { + manifest: { + "name": "Simple extension test", + "version": "1.0", + "manifest_version": 2, + "description": "", + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + yield extension.unload(); +}); + +add_task(function* test_background() { + function background() { + browser.test.log("running background script"); + + browser.test.onMessage.addListener((x, y) => { + browser.test.assertEq(x, 10, "x is 10"); + browser.test.assertEq(y, 20, "y is 20"); + + browser.test.notifyPass("background test passed"); + }); + + browser.test.sendMessage("running", 1); + } + + let extensionData = { + background, + manifest: { + "name": "Simple extension test", + "version": "1.0", + "manifest_version": 2, + "description": "", + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + + let [, x] = yield Promise.all([extension.startup(), extension.awaitMessage("running")]); + equal(x, 1, "got correct value from extension"); + + extension.sendMessage(10, 20); + yield extension.awaitFinish(); + yield extension.unload(); +}); + +add_task(function* test_extensionTypes() { + let extensionData = { + background: function() { + browser.test.assertEq(typeof browser.extensionTypes, "object", "browser.extensionTypes exists"); + browser.test.assertEq(typeof browser.extensionTypes.RunAt, "object", "browser.extensionTypes.RunAt exists"); + browser.test.notifyPass("extentionTypes test passed"); + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + + yield extension.startup(); + yield extension.awaitFinish(); + yield extension.unload(); +}); + diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_storage.js b/toolkit/components/webextensions/test/xpcshell/test_ext_storage.js new file mode 100644 index 000000000..df46dfb63 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_storage.js @@ -0,0 +1,334 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const STORAGE_SYNC_PREF = "webextensions.storage.sync.enabled"; +Cu.import("resource://gre/modules/Preferences.jsm"); + +/** + * Utility function to ensure that all supported APIs for getting are + * tested. + * + * @param {string} areaName + * either "local" or "sync" according to what we want to test + * @param {string} prop + * "key" to look up using the storage API + * @param {Object} value + * "value" to compare against + */ +async function checkGetImpl(areaName, prop, value) { + let storage = browser.storage[areaName]; + + let data = await storage.get(null); + browser.test.assertEq(value, data[prop], `null getter worked for ${prop} in ${areaName}`); + + data = await storage.get(prop); + browser.test.assertEq(value, data[prop], `string getter worked for ${prop} in ${areaName}`); + + data = await storage.get([prop]); + browser.test.assertEq(value, data[prop], `array getter worked for ${prop} in ${areaName}`); + + data = await storage.get({[prop]: undefined}); + browser.test.assertEq(value, data[prop], `object getter worked for ${prop} in ${areaName}`); +} + +add_task(function* test_local_cache_invalidation() { + function background(checkGet) { + browser.test.onMessage.addListener(async msg => { + if (msg === "set-initial") { + await browser.storage.local.set({"test-prop1": "value1", "test-prop2": "value2"}); + browser.test.sendMessage("set-initial-done"); + } else if (msg === "check") { + await checkGet("local", "test-prop1", "value1"); + await checkGet("local", "test-prop2", "value2"); + browser.test.sendMessage("check-done"); + } + }); + + browser.test.sendMessage("ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["storage"], + }, + background: `(${background})(${checkGetImpl})`, + }); + + yield extension.startup(); + yield extension.awaitMessage("ready"); + + extension.sendMessage("set-initial"); + yield extension.awaitMessage("set-initial-done"); + + Services.obs.notifyObservers(null, "extension-invalidate-storage-cache", ""); + + extension.sendMessage("check"); + yield extension.awaitMessage("check-done"); + + yield extension.unload(); +}); + +add_task(function* test_config_flag_needed() { + function background() { + let promises = []; + let apiTests = [ + {method: "get", args: ["foo"]}, + {method: "set", args: [{foo: "bar"}]}, + {method: "remove", args: ["foo"]}, + {method: "clear", args: []}, + ]; + apiTests.forEach(testDef => { + promises.push(browser.test.assertRejects( + browser.storage.sync[testDef.method](...testDef.args), + "Please set webextensions.storage.sync.enabled to true in about:config", + `storage.sync.${testDef.method} is behind a flag`)); + }); + + Promise.all(promises).then(() => browser.test.notifyPass("flag needed")); + } + + ok(!Preferences.get(STORAGE_SYNC_PREF)); + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["storage"], + }, + background: `(${background})(${checkGetImpl})`, + }); + + yield extension.startup(); + yield extension.awaitFinish("flag needed"); + yield extension.unload(); +}); + +add_task(function* test_reloading_extensions_works() { + // Just some random extension ID that we can re-use + const extensionId = "my-extension-id@1"; + + function loadExtension() { + function background() { + browser.storage.sync.set({"a": "b"}).then(() => { + browser.test.notifyPass("set-works"); + }); + } + + return ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["storage"], + }, + background: `(${background})()`, + }, extensionId); + } + + Preferences.set(STORAGE_SYNC_PREF, true); + + let extension1 = loadExtension(); + + yield extension1.startup(); + yield extension1.awaitFinish("set-works"); + yield extension1.unload(); + + let extension2 = loadExtension(); + + yield extension2.startup(); + yield extension2.awaitFinish("set-works"); + yield extension2.unload(); + + Preferences.reset(STORAGE_SYNC_PREF); +}); + +do_register_cleanup(() => { + Preferences.reset(STORAGE_SYNC_PREF); +}); + +add_task(function* test_backgroundScript() { + async function backgroundScript(checkGet) { + let globalChanges, gResolve; + function clearGlobalChanges() { + globalChanges = new Promise(resolve => { gResolve = resolve; }); + } + clearGlobalChanges(); + let expectedAreaName; + + browser.storage.onChanged.addListener((changes, areaName) => { + browser.test.assertEq(expectedAreaName, areaName, + "Expected area name received by listener"); + gResolve(changes); + }); + + async function checkChanges(areaName, changes, message) { + function checkSub(obj1, obj2) { + for (let prop in obj1) { + browser.test.assertTrue(obj1[prop] !== undefined, + `checkChanges ${areaName} ${prop} is missing (${message})`); + browser.test.assertTrue(obj2[prop] !== undefined, + `checkChanges ${areaName} ${prop} is missing (${message})`); + browser.test.assertEq(obj1[prop].oldValue, obj2[prop].oldValue, + `checkChanges ${areaName} ${prop} old (${message})`); + browser.test.assertEq(obj1[prop].newValue, obj2[prop].newValue, + `checkChanges ${areaName} ${prop} new (${message})`); + } + } + + const recentChanges = await globalChanges; + checkSub(changes, recentChanges); + checkSub(recentChanges, changes); + clearGlobalChanges(); + } + + /* eslint-disable dot-notation */ + async function runTests(areaName) { + expectedAreaName = areaName; + let storage = browser.storage[areaName]; + // Set some data and then test getters. + try { + await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); + await checkChanges(areaName, + {"test-prop1": {newValue: "value1"}, "test-prop2": {newValue: "value2"}}, + "set (a)"); + + await checkGet(areaName, "test-prop1", "value1"); + await checkGet(areaName, "test-prop2", "value2"); + + let data = await storage.get({"test-prop1": undefined, "test-prop2": undefined, "other": "default"}); + browser.test.assertEq("value1", data["test-prop1"], "prop1 correct (a)"); + browser.test.assertEq("value2", data["test-prop2"], "prop2 correct (a)"); + browser.test.assertEq("default", data["other"], "other correct"); + + data = await storage.get(["test-prop1", "test-prop2", "other"]); + browser.test.assertEq("value1", data["test-prop1"], "prop1 correct (b)"); + browser.test.assertEq("value2", data["test-prop2"], "prop2 correct (b)"); + browser.test.assertFalse("other" in data, "other correct"); + + // Remove data in various ways. + await storage.remove("test-prop1"); + await checkChanges(areaName, {"test-prop1": {oldValue: "value1"}}, "remove string"); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse("test-prop1" in data, "prop1 absent (remove string)"); + browser.test.assertTrue("test-prop2" in data, "prop2 present (remove string)"); + + await storage.set({"test-prop1": "value1"}); + await checkChanges(areaName, {"test-prop1": {newValue: "value1"}}, "set (c)"); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertEq(data["test-prop1"], "value1", "prop1 correct (c)"); + browser.test.assertEq(data["test-prop2"], "value2", "prop2 correct (c)"); + + await storage.remove(["test-prop1", "test-prop2"]); + await checkChanges(areaName, + {"test-prop1": {oldValue: "value1"}, "test-prop2": {oldValue: "value2"}}, + "remove array"); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse("test-prop1" in data, "prop1 absent (remove array)"); + browser.test.assertFalse("test-prop2" in data, "prop2 absent (remove array)"); + + // test storage.clear + await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); + // Make sure that set() handler happened before we clear the + // promise again. + await globalChanges; + + clearGlobalChanges(); + await storage.clear(); + + await checkChanges(areaName, + {"test-prop1": {oldValue: "value1"}, "test-prop2": {oldValue: "value2"}}, + "clear"); + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse("test-prop1" in data, "prop1 absent (clear)"); + browser.test.assertFalse("test-prop2" in data, "prop2 absent (clear)"); + + // Make sure we can store complex JSON data. + // known previous values + await storage.set({"test-prop1": "value1", "test-prop2": "value2"}); + + // Make sure the set() handler landed. + await globalChanges; + + clearGlobalChanges(); + await storage.set({ + "test-prop1": { + str: "hello", + bool: true, + null: null, + undef: undefined, + obj: {}, + arr: [1, 2], + date: new Date(0), + regexp: /regexp/, + func: function func() {}, + window, + }, + }); + + await storage.set({"test-prop2": function func() {}}); + const recentChanges = await globalChanges; + + browser.test.assertEq("value1", recentChanges["test-prop1"].oldValue, "oldValue correct"); + browser.test.assertEq("object", typeof(recentChanges["test-prop1"].newValue), "newValue is obj"); + clearGlobalChanges(); + + data = await storage.get({"test-prop1": undefined, "test-prop2": undefined}); + let obj = data["test-prop1"]; + + browser.test.assertEq("hello", obj.str, "string part correct"); + browser.test.assertEq(true, obj.bool, "bool part correct"); + browser.test.assertEq(null, obj.null, "null part correct"); + browser.test.assertEq(undefined, obj.undef, "undefined part correct"); + browser.test.assertEq(undefined, obj.func, "function part correct"); + browser.test.assertEq(undefined, obj.window, "window part correct"); + browser.test.assertEq("1970-01-01T00:00:00.000Z", obj.date, "date part correct"); + browser.test.assertEq("/regexp/", obj.regexp, "regexp part correct"); + browser.test.assertEq("object", typeof(obj.obj), "object part correct"); + browser.test.assertTrue(Array.isArray(obj.arr), "array part present"); + browser.test.assertEq(1, obj.arr[0], "arr[0] part correct"); + browser.test.assertEq(2, obj.arr[1], "arr[1] part correct"); + browser.test.assertEq(2, obj.arr.length, "arr.length part correct"); + + obj = data["test-prop2"]; + + browser.test.assertEq("[object Object]", {}.toString.call(obj), "function serialized as a plain object"); + browser.test.assertEq(0, Object.keys(obj).length, "function serialized as an empty object"); + } catch (e) { + browser.test.fail(`Error: ${e} :: ${e.stack}`); + browser.test.notifyFail("storage"); + } + } + + browser.test.onMessage.addListener(msg => { + let promise; + if (msg === "test-local") { + promise = runTests("local"); + } else if (msg === "test-sync") { + promise = runTests("sync"); + } + promise.then(() => browser.test.sendMessage("test-finished")); + }); + + browser.test.sendMessage("ready"); + } + + let extensionData = { + background: `(${backgroundScript})(${checkGetImpl})`, + manifest: { + permissions: ["storage"], + }, + }; + + Preferences.set(STORAGE_SYNC_PREF, true); + + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + yield extension.awaitMessage("ready"); + + extension.sendMessage("test-local"); + yield extension.awaitMessage("test-finished"); + + extension.sendMessage("test-sync"); + yield extension.awaitMessage("test-finished"); + + Preferences.reset(STORAGE_SYNC_PREF); + yield extension.unload(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_topSites.js b/toolkit/components/webextensions/test/xpcshell/test_ext_topSites.js new file mode 100644 index 000000000..eb3f552ed --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_ext_topSites.js @@ -0,0 +1,85 @@ +"use strict"; + +Cu.import("resource://gre/modules/NewTabUtils.jsm"); + + +function TestProvider(getLinksFn) { + this.getLinks = getLinksFn; + this._observers = new Set(); +} + +TestProvider.prototype = { + addObserver: function(observer) { + this._observers.add(observer); + }, + notifyLinkChanged: function(link, index = -1, deleted = false) { + this._notifyObservers("onLinkChanged", link, index, deleted); + }, + notifyManyLinksChanged: function() { + this._notifyObservers("onManyLinksChanged"); + }, + _notifyObservers: function(observerMethodName, ...args) { + args.unshift(this); + for (let obs of this._observers) { + if (obs[observerMethodName]) { + obs[observerMethodName].apply(NewTabUtils.links, args); + } + } + }, +}; + +function makeLinks(links) { + // Important: To avoid test failures due to clock jitter on Windows XP, call + // Date.now() once here, not each time through the loop. + let frecency = 0; + let now = Date.now() * 1000; + let places = []; + links.map((link, i) => { + places.push({ + url: link.url, + title: link.title, + lastVisitDate: now - i, + frecency: frecency++, + }); + }); + return places; +} + +add_task(function* test_topSites() { + let expect = [{url: "http://example.com/", title: "site#-1"}, + {url: "http://example0.com/", title: "site#0"}, + {url: "http://example1.com/", title: "site#1"}, + {url: "http://example2.com/", title: "site#2"}, + {url: "http://example3.com/", title: "site#3"}]; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + "permissions": [ + "topSites", + ], + }, + background() { + browser.topSites.get(result => { + browser.test.sendMessage("done", result); + }); + }, + }); + + + let expectedLinks = makeLinks(expect); + let provider = new TestProvider(done => done(expectedLinks)); + + NewTabUtils.initWithoutProviders(); + NewTabUtils.links.addProvider(provider); + + yield NewTabUtils.links.populateCache(); + + yield extension.startup(); + + let result = yield extension.awaitMessage("done"); + Assert.deepEqual(expect, result, "got topSites"); + + yield extension.unload(); + + NewTabUtils.links.removeProvider(provider); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_getAPILevelForWindow.js b/toolkit/components/webextensions/test/xpcshell/test_getAPILevelForWindow.js new file mode 100644 index 000000000..68741a6cc --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_getAPILevelForWindow.js @@ -0,0 +1,55 @@ +"use strict"; + +Cu.import("resource://gre/modules/ExtensionManagement.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +function createWindowWithAddonId(addonId) { + let baseURI = Services.io.newURI("about:blank", null, null); + let originAttributes = {addonId}; + let principal = Services.scriptSecurityManager + .createCodebasePrincipal(baseURI, originAttributes); + let chromeNav = Services.appShell.createWindowlessBrowser(true); + let interfaceRequestor = chromeNav.QueryInterface(Ci.nsIInterfaceRequestor); + let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell); + docShell.createAboutBlankContentViewer(principal); + + return {chromeNav, window: docShell.contentViewer.DOMDocument.defaultView}; +} + +add_task(function* test_eventpages() { + const {getAPILevelForWindow, getAddonIdForWindow} = ExtensionManagement; + const {NO_PRIVILEGES, FULL_PRIVILEGES} = ExtensionManagement.API_LEVELS; + const FAKE_ADDON_ID = "fakeAddonId"; + const OTHER_ADDON_ID = "otherFakeAddonId"; + const EMPTY_ADDON_ID = ""; + + let fakeAddonId = createWindowWithAddonId(FAKE_ADDON_ID); + equal(getAddonIdForWindow(fakeAddonId.window), FAKE_ADDON_ID, + "the window has the expected addonId"); + + let apiLevel = getAPILevelForWindow(fakeAddonId.window, FAKE_ADDON_ID); + equal(apiLevel, FULL_PRIVILEGES, + "apiLevel for the window with the right addonId should be FULL_PRIVILEGES"); + + apiLevel = getAPILevelForWindow(fakeAddonId.window, OTHER_ADDON_ID); + equal(apiLevel, NO_PRIVILEGES, + "apiLevel for the window with a different addonId should be NO_PRIVILEGES"); + + fakeAddonId.chromeNav.close(); + + // NOTE: check that window with an empty addon Id (which are window that are + // not Extensions pages) always get no WebExtensions APIs. + let emptyAddonId = createWindowWithAddonId(EMPTY_ADDON_ID); + equal(getAddonIdForWindow(emptyAddonId.window), EMPTY_ADDON_ID, + "the window has the expected addonId"); + + apiLevel = getAPILevelForWindow(emptyAddonId.window, EMPTY_ADDON_ID); + equal(apiLevel, NO_PRIVILEGES, + "apiLevel for empty addonId should be NO_PRIVILEGES"); + + apiLevel = getAPILevelForWindow(emptyAddonId.window, OTHER_ADDON_ID); + equal(apiLevel, NO_PRIVILEGES, + "apiLevel for an 'empty addonId' window should be always NO_PRIVILEGES"); + + emptyAddonId.chromeNav.close(); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_locale_converter.js b/toolkit/components/webextensions/test/xpcshell/test_locale_converter.js new file mode 100644 index 000000000..c8b1ee92b --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_locale_converter.js @@ -0,0 +1,133 @@ +"use strict"; + +const convService = Cc["@mozilla.org/streamConverters;1"] + .getService(Ci.nsIStreamConverterService); + +const UUID = "72b61ee3-aceb-476c-be1b-0822b036c9f1"; +const ADDON_ID = "test@web.extension"; +const URI = NetUtil.newURI(`moz-extension://${UUID}/file.css`); + +const FROM_TYPE = "application/vnd.mozilla.webext.unlocalized"; +const TO_TYPE = "text/css"; + + +function StringStream(string) { + let stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + + stream.data = string; + return stream; +} + + +// Initialize the policy service with a stub localizer for our +// add-on ID. +add_task(function* init() { + const aps = Cc["@mozilla.org/addons/policy-service;1"] + .getService(Ci.nsIAddonPolicyService).wrappedJSObject; + + let oldCallback = aps.setExtensionURIToAddonIdCallback(uri => { + if (uri.host == UUID) { + return ADDON_ID; + } + }); + + aps.setAddonLocalizeCallback(ADDON_ID, string => { + return string.replace(/__MSG_(.*?)__/g, ""); + }); + + do_register_cleanup(() => { + aps.setExtensionURIToAddonIdCallback(oldCallback); + aps.setAddonLocalizeCallback(ADDON_ID, null); + }); +}); + + +// Test that the synchronous converter works as expected with a +// simple string. +add_task(function* testSynchronousConvert() { + let stream = StringStream("Foo __MSG_xxx__ bar __MSG_yyy__ baz"); + + let resultStream = convService.convert(stream, FROM_TYPE, TO_TYPE, URI); + + let result = NetUtil.readInputStreamToString(resultStream, resultStream.available()); + + equal(result, "Foo bar baz"); +}); + + +// Test that the asynchronous converter works as expected with input +// split into multiple chunks, and a boundary in the middle of a +// replacement token. +add_task(function* testAsyncConvert() { + let listener; + let awaitResult = new Promise((resolve, reject) => { + listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]), + + onDataAvailable(request, context, inputStream, offset, count) { + this.resultParts.push(NetUtil.readInputStreamToString(inputStream, count)); + }, + + onStartRequest() { + ok(!("resultParts" in this)); + this.resultParts = []; + }, + + onStopRequest(request, context, statusCode) { + if (!Components.isSuccessCode(statusCode)) { + reject(new Error(statusCode)); + } + + resolve(this.resultParts.join("\n")); + }, + }; + }); + + let parts = ["Foo __MSG_x", "xx__ bar __MSG_yyy__ baz"]; + + let converter = convService.asyncConvertData(FROM_TYPE, TO_TYPE, listener, URI); + converter.onStartRequest(null, null); + + for (let part of parts) { + converter.onDataAvailable(null, null, StringStream(part), 0, part.length); + } + + converter.onStopRequest(null, null, Cr.NS_OK); + + + let result = yield awaitResult; + equal(result, "Foo bar baz"); +}); + + +// Test that attempting to initialize a converter with the URI of a +// nonexistent WebExtension fails. +add_task(function* testInvalidUUID() { + let uri = NetUtil.newURI("moz-extension://eb4f3be8-41c9-4970-aa6d-b84d1ecc02b2/file.css"); + let stream = StringStream("Foo __MSG_xxx__ bar __MSG_yyy__ baz"); + + // Assert.throws raise a TypeError exception when the expected param + // is an arrow function. (See Bug 1237961 for rationale) + let expectInvalidContextException = function(e) { + return e.result === Cr.NS_ERROR_INVALID_ARG && /Invalid context/.test(e); + }; + + Assert.throws(() => { + convService.convert(stream, FROM_TYPE, TO_TYPE, uri); + }, expectInvalidContextException); + + Assert.throws(() => { + let listener = {QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener])}; + + convService.asyncConvertData(FROM_TYPE, TO_TYPE, listener, uri); + }, expectInvalidContextException); +}); + + +// Test that an empty stream does not throw an NS_ERROR_ILLEGAL_VALUE. +add_task(function* testEmptyStream() { + let stream = StringStream(""); + let resultStream = convService.convert(stream, FROM_TYPE, TO_TYPE, URI); + equal(resultStream.data, ""); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_locale_data.js b/toolkit/components/webextensions/test/xpcshell/test_locale_data.js new file mode 100644 index 000000000..c3cd44e57 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_locale_data.js @@ -0,0 +1,130 @@ +"use strict"; + +Cu.import("resource://gre/modules/Extension.jsm"); + +/* globals ExtensionData */ + +const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); + +function* generateAddon(data) { + let id = uuidGenerator.generateUUID().number; + + data = Object.assign({embedded: true}, data); + data.manifest = Object.assign({applications: {gecko: {id}}}, data.manifest); + + let xpi = Extension.generateXPI(data); + do_register_cleanup(() => { + Services.obs.notifyObservers(xpi, "flush-cache-entry", null); + xpi.remove(false); + }); + + let fileURI = Services.io.newFileURI(xpi); + let jarURI = NetUtil.newURI(`jar:${fileURI.spec}!/webextension/`); + + let extension = new ExtensionData(jarURI); + yield extension.readManifest(); + + return extension; +} + +add_task(function* testMissingDefaultLocale() { + let extension = yield generateAddon({ + "files": { + "_locales/en_US/messages.json": {}, + }, + }); + + equal(extension.errors.length, 0, "No errors reported"); + + yield extension.initAllLocales(); + + equal(extension.errors.length, 1, "One error reported"); + + do_print(`Got error: ${extension.errors[0]}`); + + ok(extension.errors[0].includes('"default_locale" property is required'), + "Got missing default_locale error"); +}); + + +add_task(function* testInvalidDefaultLocale() { + let extension = yield generateAddon({ + "manifest": { + "default_locale": "en", + }, + + "files": { + "_locales/en_US/messages.json": {}, + }, + }); + + equal(extension.errors.length, 1, "One error reported"); + + do_print(`Got error: ${extension.errors[0]}`); + + ok(extension.errors[0].includes("Loading locale file _locales/en/messages.json"), + "Got invalid default_locale error"); + + yield extension.initAllLocales(); + + equal(extension.errors.length, 2, "Two errors reported"); + + do_print(`Got error: ${extension.errors[1]}`); + + ok(extension.errors[1].includes('"default_locale" property must correspond'), + "Got invalid default_locale error"); +}); + + +add_task(function* testUnexpectedDefaultLocale() { + let extension = yield generateAddon({ + "manifest": { + "default_locale": "en_US", + }, + }); + + equal(extension.errors.length, 1, "One error reported"); + + do_print(`Got error: ${extension.errors[0]}`); + + ok(extension.errors[0].includes("Loading locale file _locales/en-US/messages.json"), + "Got invalid default_locale error"); + + yield extension.initAllLocales(); + + equal(extension.errors.length, 2, "One error reported"); + + do_print(`Got error: ${extension.errors[1]}`); + + ok(extension.errors[1].includes('"default_locale" property must correspond'), + "Got unexpected default_locale error"); +}); + + +add_task(function* testInvalidSyntax() { + let extension = yield generateAddon({ + "manifest": { + "default_locale": "en_US", + }, + + "files": { + "_locales/en_US/messages.json": '{foo: {message: "bar", description: "baz"}}', + }, + }); + + equal(extension.errors.length, 1, "No errors reported"); + + do_print(`Got error: ${extension.errors[0]}`); + + ok(extension.errors[0].includes("Loading locale file _locales\/en_US\/messages\.json: SyntaxError"), + "Got syntax error"); + + yield extension.initAllLocales(); + + equal(extension.errors.length, 2, "One error reported"); + + do_print(`Got error: ${extension.errors[1]}`); + + ok(extension.errors[1].includes("Loading locale file _locales\/en_US\/messages\.json: SyntaxError"), + "Got syntax error"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/test_native_messaging.js b/toolkit/components/webextensions/test/xpcshell/test_native_messaging.js new file mode 100644 index 000000000..1fcb7799e --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/test_native_messaging.js @@ -0,0 +1,302 @@ +"use strict"; + +/* global OS, HostManifestManager, NativeApp */ +Cu.import("resource://gre/modules/AppConstants.jsm"); +Cu.import("resource://gre/modules/AsyncShutdown.jsm"); +Cu.import("resource://gre/modules/ExtensionCommon.jsm"); +Cu.import("resource://gre/modules/FileUtils.jsm"); +Cu.import("resource://gre/modules/Schemas.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +const {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm"); +Cu.import("resource://gre/modules/NativeMessaging.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); + +let registry = null; +if (AppConstants.platform == "win") { + Cu.import("resource://testing-common/MockRegistry.jsm"); + registry = new MockRegistry(); + do_register_cleanup(() => { + registry.shutdown(); + }); +} + +const REGPATH = "Software\\Mozilla\\NativeMessagingHosts"; + +const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json"; + +let dir = FileUtils.getDir("TmpD", ["NativeMessaging"]); +dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +let userDir = dir.clone(); +userDir.append("user"); +userDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +let globalDir = dir.clone(); +globalDir.append("global"); +globalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +let dirProvider = { + getFile(property) { + if (property == "XREUserNativeMessaging") { + return userDir.clone(); + } else if (property == "XRESysNativeMessaging") { + return globalDir.clone(); + } + return null; + }, +}; + +Services.dirsvc.registerProvider(dirProvider); + +do_register_cleanup(() => { + Services.dirsvc.unregisterProvider(dirProvider); + dir.remove(true); +}); + +function writeManifest(path, manifest) { + if (typeof manifest != "string") { + manifest = JSON.stringify(manifest); + } + return OS.File.writeAtomic(path, manifest); +} + +let PYTHON; +add_task(function* setup() { + yield Schemas.load(BASE_SCHEMA); + + PYTHON = yield Subprocess.pathSearch("python2.7"); + if (PYTHON == null) { + PYTHON = yield Subprocess.pathSearch("python"); + } + notEqual(PYTHON, null, "Found a suitable python interpreter"); +}); + +let global = this; + +// Test of HostManifestManager.lookupApplication() begin here... +let context = { + url: null, + jsonStringify(...args) { return JSON.stringify(...args); }, + cloneScope: global, + logError() {}, + preprocessors: {}, + callOnClose: () => {}, + forgetOnClose: () => {}, +}; + +class MockContext extends ExtensionCommon.BaseContext { + constructor(extensionId) { + let fakeExtension = {id: extensionId}; + super("testEnv", fakeExtension); + this.sandbox = Cu.Sandbox(global); + } + + get cloneScope() { + return global; + } + + get principal() { + return Cu.getObjectPrincipal(this.sandbox); + } +} + +let templateManifest = { + name: "test", + description: "this is only a test", + path: "/bin/cat", + type: "stdio", + allowed_extensions: ["extension@tests.mozilla.org"], +}; + +add_task(function* test_nonexistent_manifest() { + let result = yield HostManifestManager.lookupApplication("test", context); + equal(result, null, "lookupApplication returns null for non-existent application"); +}); + +const USER_TEST_JSON = OS.Path.join(userDir.path, "test.json"); + +add_task(function* test_good_manifest() { + yield writeManifest(USER_TEST_JSON, templateManifest); + if (registry) { + registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + `${REGPATH}\\test`, "", USER_TEST_JSON); + } + + let result = yield HostManifestManager.lookupApplication("test", context); + notEqual(result, null, "lookupApplication finds a good manifest"); + equal(result.path, USER_TEST_JSON, "lookupApplication returns the correct path"); + deepEqual(result.manifest, templateManifest, "lookupApplication returns the manifest contents"); +}); + +add_task(function* test_invalid_json() { + yield writeManifest(USER_TEST_JSON, "this is not valid json"); + let result = yield HostManifestManager.lookupApplication("test", context); + equal(result, null, "lookupApplication ignores bad json"); +}); + +add_task(function* test_invalid_name() { + let manifest = Object.assign({}, templateManifest); + manifest.name = "../test"; + yield writeManifest(USER_TEST_JSON, manifest); + let result = yield HostManifestManager.lookupApplication("test", context); + equal(result, null, "lookupApplication ignores an invalid name"); +}); + +add_task(function* test_name_mismatch() { + let manifest = Object.assign({}, templateManifest); + manifest.name = "not test"; + yield writeManifest(USER_TEST_JSON, manifest); + let result = yield HostManifestManager.lookupApplication("test", context); + let what = (AppConstants.platform == "win") ? "registry key" : "json filename"; + equal(result, null, `lookupApplication ignores mistmatch between ${what} and name property`); +}); + +add_task(function* test_missing_props() { + const PROPS = [ + "name", + "description", + "path", + "type", + "allowed_extensions", + ]; + for (let prop of PROPS) { + let manifest = Object.assign({}, templateManifest); + delete manifest[prop]; + + yield writeManifest(USER_TEST_JSON, manifest); + let result = yield HostManifestManager.lookupApplication("test", context); + equal(result, null, `lookupApplication ignores missing ${prop}`); + } +}); + +add_task(function* test_invalid_type() { + let manifest = Object.assign({}, templateManifest); + manifest.type = "bogus"; + yield writeManifest(USER_TEST_JSON, manifest); + let result = yield HostManifestManager.lookupApplication("test", context); + equal(result, null, "lookupApplication ignores invalid type"); +}); + +add_task(function* test_no_allowed_extensions() { + let manifest = Object.assign({}, templateManifest); + manifest.allowed_extensions = []; + yield writeManifest(USER_TEST_JSON, manifest); + let result = yield HostManifestManager.lookupApplication("test", context); + equal(result, null, "lookupApplication ignores manifest with no allowed_extensions"); +}); + +const GLOBAL_TEST_JSON = OS.Path.join(globalDir.path, "test.json"); +let globalManifest = Object.assign({}, templateManifest); +globalManifest.description = "This manifest is from the systemwide directory"; + +add_task(function* good_manifest_system_dir() { + yield OS.File.remove(USER_TEST_JSON); + yield writeManifest(GLOBAL_TEST_JSON, globalManifest); + if (registry) { + registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + `${REGPATH}\\test`, "", null); + registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + `${REGPATH}\\test`, "", GLOBAL_TEST_JSON); + } + + let where = (AppConstants.platform == "win") ? "registry location" : "directory"; + let result = yield HostManifestManager.lookupApplication("test", context); + notEqual(result, null, `lookupApplication finds a manifest in the system-wide ${where}`); + equal(result.path, GLOBAL_TEST_JSON, `lookupApplication returns path in the system-wide ${where}`); + deepEqual(result.manifest, globalManifest, `lookupApplication returns manifest contents from the system-wide ${where}`); +}); + +add_task(function* test_user_dir_precedence() { + yield writeManifest(USER_TEST_JSON, templateManifest); + if (registry) { + registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + `${REGPATH}\\test`, "", USER_TEST_JSON); + } + // global test.json and LOCAL_MACHINE registry key on windows are + // still present from the previous test + + let result = yield HostManifestManager.lookupApplication("test", context); + notEqual(result, null, "lookupApplication finds a manifest when entries exist in both user-specific and system-wide locations"); + equal(result.path, USER_TEST_JSON, "lookupApplication returns the user-specific path when user-specific and system-wide entries both exist"); + deepEqual(result.manifest, templateManifest, "lookupApplication returns user-specific manifest contents with user-specific and system-wide entries both exist"); +}); + +// Test shutdown handling in NativeApp +add_task(function* test_native_app_shutdown() { + const SCRIPT = String.raw` +import signal +import struct +import sys + +signal.signal(signal.SIGTERM, signal.SIG_IGN) + +while True: + rawlen = sys.stdin.read(4) + if len(rawlen) == 0: + signal.pause() + msglen = struct.unpack('@I', rawlen)[0] + msg = sys.stdin.read(msglen) + + sys.stdout.write(struct.pack('@I', msglen)) + sys.stdout.write(msg) + `; + + let scriptPath = OS.Path.join(userDir.path, "wontdie.py"); + let manifestPath = OS.Path.join(userDir.path, "wontdie.json"); + + const ID = "native@tests.mozilla.org"; + let manifest = { + name: "wontdie", + description: "test async shutdown of native apps", + type: "stdio", + allowed_extensions: [ID], + }; + + if (AppConstants.platform == "win") { + yield OS.File.writeAtomic(scriptPath, SCRIPT); + + let batPath = OS.Path.join(userDir.path, "wontdie.bat"); + let batBody = `@ECHO OFF\n${PYTHON} -u "${scriptPath}" %*\n`; + yield OS.File.writeAtomic(batPath, batBody); + yield OS.File.setPermissions(batPath, {unixMode: 0o755}); + + manifest.path = batPath; + yield writeManifest(manifestPath, manifest); + + registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + `${REGPATH}\\wontdie`, "", manifestPath); + } else { + yield OS.File.writeAtomic(scriptPath, `#!${PYTHON} -u\n${SCRIPT}`); + yield OS.File.setPermissions(scriptPath, {unixMode: 0o755}); + manifest.path = scriptPath; + yield writeManifest(manifestPath, manifest); + } + + let mockContext = new MockContext(ID); + let app = new NativeApp(mockContext, "wontdie"); + + // send a message and wait for the reply to make sure the app is running + let MSG = "test"; + let recvPromise = new Promise(resolve => { + let listener = (what, msg) => { + equal(msg, MSG, "Received test message"); + app.off("message", listener); + resolve(); + }; + app.on("message", listener); + }); + + let buffer = NativeApp.encodeMessage(mockContext, MSG); + app.send(buffer); + yield recvPromise; + + app._cleanup(); + + do_print("waiting for async shutdown"); + Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); + AsyncShutdown.profileBeforeChange._trigger(); + Services.prefs.clearUserPref("toolkit.asyncshutdown.testing"); + + let procs = yield SubprocessImpl.Process.getWorker().call("getProcesses", []); + equal(procs.size, 0, "native process exited"); +}); diff --git a/toolkit/components/webextensions/test/xpcshell/xpcshell.ini b/toolkit/components/webextensions/test/xpcshell/xpcshell.ini new file mode 100644 index 000000000..d2c6fd5d0 --- /dev/null +++ b/toolkit/components/webextensions/test/xpcshell/xpcshell.ini @@ -0,0 +1,69 @@ +[DEFAULT] +head = head.js +tail = +firefox-appdir = browser +skip-if = appname == "thunderbird" +support-files = + data/** head_sync.js +tags = webextensions + +[test_csp_custom_policies.js] +[test_csp_validator.js] +[test_ext_alarms.js] +[test_ext_alarms_does_not_fire.js] +[test_ext_alarms_periodic.js] +[test_ext_alarms_replaces.js] +[test_ext_apimanager.js] +[test_ext_api_permissions.js] +[test_ext_background_generated_load_events.js] +[test_ext_background_generated_reload.js] +[test_ext_background_global_history.js] +skip-if = os == "android" # Android does not use Places for history. +[test_ext_background_private_browsing.js] +[test_ext_background_runtime_connect_params.js] +[test_ext_background_sub_windows.js] +[test_ext_background_window_properties.js] +skip-if = os == "android" +[test_ext_contexts.js] +[test_ext_downloads.js] +[test_ext_downloads_download.js] +skip-if = os == "android" +[test_ext_downloads_misc.js] +skip-if = os == "android" +[test_ext_downloads_search.js] +skip-if = os == "android" +[test_ext_experiments.js] +skip-if = release_or_beta +[test_ext_extension.js] +[test_ext_idle.js] +[test_ext_json_parser.js] +[test_ext_localStorage.js] +[test_ext_management.js] +[test_ext_management_uninstall_self.js] +[test_ext_manifest_content_security_policy.js] +[test_ext_manifest_incognito.js] +[test_ext_manifest_minimum_chrome_version.js] +[test_ext_onmessage_removelistener.js] +[test_ext_runtime_connect_no_receiver.js] +[test_ext_runtime_getBrowserInfo.js] +[test_ext_runtime_getPlatformInfo.js] +[test_ext_runtime_onInstalled_and_onStartup.js] +[test_ext_runtime_sendMessage.js] +[test_ext_runtime_sendMessage_errors.js] +[test_ext_runtime_sendMessage_no_receiver.js] +[test_ext_runtime_sendMessage_self.js] +[test_ext_schemas.js] +[test_ext_schemas_api_injection.js] +[test_ext_schemas_async.js] +[test_ext_schemas_allowed_contexts.js] +[test_ext_simple.js] +[test_ext_storage.js] +[test_ext_topSites.js] +skip-if = os == "android" +[test_getAPILevelForWindow.js] +[test_ext_legacy_extension_context.js] +[test_ext_legacy_extension_embedding.js] +[test_locale_converter.js] +[test_locale_data.js] +[test_native_messaging.js] +skip-if = os == "android" -- cgit v1.2.3 From 4fb11cd5966461bccc3ed1599b808237be6b0de9 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Sat, 10 Feb 2018 02:49:12 -0500 Subject: Move WebExtensions enabled Add-ons Manager --- browser/experiments/test/xpcshell/xpcshell.ini | 2 +- services/sync/tests/unit/xpcshell.ini | 4 +- .../components/telemetry/tests/unit/xpcshell.ini | 2 +- .../en-US/chrome/mozapps/extensions/about.dtd | 9 - .../en-US/chrome/mozapps/extensions/blocklist.dtd | 17 - .../en-US/chrome/mozapps/extensions/extensions.dtd | 251 - .../mozapps/extensions/extensions.properties | 184 - .../en-US/chrome/mozapps/extensions/newaddon.dtd | 15 - .../chrome/mozapps/extensions/newaddon.properties | 10 - .../en-US/chrome/mozapps/extensions/update.dtd | 65 - .../chrome/mozapps/extensions/update.properties | 21 - .../chrome/mozapps/extensions/xpinstallConfirm.dtd | 13 - .../mozapps/extensions/xpinstallConfirm.properties | 11 - .../en-US/chrome/mozapps/webextensions/about.dtd | 9 + .../chrome/mozapps/webextensions/blocklist.dtd | 17 + .../chrome/mozapps/webextensions/extensions.dtd | 251 + .../mozapps/webextensions/extensions.properties | 184 + .../chrome/mozapps/webextensions/newaddon.dtd | 15 + .../mozapps/webextensions/newaddon.properties | 10 + .../en-US/chrome/mozapps/webextensions/update.dtd | 65 + .../chrome/mozapps/webextensions/update.properties | 21 + .../mozapps/webextensions/xpinstallConfirm.dtd | 13 + .../webextensions/xpinstallConfirm.properties | 11 + toolkit/locales/jar.mn | 24 +- toolkit/moz.build | 4 +- toolkit/mozapps/extensions/.eslintrc.js | 8 - toolkit/mozapps/extensions/AddonContentPolicy.cpp | 478 - toolkit/mozapps/extensions/AddonContentPolicy.h | 22 - toolkit/mozapps/extensions/AddonManager.jsm | 3674 -------- toolkit/mozapps/extensions/AddonManagerWebAPI.cpp | 171 - toolkit/mozapps/extensions/AddonManagerWebAPI.h | 33 - toolkit/mozapps/extensions/AddonPathService.cpp | 258 - toolkit/mozapps/extensions/AddonPathService.h | 55 - .../mozapps/extensions/ChromeManifestParser.jsm | 157 - toolkit/mozapps/extensions/DeferredSave.jsm | 275 - .../mozapps/extensions/LightweightThemeManager.jsm | 909 -- toolkit/mozapps/extensions/addonManager.js | 296 - toolkit/mozapps/extensions/amContentHandler.js | 100 - toolkit/mozapps/extensions/amIAddonManager.idl | 29 - toolkit/mozapps/extensions/amIAddonPathService.idl | 37 - .../mozapps/extensions/amIWebInstallListener.idl | 134 - toolkit/mozapps/extensions/amIWebInstaller.idl | 82 - toolkit/mozapps/extensions/amInstallTrigger.js | 240 - toolkit/mozapps/extensions/amWebAPI.js | 269 - toolkit/mozapps/extensions/amWebInstallListener.js | 348 - .../extensions/content/OpenH264-license.txt | 59 - toolkit/mozapps/extensions/content/about.js | 103 - toolkit/mozapps/extensions/content/about.xul | 57 - toolkit/mozapps/extensions/content/blocklist.css | 11 - toolkit/mozapps/extensions/content/blocklist.js | 72 - toolkit/mozapps/extensions/content/blocklist.xml | 58 - toolkit/mozapps/extensions/content/blocklist.xul | 46 - toolkit/mozapps/extensions/content/eula.js | 25 - toolkit/mozapps/extensions/content/eula.xul | 35 - toolkit/mozapps/extensions/content/extensions.css | 270 - toolkit/mozapps/extensions/content/extensions.js | 3915 -------- toolkit/mozapps/extensions/content/extensions.xml | 2008 ----- toolkit/mozapps/extensions/content/extensions.xul | 715 -- toolkit/mozapps/extensions/content/gmpPrefs.xul | 8 - toolkit/mozapps/extensions/content/list.js | 165 - toolkit/mozapps/extensions/content/list.xul | 44 - toolkit/mozapps/extensions/content/newaddon.js | 137 - toolkit/mozapps/extensions/content/newaddon.xul | 67 - toolkit/mozapps/extensions/content/pluginPrefs.xul | 20 - toolkit/mozapps/extensions/content/setting.xml | 486 - toolkit/mozapps/extensions/content/update.js | 663 -- toolkit/mozapps/extensions/content/update.xul | 194 - toolkit/mozapps/extensions/content/updateinfo.xsl | 41 - .../extensions/content/xpinstallConfirm.css | 8 - .../mozapps/extensions/content/xpinstallConfirm.js | 196 - .../extensions/content/xpinstallConfirm.xul | 37 - .../mozapps/extensions/content/xpinstallItem.xml | 51 - toolkit/mozapps/extensions/docs/SystemAddons.rst | 224 - toolkit/mozapps/extensions/docs/index.rst | 14 - toolkit/mozapps/extensions/extensions.manifest | 27 - .../extensions/internal/APIExtensionBootstrap.js | 39 - .../mozapps/extensions/internal/AddonConstants.jsm | 31 - .../mozapps/extensions/internal/AddonLogging.jsm | 192 - .../extensions/internal/AddonRepository.jsm | 1988 ----- .../internal/AddonRepository_SQLiteMigrator.jsm | 522 -- .../mozapps/extensions/internal/AddonTestUtils.jsm | 1231 --- .../extensions/internal/AddonUpdateChecker.jsm | 934 -- toolkit/mozapps/extensions/internal/Content.js | 38 - .../extensions/internal/E10SAddonsRollout.jsm | 982 --- .../mozapps/extensions/internal/GMPProvider.jsm | 699 -- .../internal/LightweightThemeImageOptimizer.jsm | 180 - .../mozapps/extensions/internal/PluginProvider.jsm | 600 -- .../extensions/internal/ProductAddonChecker.jsm | 467 - .../internal/SpellCheckDictionaryBootstrap.js | 17 - .../extensions/internal/WebExtensionBootstrap.js | 39 - .../mozapps/extensions/internal/XPIProvider.jsm | 9305 -------------------- .../extensions/internal/XPIProviderUtils.js | 2255 ----- toolkit/mozapps/extensions/internal/moz.build | 36 - toolkit/mozapps/extensions/jar.mn | 35 - toolkit/mozapps/extensions/moz.build | 66 - toolkit/mozapps/extensions/nsBlocklistService.js | 1667 ---- .../extensions/nsBlocklistServiceContent.js | 113 - .../extensions/test/AddonManagerTesting.jsm | 115 - toolkit/mozapps/extensions/test/Makefile.in | 20 - .../test/addons/blocklist_hard1_1/install.rdf | 18 - .../test/addons/blocklist_hard1_2/install.rdf | 18 - .../test/addons/blocklist_hard1_3/install.rdf | 18 - .../test/addons/blocklist_regexp1_1/install.rdf | 18 - .../test/addons/blocklist_regexp1_2/install.rdf | 18 - .../test/addons/blocklist_regexp1_3/install.rdf | 18 - .../test/addons/blocklist_soft1_1/install.rdf | 18 - .../test/addons/blocklist_soft1_2/install.rdf | 18 - .../test/addons/blocklist_soft1_3/install.rdf | 18 - .../test/addons/blocklist_soft2_1/install.rdf | 18 - .../test/addons/blocklist_soft2_2/install.rdf | 18 - .../test/addons/blocklist_soft2_3/install.rdf | 18 - .../test/addons/blocklist_soft3_1/install.rdf | 18 - .../test/addons/blocklist_soft3_2/install.rdf | 18 - .../test/addons/blocklist_soft3_3/install.rdf | 18 - .../test/addons/blocklist_soft4_1/install.rdf | 18 - .../test/addons/blocklist_soft4_2/install.rdf | 18 - .../test/addons/blocklist_soft4_3/install.rdf | 18 - .../test/addons/blocklist_soft5_1/install.rdf | 19 - .../test/addons/blocklist_soft5_2/install.rdf | 19 - .../test/addons/blocklist_soft5_3/install.rdf | 19 - .../test/addons/bootstrap_globals/bootstrap.js | 29 - .../test/addons/bootstrap_globals/install.rdf | 23 - .../extensions/test/addons/min1max1/install.rdf | 22 - .../extensions/test/addons/min1max2/install.rdf | 22 - .../extensions/test/addons/min1max3/install.rdf | 22 - .../extensions/test/addons/min1max3b/install.rdf | 22 - .../test/addons/override1x2-1x3/install.rdf | 23 - .../test/addons/test_AddonRepository_1/install.rdf | 33 - .../test/addons/test_AddonRepository_2/install.rdf | 23 - .../test/addons/test_AddonRepository_3/icon.png | 1 - .../test/addons/test_AddonRepository_3/install.rdf | 23 - .../test/addons/test_AddonRepository_3/preview.png | 1 - .../test/addons/test_bootstrap1_1/bootstrap.js | 1 - .../test/addons/test_bootstrap1_1/install.rdf | 28 - .../test/addons/test_bootstrap1_1/version.jsm | 3 - .../test/addons/test_bootstrap1_2/bootstrap.js | 1 - .../test/addons/test_bootstrap1_2/install.rdf | 24 - .../test/addons/test_bootstrap1_2/version.jsm | 3 - .../test/addons/test_bootstrap1_3/bootstrap.js | 1 - .../test/addons/test_bootstrap1_3/install.rdf | 24 - .../test/addons/test_bootstrap1_3/version.jsm | 3 - .../test/addons/test_bootstrap1_4/install.rdf | 23 - .../test/addons/test_bootstrap2_1/bootstrap.js | 1 - .../test/addons/test_bootstrap2_1/install.rdf | 28 - .../test/addons/test_bootstrap_const/bootstrap.js | 5 - .../test/addons/test_bootstrap_const/install.rdf | 24 - .../test/addons/test_bug299716_2/install.rdf | 30 - .../test/addons/test_bug299716_a_1/install.rdf | 21 - .../test/addons/test_bug299716_a_2/install.rdf | 21 - .../test/addons/test_bug299716_b_1/install.rdf | 20 - .../test/addons/test_bug299716_b_2/install.rdf | 20 - .../test/addons/test_bug299716_c_1/install.rdf | 30 - .../test/addons/test_bug299716_c_2/install.rdf | 30 - .../test/addons/test_bug299716_d_1/install.rdf | 30 - .../test/addons/test_bug299716_d_2/install.rdf | 30 - .../test/addons/test_bug299716_e_1/install.rdf | 30 - .../test/addons/test_bug299716_e_2/install.rdf | 30 - .../test/addons/test_bug299716_f_1/install.rdf | 30 - .../test/addons/test_bug299716_f_2/install.rdf | 30 - .../test/addons/test_bug299716_g_1/install.rdf | 21 - .../test/addons/test_bug299716_g_2/install.rdf | 21 - .../test/addons/test_bug324121_1/install.rdf | 25 - .../test/addons/test_bug324121_2/install.rdf | 25 - .../test/addons/test_bug324121_3/install.rdf | 25 - .../test/addons/test_bug324121_4/install.rdf | 25 - .../test/addons/test_bug324121_5/install.rdf | 25 - .../test/addons/test_bug324121_6/install.rdf | 25 - .../test/addons/test_bug324121_7/install.rdf | 25 - .../test/addons/test_bug324121_8/install.rdf | 25 - .../test/addons/test_bug324121_9/install.rdf | 25 - .../test/addons/test_bug335238_1/install.rdf | 22 - .../test/addons/test_bug335238_2/install.rdf | 30 - .../test/addons/test_bug335238_3/install.rdf | 30 - .../test/addons/test_bug335238_4/install.rdf | 30 - .../test/addons/test_bug371495/install.rdf | 26 - .../test/addons/test_bug394300_1/install.rdf | 22 - .../test/addons/test_bug394300_2/install.rdf | 22 - .../test/addons/test_bug397778/install.rdf | 78 - .../test/addons/test_bug425657/install.rdf | 17 - .../test/addons/test_bug470377_1/install.rdf | 17 - .../test/addons/test_bug470377_2/install.rdf | 17 - .../test/addons/test_bug470377_3/install.rdf | 17 - .../test/addons/test_bug470377_4/install.rdf | 17 - .../test/addons/test_bug470377_5/install.rdf | 17 - .../test/addons/test_bug521905/install.rdf | 22 - .../test/addons/test_bug567173/install.rdf | 22 - .../test/addons/test_bug567184/bootstrap.js | 7 - .../test/addons/test_bug567184/install.rdf | 24 - .../test/addons/test_bug587088_1/install.rdf | 22 - .../test/addons/test_bug587088_1/testfile | 1 - .../test/addons/test_bug587088_1/testfile1 | 0 .../test/addons/test_bug587088_2/install.rdf | 22 - .../test/addons/test_bug587088_2/testfile | 1 - .../test/addons/test_bug587088_2/testfile2 | 0 .../test/addons/test_bug594058/directory/file1 | 0 .../test/addons/test_bug594058/install.rdf | 21 - .../test/addons/test_bug595573/install.rdf | 24 - .../test/addons/test_bug655254/install.rdf | 18 - .../test/addons/test_bug655254_2/bootstrap.js | 9 - .../test/addons/test_bug655254_2/install.rdf | 19 - .../test/addons/test_bug659772/install.rdf | 24 - .../test/addons/test_bug675371/chrome.manifest | 1 - .../test/addons/test_bug675371/install.rdf | 24 - .../extensions/test/addons/test_bug675371/test.js | 1 - .../test/addons/test_bug740612_1/bootstrap.js | 1 - .../test/addons/test_bug740612_1/install.rdf | 24 - .../test/addons/test_bug740612_2/bootstrap.js | 23 - .../test/addons/test_bug740612_2/install.rdf | 24 - .../test/addons/test_bug757663/install.rdf | 24 - .../test/addons/test_cacheflush1/install.rdf | 22 - .../test/addons/test_cacheflush2/install.rdf | 23 - .../addons/test_chromemanifest_1/chrome.manifest | 6 - .../test/addons/test_chromemanifest_1/install.rdf | 23 - .../addons/test_chromemanifest_2/chrome.manifest | 7 - .../test/addons/test_chromemanifest_2/install.rdf | 24 - .../addons/test_chromemanifest_3/chrome.manifest | 9 - .../test/addons/test_chromemanifest_3/inner.jar | Bin 180 -> 0 bytes .../test/addons/test_chromemanifest_3/install.rdf | 24 - .../addons/test_chromemanifest_4/chrome.manifest | 6 - .../components/components.manifest | 2 - .../components/other/something.manifest | 1 - .../test/addons/test_chromemanifest_4/install.rdf | 24 - .../addons/test_chromemanifest_5/chrome.manifest | 7 - .../test/addons/test_chromemanifest_5/install.rdf | 24 - .../addons/test_chromemanifest_6/chrome.manifest | 1 - .../test/addons/test_chromemanifest_6/install.rdf | 24 - .../test/addons/test_data_directory/install.rdf | 22 - .../test/addons/test_db_sanity_1_1/install.rdf | 58 - .../test/addons/test_db_sanity_1_2/install.rdf | 59 - .../test_delay_update_complete_v2/bootstrap.js | 10 - .../test_delay_update_complete_v2/install.rdf | 27 - .../manifest.json | 10 - .../addons/test_delay_update_defer_v2/bootstrap.js | 10 - .../addons/test_delay_update_defer_v2/install.rdf | 27 - .../manifest.json | 10 - .../test_delay_update_ignore_v2/bootstrap.js | 8 - .../addons/test_delay_update_ignore_v2/install.rdf | 28 - .../manifest.json | 10 - .../test/addons/test_dictionary/chrome.manifest | 1 - .../addons/test_dictionary/dictionaries/ab-CD.dic | 2 - .../test/addons/test_dictionary/install.rdf | 25 - .../test_dictionary_2/dictionaries/ab-CD.dic | 2 - .../test/addons/test_dictionary_2/install.rdf | 24 - .../test/addons/test_dictionary_3/install.rdf | 25 - .../test/addons/test_dictionary_4/install.rdf | 24 - .../test/addons/test_dictionary_5/install.rdf | 25 - .../test/addons/test_distribution1_2/install.rdf | 23 - .../test/addons/test_experiment1/bootstrap.js | 1 - .../test/addons/test_experiment1/install.rdf | 16 - .../test/addons/test_filepointer/install.rdf | 22 - .../test/addons/test_getresource/icon.png | 1 - .../test/addons/test_getresource/install.rdf | 23 - .../addons/test_getresource/subdir/subfile.txt | 1 - .../test/addons/test_hotfix_1/install.rdf | 23 - .../test/addons/test_hotfix_2/install.rdf | 23 - .../extensions/test/addons/test_install1/icon.png | 1 - .../test/addons/test_install1/icon64.png | 1 - .../test/addons/test_install1/install.rdf | 24 - .../test/addons/test_install2_1/icon.png | 1 - .../test/addons/test_install2_1/install.rdf | 24 - .../test/addons/test_install2_2/install.rdf | 24 - .../test/addons/test_install3/install.rdf | 27 - .../test/addons/test_install4/addon4.xpi | Bin 509 -> 0 bytes .../test/addons/test_install4/addon5.jar | Bin 512 -> 0 bytes .../test/addons/test_install4/addon6.xpi | Bin 512 -> 0 bytes .../test/addons/test_install4/addon7.jar | Bin 512 -> 0 bytes .../test/addons/test_install4/badaddon.jar | 1 - .../test/addons/test_install4/badaddon.xpi | 1 - .../extensions/test/addons/test_install4/icon.png | 1 - .../test/addons/test_install4/install.rdf | 10 - .../test/addons/test_install5/chrome.manifest | 1 - .../test/addons/test_install5/install.rdf | 26 - .../test/addons/test_install6/install.rdf | 24 - .../test/addons/test_install7/addon1.xpi | 1 - .../test/addons/test_install7/addon2.xpi | 1 - .../test/addons/test_install7/install.rdf | 10 - .../test/addons/test_install8/install.rdf | 10 - .../test/addons/test_jetpack/bootstrap.js | 17 - .../test/addons/test_jetpack/harness-options.json | 1 - .../test/addons/test_jetpack/install.rdf | 28 - .../test/addons/test_langpack/chrome.manifest | 1 - .../test/addons/test_langpack/install.rdf | 23 - .../extensions/test/addons/test_locale/install.rdf | 61 - .../test/addons/test_locked2_5/install.rdf | 23 - .../test/addons/test_locked2_6/install.rdf | 23 - .../test/addons/test_migrate4_6/install.rdf | 23 - .../test/addons/test_migrate4_7/install.rdf | 23 - .../test/addons/test_migrate6/install.rdf | 23 - .../test/addons/test_migrate7/install.rdf | 24 - .../test/addons/test_migrate8/chrome.manifest | 6 - .../test/addons/test_migrate8/install.rdf | 24 - .../test/addons/test_migrate9/install.rdf | 26 - .../test/addons/test_symbol/bootstrap.js | 62 - .../extensions/test/addons/test_symbol/install.rdf | 28 - .../extensions/test/addons/test_theme/install.rdf | 26 - .../extensions/test/addons/test_theme/preview.png | 1 - .../test/addons/test_undoincompatible/bootstrap.js | 1 - .../test/addons/test_undoincompatible/install.rdf | 28 - .../test/addons/test_undouninstall1/bootstrap.js | 1 - .../test/addons/test_undouninstall1/install.rdf | 28 - .../extensions/test/addons/test_update/install.rdf | 23 - .../test/addons/test_update12/install.rdf | 23 - .../test/addons/test_update8/install.rdf | 23 - .../test/addons/test_update_multi1/bootstrap.js | 5 - .../test/addons/test_update_multi1/install.rdf | 16 - .../test/addons/test_update_multi2/addon.xpi | Bin 693 -> 0 bytes .../test/addons/test_update_multi2/install.rdf | 9 - .../test/addons/test_updateid1/bootstrap.js | 5 - .../test/addons/test_updateid1/install.rdf | 16 - .../test/addons/test_updateid2/bootstrap.js | 5 - .../test/addons/test_updateid2/install.rdf | 16 - .../test/addons/upgradeable1x2-3_1/install.rdf | 22 - .../test/addons/upgradeable1x2-3_2/install.rdf | 22 - .../test/addons/webextension_1/chrome.manifest | 1 - .../test/addons/webextension_1/manifest.json | 14 - .../test/addons/webextension_2/install.rdf | 30 - .../test/addons/webextension_2/manifest.json | 10 - .../webextension_3/_locales/en/messages.json | 10 - .../webextension_3/_locales/fr/messages.json | 10 - .../test/addons/webextension_3/manifest.json | 12 - .../mozapps/extensions/test/browser/.eslintrc.js | 7 - .../extensions/test/browser/addon_about.xul | 6 - .../extensions/test/browser/addon_prefs.xul | 6 - .../test/browser/addons/browser_bug557956_1.xpi | Bin 4426 -> 0 bytes .../browser/addons/browser_bug557956_1/install.rdf | 31 - .../test/browser/addons/browser_bug557956_10.xpi | Bin 4425 -> 0 bytes .../addons/browser_bug557956_10/install.rdf | 31 - .../test/browser/addons/browser_bug557956_2.xpi | Bin 4427 -> 0 bytes .../browser/addons/browser_bug557956_2/install.rdf | 31 - .../test/browser/addons/browser_bug557956_3.xpi | Bin 4425 -> 0 bytes .../browser/addons/browser_bug557956_3/install.rdf | 31 - .../test/browser/addons/browser_bug557956_4.xpi | Bin 4432 -> 0 bytes .../browser/addons/browser_bug557956_4/install.rdf | 31 - .../test/browser/addons/browser_bug557956_5.xpi | Bin 4427 -> 0 bytes .../browser/addons/browser_bug557956_5/install.rdf | 31 - .../test/browser/addons/browser_bug557956_6.xpi | Bin 4424 -> 0 bytes .../browser/addons/browser_bug557956_6/install.rdf | 31 - .../test/browser/addons/browser_bug557956_7.xpi | Bin 4424 -> 0 bytes .../browser/addons/browser_bug557956_7/install.rdf | 31 - .../test/browser/addons/browser_bug557956_8_1.xpi | Bin 4427 -> 0 bytes .../addons/browser_bug557956_8_1/install.rdf | 31 - .../test/browser/addons/browser_bug557956_9_1.xpi | Bin 4421 -> 0 bytes .../addons/browser_bug557956_9_1/install.rdf | 31 - .../test/browser/addons/browser_bug567127_1.xpi | Bin 4425 -> 0 bytes .../browser/addons/browser_bug567127_1/install.rdf | 30 - .../test/browser/addons/browser_bug567127_2.xpi | Bin 4427 -> 0 bytes .../browser/addons/browser_bug567127_2/install.rdf | 30 - .../test/browser/addons/browser_bug596336_1.xpi | Bin 4449 -> 0 bytes .../browser/addons/browser_bug596336_1/install.rdf | 31 - .../test/browser/addons/browser_bug596336_2.xpi | Bin 4440 -> 0 bytes .../browser/addons/browser_bug596336_2/install.rdf | 31 - .../test/browser/addons/browser_dragdrop1.xpi | Bin 4424 -> 0 bytes .../browser/addons/browser_dragdrop1/install.rdf | 30 - .../test/browser/addons/browser_dragdrop2.xpi | Bin 4420 -> 0 bytes .../browser/addons/browser_dragdrop2/install.rdf | 30 - .../test/browser/addons/browser_experiment1.xpi | Bin 4328 -> 0 bytes .../browser/addons/browser_experiment1/install.rdf | 16 - .../browser/addons/browser_inlinesettings1.xpi | Bin 5811 -> 0 bytes .../addons/browser_inlinesettings1/bootstrap.js | 8 - .../addons/browser_inlinesettings1/chrome.manifest | 1 - .../addons/browser_inlinesettings1/install.rdf | 27 - .../addons/browser_inlinesettings1/options.xul | 23 - .../addons/browser_inlinesettings1/settings.dtd | 1 - .../addons/browser_inlinesettings1_custom.xpi | Bin 6155 -> 0 bytes .../browser_inlinesettings1_custom/binding.xml | 19 - .../browser_inlinesettings1_custom/bootstrap.js | 8 - .../browser_inlinesettings1_custom/chrome.manifest | 2 - .../browser_inlinesettings1_custom/install.rdf | 27 - .../browser_inlinesettings1_custom/options.xul | 5 - .../browser_inlinesettings1_custom/string.dtd | 1 - .../addons/browser_inlinesettings1_info.xpi | Bin 5279 -> 0 bytes .../browser_inlinesettings1_info/bootstrap.js | 8 - .../browser_inlinesettings1_info/install.rdf | 28 - .../browser_inlinesettings1_info/options.xul | 19 - .../test/browser/addons/browser_install1_1.xpi | Bin 4489 -> 0 bytes .../browser/addons/browser_install1_1/install.rdf | 32 - .../test/browser/addons/browser_install1_2.xpi | Bin 4415 -> 0 bytes .../browser/addons/browser_install1_2/install.rdf | 30 - .../test/browser/addons/browser_installssl.xpi | Bin 4430 -> 0 bytes .../browser/addons/browser_installssl/install.rdf | 30 - .../test/browser/addons/browser_searching.xpi | Bin 4808 -> 0 bytes .../browser/addons/browser_searching/bootstrap.js | 9 - .../browser/addons/browser_searching/install.rdf | 25 - .../test/browser/addons/browser_update1_1.xpi | Bin 5479 -> 0 bytes .../browser/addons/browser_update1_1/bootstrap.js | 12 - .../addons/browser_update1_1/chrome.manifest | 1 - .../addons/browser_update1_1/frame-script.js | 6 - .../browser/addons/browser_update1_1/install.rdf | 31 - .../test/browser/addons/browser_update1_2.xpi | Bin 5481 -> 0 bytes .../browser/addons/browser_update1_2/bootstrap.js | 12 - .../addons/browser_update1_2/chrome.manifest | 1 - .../addons/browser_update1_2/frame-script.js | 6 - .../browser/addons/browser_update1_2/install.rdf | 31 - .../test/browser/addons/browser_webapi_install.xpi | Bin 4782 -> 0 bytes .../addons/browser_webapi_install/bootstrap.js | 9 - .../addons/browser_webapi_install/install.rdf | 29 - .../test/browser/addons/options_signed.xpi | Bin 4560 -> 0 bytes .../browser/addons/options_signed/manifest.json | 11 - .../browser/addons/options_signed/options.html | 9 - .../extensions/test/browser/blockNoPlugins.xml | 7 - .../extensions/test/browser/blockPluginHard.xml | 11 - .../extensions/test/browser/browser-common.ini | 67 - .../extensions/test/browser/browser-window.ini | 52 - .../mozapps/extensions/test/browser/browser.ini | 75 - .../extensions/test/browser/browser_CTP_plugins.js | 172 - .../extensions/test/browser/browser_about.js | 84 - .../browser/browser_addonrepository_performance.js | 99 - .../extensions/test/browser/browser_bug523784.js | 120 - .../extensions/test/browser/browser_bug557943.js | 80 - .../extensions/test/browser/browser_bug557956.js | 524 -- .../extensions/test/browser/browser_bug557956.rdf | 310 - .../extensions/test/browser/browser_bug557956.xml | 20 - .../test/browser/browser_bug557956_8_2.xpi | Bin 4438 -> 0 bytes .../test/browser/browser_bug557956_9_2.xpi | Bin 4426 -> 0 bytes .../extensions/test/browser/browser_bug562797.js | 975 -- .../extensions/test/browser/browser_bug562854.js | 129 - .../extensions/test/browser/browser_bug562890.js | 78 - .../extensions/test/browser/browser_bug562899.js | 88 - .../extensions/test/browser/browser_bug562992.js | 70 - .../extensions/test/browser/browser_bug567127.js | 136 - .../extensions/test/browser/browser_bug567137.js | 40 - .../extensions/test/browser/browser_bug570760.js | 44 - .../extensions/test/browser/browser_bug572561.js | 99 - .../extensions/test/browser/browser_bug573062.js | 116 - .../extensions/test/browser/browser_bug577990.js | 132 - .../extensions/test/browser/browser_bug580298.js | 98 - .../extensions/test/browser/browser_bug581076.js | 132 - .../extensions/test/browser/browser_bug586574.js | 286 - .../extensions/test/browser/browser_bug587970.js | 180 - .../extensions/test/browser/browser_bug590347.js | 121 - .../extensions/test/browser/browser_bug591465.js | 512 -- .../extensions/test/browser/browser_bug591465.xml | 35 - .../extensions/test/browser/browser_bug591663.js | 161 - .../extensions/test/browser/browser_bug593535.js | 119 - .../extensions/test/browser/browser_bug593535.xml | 34 - .../extensions/test/browser/browser_bug596336.js | 156 - .../extensions/test/browser/browser_bug608316.js | 65 - .../extensions/test/browser/browser_bug610764.js | 34 - .../extensions/test/browser/browser_bug616841.js | 21 - .../extensions/test/browser/browser_bug618502.js | 44 - .../extensions/test/browser/browser_bug679604.js | 29 - .../extensions/test/browser/browser_bug714593.js | 140 - .../test/browser/browser_cancelCompatCheck.js | 462 - .../browser/browser_checkAddonCompatibility.js | 34 - .../extensions/test/browser/browser_details.js | 1053 --- .../extensions/test/browser/browser_discovery.js | 651 -- .../test/browser/browser_discovery_install.js | 133 - .../extensions/test/browser/browser_dragdrop.js | 234 - .../extensions/test/browser/browser_eula.js | 85 - .../extensions/test/browser/browser_eula.xml | 35 - .../extensions/test/browser/browser_experiments.js | 654 -- .../test/browser/browser_globalwarnings.js | 63 - .../extensions/test/browser/browser_gmpProvider.js | 418 - .../extensions/test/browser/browser_hotfix.js | 171 - .../test/browser/browser_inlinesettings.js | 680 -- .../test/browser/browser_inlinesettings_browser.js | 207 - .../test/browser/browser_inlinesettings_custom.js | 92 - .../test/browser/browser_inlinesettings_info.js | 574 -- .../extensions/test/browser/browser_install.js | 312 - .../extensions/test/browser/browser_install.rdf | 27 - .../test/browser/browser_install.rdf^headers^ | 1 - .../extensions/test/browser/browser_install.xml | 34 - .../extensions/test/browser/browser_install1_3.xpi | Bin 4419 -> 0 bytes .../extensions/test/browser/browser_installssl.js | 374 - .../extensions/test/browser/browser_list.js | 956 -- .../test/browser/browser_manualupdates.js | 246 - .../test/browser/browser_metadataTimeout.js | 114 - .../extensions/test/browser/browser_newaddon.js | 232 - .../extensions/test/browser/browser_openDialog.js | 173 - .../browser/browser_plugin_enabled_state_locked.js | 124 - .../extensions/test/browser/browser_pluginprefs.js | 61 - .../extensions/test/browser/browser_purchase.js | 197 - .../extensions/test/browser/browser_purchase.xml | 180 - .../test/browser/browser_recentupdates.js | 125 - .../extensions/test/browser/browser_searching.js | 698 -- .../extensions/test/browser/browser_searching.xml | 277 - .../test/browser/browser_searching_empty.xml | 3 - .../extensions/test/browser/browser_sorting.js | 372 - .../test/browser/browser_sorting_plugins.js | 95 - .../test/browser/browser_system_addons_are_e10s.js | 13 - .../extensions/test/browser/browser_tabsettings.js | 100 - .../test/browser/browser_task_next_test.js | 17 - .../extensions/test/browser/browser_types.js | 473 - .../test/browser/browser_uninstalling.js | 1098 --- .../extensions/test/browser/browser_update.js | 53 - .../extensions/test/browser/browser_updateid.js | 84 - .../extensions/test/browser/browser_updatessl.js | 370 - .../extensions/test/browser/browser_updatessl.rdf | 25 - .../test/browser/browser_updatessl.rdf^headers^ | 1 - .../extensions/test/browser/browser_webapi.js | 106 - .../test/browser/browser_webapi_access.js | 127 - .../test/browser/browser_webapi_addon_listener.js | 174 - .../test/browser/browser_webapi_enable.js | 62 - .../test/browser/browser_webapi_install.js | 311 - .../test/browser/browser_webapi_uninstall.js | 51 - .../test/browser/browser_webext_options.js | 70 - .../extensions/test/browser/cancelCompatCheck.sjs | 43 - .../mozapps/extensions/test/browser/discovery.html | 10 - .../extensions/test/browser/discovery_frame.html | 6 - .../extensions/test/browser/discovery_install.html | 19 - toolkit/mozapps/extensions/test/browser/head.js | 1468 --- .../extensions/test/browser/more_options.xul | 32 - toolkit/mozapps/extensions/test/browser/moz.build | 10 - .../mozapps/extensions/test/browser/options.xul | 12 - .../extensions/test/browser/plugin_test.html | 7 - .../mozapps/extensions/test/browser/redirect.sjs | 5 - .../extensions/test/browser/releaseNotes.xhtml | 15 - .../extensions/test/browser/signed_hotfix.rdf | 26 - .../extensions/test/browser/signed_hotfix.xpi | Bin 2745 -> 0 bytes .../extensions/test/browser/unsigned_hotfix.rdf | 26 - .../extensions/test/browser/unsigned_hotfix.xpi | Bin 560 -> 0 bytes .../test/browser/webapi_addon_listener.html | 30 - .../test/browser/webapi_checkavailable.html | 13 - .../test/browser/webapi_checkchromeframe.xul | 6 - .../test/browser/webapi_checkframed.html | 7 - .../test/browser/webapi_checknavigatedwindow.html | 28 - .../mozapps/extensions/test/mochitest/.eslintrc.js | 7 - .../extensions/test/mochitest/file_bug687194.xpi | Bin 5659 -> 0 bytes .../extensions/test/mochitest/file_empty.html | 2 - .../extensions/test/mochitest/mochitest.ini | 9 - .../extensions/test/mochitest/test_bug609794.html | 27 - .../extensions/test/mochitest/test_bug687194.html | 133 - .../extensions/test/mochitest/test_bug887098.html | 52 - toolkit/mozapps/extensions/test/moz.build | 19 - .../mozapps/extensions/test/xpcshell/.eslintrc.js | 7 - .../test/xpcshell/data/BootstrapMonitor.jsm | 30 - .../xpcshell/data/blocklistchange/addon_change.xml | 31 - .../data/blocklistchange/addon_update1.rdf | 144 - .../data/blocklistchange/addon_update2.rdf | 144 - .../data/blocklistchange/addon_update3.rdf | 144 - .../xpcshell/data/blocklistchange/app_update.xml | 62 - .../data/blocklistchange/blocklist_update1.xml | 3 - .../data/blocklistchange/blocklist_update2.xml | 26 - .../data/blocklistchange/manual_update.xml | 27 - .../test/xpcshell/data/bug455906_block.xml | 18 - .../test/xpcshell/data/bug455906_empty.xml | 7 - .../test/xpcshell/data/bug455906_start.xml | 30 - .../test/xpcshell/data/bug455906_warn.xml | 33 - .../extensions/test/xpcshell/data/corrupt.xpi | 1 - .../extensions/test/xpcshell/data/corruptfile.xpi | Bin 633 -> 0 bytes .../extensions/test/xpcshell/data/empty.xpi | Bin 197 -> 0 bytes .../test/xpcshell/data/from_sources/bootstrap.js | 1 - .../test/xpcshell/data/from_sources/install.rdf | 28 - .../test/xpcshell/data/pluginInfoURL_block.xml | 45 - .../test/xpcshell/data/productaddons/bad.txt | 1 - .../test/xpcshell/data/productaddons/bad.xml | 3 - .../test/xpcshell/data/productaddons/bad2.xml | 3 - .../test/xpcshell/data/productaddons/empty.xml | 5 - .../test/xpcshell/data/productaddons/good.xml | 11 - .../test/xpcshell/data/productaddons/missing.xml | 3 - .../test/xpcshell/data/productaddons/unsigned.xpi | Bin 452 -> 0 bytes .../data/signing_checks/bootstrap_1/bootstrap.js | 29 - .../data/signing_checks/bootstrap_1/install.rdf | 24 - .../data/signing_checks/bootstrap_1/test.txt | 1 - .../data/signing_checks/bootstrap_2/bootstrap.js | 29 - .../data/signing_checks/bootstrap_2/install.rdf | 24 - .../data/signing_checks/bootstrap_2/test.txt | 1 - .../xpcshell/data/signing_checks/hotfix_badid.xpi | Bin 5151 -> 0 bytes .../xpcshell/data/signing_checks/hotfix_broken.xpi | Bin 5298 -> 0 bytes .../xpcshell/data/signing_checks/hotfix_good.xpi | Bin 5158 -> 0 bytes .../xpcshell/data/signing_checks/long_63_hash.xpi | Bin 4471 -> 0 bytes .../xpcshell/data/signing_checks/long_63_plain.xpi | Bin 4433 -> 0 bytes .../xpcshell/data/signing_checks/long_64_hash.xpi | Bin 4474 -> 0 bytes .../xpcshell/data/signing_checks/long_64_plain.xpi | Bin 4436 -> 0 bytes .../xpcshell/data/signing_checks/long_65_hash.xpi | Bin 4487 -> 0 bytes .../xpcshell/data/signing_checks/multi_badid.xpi | Bin 6443 -> 0 bytes .../xpcshell/data/signing_checks/multi_broken.xpi | Bin 6563 -> 0 bytes .../xpcshell/data/signing_checks/multi_signed.xpi | Bin 6425 -> 0 bytes .../data/signing_checks/multi_unsigned.xpi | Bin 2436 -> 0 bytes .../data/signing_checks/nonbootstrap_1/install.rdf | 23 - .../data/signing_checks/nonbootstrap_1/test.txt | 1 - .../data/signing_checks/nonbootstrap_2/install.rdf | 23 - .../data/signing_checks/nonbootstrap_2/test.txt | 1 - .../signing_checks/preliminary_bootstrap_2.xpi | Bin 5161 -> 0 bytes .../data/signing_checks/signed_bootstrap_1.xpi | Bin 5150 -> 0 bytes .../data/signing_checks/signed_bootstrap_2.xpi | Bin 5149 -> 0 bytes .../signing_checks/signed_bootstrap_badid_2.xpi | Bin 5155 -> 0 bytes .../data/signing_checks/signed_nonbootstrap_2.xpi | Bin 4627 -> 0 bytes .../signing_checks/signed_nonbootstrap_badid_2.xpi | Bin 4634 -> 0 bytes .../data/signing_checks/unsigned_bootstrap_2.xpi | Bin 1156 -> 0 bytes .../signing_checks/unsigned_nonbootstrap_2.xpi | Bin 691 -> 0 bytes .../test/xpcshell/data/system_addons/bootstrap.js | 1 - .../test/xpcshell/data/system_addons/system1_1.xpi | Bin 4692 -> 0 bytes .../data/system_addons/system1_1_badcert.xpi | Bin 4808 -> 0 bytes .../test/xpcshell/data/system_addons/system1_2.xpi | Bin 4695 -> 0 bytes .../test/xpcshell/data/system_addons/system2_1.xpi | Bin 4692 -> 0 bytes .../test/xpcshell/data/system_addons/system2_2.xpi | Bin 4695 -> 0 bytes .../test/xpcshell/data/system_addons/system2_3.xpi | Bin 4697 -> 0 bytes .../test/xpcshell/data/system_addons/system3_1.xpi | Bin 4689 -> 0 bytes .../test/xpcshell/data/system_addons/system3_2.xpi | Bin 4691 -> 0 bytes .../test/xpcshell/data/system_addons/system3_3.xpi | Bin 4693 -> 0 bytes .../test/xpcshell/data/system_addons/system4_1.xpi | Bin 4692 -> 0 bytes .../test/xpcshell/data/system_addons/system5_1.xpi | Bin 4691 -> 0 bytes .../data/system_addons/system6_1_unpack.xpi | Bin 4708 -> 0 bytes .../data/system_addons/system6_2_notBootstrap.xpi | Bin 4682 -> 0 bytes .../system_addons/system6_3_notMultiprocess.xpi | Bin 4675 -> 0 bytes .../data/system_addons/system_delay_complete.xpi | Bin 5090 -> 0 bytes .../data/system_addons/system_delay_complete_2.xpi | Bin 4706 -> 0 bytes .../data/system_addons/system_delay_defer.xpi | Bin 5095 -> 0 bytes .../data/system_addons/system_delay_defer_2.xpi | Bin 4701 -> 0 bytes .../data/system_addons/system_delay_defer_also.xpi | Bin 5117 -> 0 bytes .../system_addons/system_delay_defer_also_2.xpi | Bin 4716 -> 0 bytes .../data/system_addons/system_delay_ignore.xpi | Bin 5098 -> 0 bytes .../data/system_addons/system_delay_ignore_2.xpi | Bin 4707 -> 0 bytes .../data/system_addons/system_failed_update.xpi | Bin 735 -> 0 bytes .../test/xpcshell/data/test_AddonRepository.xml | 820 -- .../xpcshell/data/test_AddonRepository_cache.xml | 182 - .../test_AddonRepository_compatmode_ignore.xml | 23 - .../test_AddonRepository_compatmode_normal.xml | 23 - .../test_AddonRepository_compatmode_strict.xml | 23 - .../xpcshell/data/test_AddonRepository_empty.xml | 3 - .../xpcshell/data/test_AddonRepository_failed.xml | 21 - .../data/test_AddonRepository_getAddonsByIDs.xml | 187 - .../test/xpcshell/data/test_backgroundupdate.rdf | 70 - .../data/test_blocklist_metadata_filters_1.xml | 21 - .../test/xpcshell/data/test_blocklist_prefs_1.xml | 28 - .../test/xpcshell/data/test_blocklist_regexp_1.xml | 20 - .../test/xpcshell/data/test_bug299716.rdf | 181 - .../test/xpcshell/data/test_bug299716_2.rdf | 23 - .../test/xpcshell/data/test_bug324121.rdf | 91 - .../test/xpcshell/data/test_bug393285.xml | 30 - .../test/xpcshell/data/test_bug394300.rdf | 159 - .../test/xpcshell/data/test_bug424262.xml | 185 - .../test/xpcshell/data/test_bug449027_app.xml | 333 - .../test/xpcshell/data/test_bug449027_toolkit.xml | 208 - .../test/xpcshell/data/test_bug468528.xml | 15 - .../xpcshell/data/test_bug470377/install_1.rdf | 17 - .../xpcshell/data/test_bug470377/install_2.rdf | 17 - .../xpcshell/data/test_bug470377/install_3.rdf | 17 - .../xpcshell/data/test_bug470377/install_4.rdf | 17 - .../xpcshell/data/test_bug470377/install_5.rdf | 17 - .../test/xpcshell/data/test_bug470377/update_1.rdf | 26 - .../test/xpcshell/data/test_bug470377/update_2.rdf | 26 - .../test/xpcshell/data/test_bug470377/update_3.rdf | 26 - .../test/xpcshell/data/test_bug470377/update_4.rdf | 26 - .../test/xpcshell/data/test_bug470377/update_5.rdf | 26 - .../test/xpcshell/data/test_bug514327_1.xml | 17 - .../test/xpcshell/data/test_bug514327_2.xml | 10 - .../test/xpcshell/data/test_bug514327_3_empty.xml | 4 - .../xpcshell/data/test_bug514327_3_outdated_1.xml | 13 - .../xpcshell/data/test_bug514327_3_outdated_2.xml | 13 - .../test/xpcshell/data/test_bug526598_1.xpi | Bin 458 -> 0 bytes .../test/xpcshell/data/test_bug526598_2.xpi | Bin 458 -> 0 bytes .../test/xpcshell/data/test_bug541420.xpi | Bin 577 -> 0 bytes .../test/xpcshell/data/test_bug542391.rdf | 25 - .../test/xpcshell/data/test_bug554133.xml | 292 - .../test/xpcshell/data/test_bug619730.xml | 7 - .../test/xpcshell/data/test_bug655254.rdf | 26 - .../test/xpcshell/data/test_compatoverrides.xml | 228 - .../extensions/test/xpcshell/data/test_corrupt.rdf | 44 - .../data/test_delay_update_complete/bootstrap.js | 24 - .../data/test_delay_update_defer/bootstrap.js | 34 - .../data/test_delay_update_ignore/bootstrap.js | 26 - .../xpcshell/data/test_delay_updates_complete.json | 11 - .../xpcshell/data/test_delay_updates_complete.rdf | 26 - .../xpcshell/data/test_delay_updates_defer.json | 11 - .../xpcshell/data/test_delay_updates_defer.rdf | 26 - .../xpcshell/data/test_delay_updates_ignore.json | 11 - .../xpcshell/data/test_delay_updates_ignore.rdf | 26 - .../test/xpcshell/data/test_dictionary.rdf | 65 - .../data/test_distribution2_2/bootstrap.js | 21 - .../xpcshell/data/test_distribution2_2/install.rdf | 23 - .../data/test_distribution2_2/subdir/dummy.txt | 1 - .../test_distribution2_2/subdir/subdir2/dummy2.txt | 1 - .../test/xpcshell/data/test_gfxBlacklist.xml | 304 - .../test/xpcshell/data/test_gfxBlacklist2.xml | 31 - .../test/xpcshell/data/test_gfxBlacklist_AllOS.xml | 783 -- .../xpcshell/data/test_gfxBlacklist_OSVersion.xml | 32 - .../test/xpcshell/data/test_hotfix_1.rdf | 26 - .../test/xpcshell/data/test_hotfix_2.rdf | 26 - .../test/xpcshell/data/test_hotfix_3.rdf | 26 - .../extensions/test/xpcshell/data/test_install.rdf | 63 - .../extensions/test/xpcshell/data/test_install.xml | 53 - .../extensions/test/xpcshell/data/test_migrate.rdf | 125 - .../test/xpcshell/data/test_migrate4.rdf | 46 - .../test/xpcshell/data/test_no_update.json | 7 - .../data/test_overrideblocklist/ancient.xml | 8 - .../xpcshell/data/test_overrideblocklist/new.xml | 8 - .../xpcshell/data/test_overrideblocklist/old.xml | 8 - .../test/xpcshell/data/test_pluginBlocklistCtp.xml | 26 - .../xpcshell/data/test_pluginBlocklistCtpUndo.xml | 10 - .../test/xpcshell/data/test_proxy/bootstrap.js | 1 - .../test/xpcshell/data/test_softblocked1.xml | 9 - .../test/xpcshell/data/test_sourceURI.xml | 18 - .../test/xpcshell/data/test_temporary/bootstrap.js | 1 - .../extensions/test/xpcshell/data/test_update.json | 215 - .../extensions/test/xpcshell/data/test_update.rdf | 270 - .../extensions/test/xpcshell/data/test_update.xml | 26 - .../test/xpcshell/data/test_update_multi.rdf | 26 - .../test/xpcshell/data/test_updatecheck.json | 327 - .../test/xpcshell/data/test_updatecheck.rdf | 419 - .../xpcshell/data/test_updatecompatmode_ignore.rdf | 26 - .../xpcshell/data/test_updatecompatmode_normal.rdf | 26 - .../xpcshell/data/test_updatecompatmode_strict.rdf | 26 - .../test/xpcshell/data/test_updateid.rdf | 26 - .../extensions/test/xpcshell/data/unsigned.xpi | Bin 452 -> 0 bytes .../test/xpcshell/data/webext-implicit-id.xpi | Bin 4182 -> 0 bytes .../extensions/test/xpcshell/head_addons.js | 1345 --- .../extensions/test/xpcshell/head_unpack.js | 3 - .../test/xpcshell/test_AddonRepository.js | 625 -- .../test/xpcshell/test_AddonRepository_cache.js | 704 -- .../xpcshell/test_AddonRepository_compatmode.js | 90 - .../test/xpcshell/test_ChromeManifestParser.js | 108 - .../extensions/test/xpcshell/test_DeferredSave.js | 549 -- .../test/xpcshell/test_LightweightThemeManager.js | 598 -- .../test/xpcshell/test_ProductAddonChecker.js | 244 - .../extensions/test/xpcshell/test_XPIStates.js | 299 - .../extensions/test/xpcshell/test_XPIcancel.js | 66 - .../test/xpcshell/test_addon_path_service.js | 38 - .../test/xpcshell/test_asyncBlocklistLoad.js | 44 - .../test/xpcshell/test_backgroundupdate.js | 126 - .../extensions/test/xpcshell/test_bad_json.js | 54 - .../extensions/test/xpcshell/test_badschema.js | 404 - .../extensions/test/xpcshell/test_blocklist_gfx.js | 157 - .../xpcshell/test_blocklist_metadata_filters.js | 147 - .../test/xpcshell/test_blocklist_prefs.js | 148 - .../test/xpcshell/test_blocklist_regexp.js | 114 - .../test/xpcshell/test_blocklistchange.js | 1305 --- .../extensions/test/xpcshell/test_bootstrap.js | 1403 --- .../test/xpcshell/test_bootstrap_const.js | 17 - .../test/xpcshell/test_bootstrap_globals.js | 37 - .../test/xpcshell/test_bootstrap_resource.js | 56 - .../extensions/test/xpcshell/test_bug1180901.js | 35 - .../extensions/test/xpcshell/test_bug1180901_2.js | 60 - .../extensions/test/xpcshell/test_bug299716.js | 208 - .../extensions/test/xpcshell/test_bug299716_2.js | 50 - .../extensions/test/xpcshell/test_bug324121.js | 178 - .../extensions/test/xpcshell/test_bug335238.js | 173 - .../extensions/test/xpcshell/test_bug371495.js | 35 - .../extensions/test/xpcshell/test_bug384052.js | 103 - .../extensions/test/xpcshell/test_bug393285.js | 316 - .../extensions/test/xpcshell/test_bug394300.js | 56 - .../extensions/test/xpcshell/test_bug397778.js | 117 - .../extensions/test/xpcshell/test_bug406118.js | 155 - .../extensions/test/xpcshell/test_bug424262.js | 62 - .../extensions/test/xpcshell/test_bug425657.js | 27 - .../extensions/test/xpcshell/test_bug430120.js | 135 - .../extensions/test/xpcshell/test_bug449027.js | 429 - .../extensions/test/xpcshell/test_bug455906.js | 517 -- .../extensions/test/xpcshell/test_bug465190.js | 39 - .../extensions/test/xpcshell/test_bug468528.js | 58 - .../extensions/test/xpcshell/test_bug470377_1.js | 49 - .../test/xpcshell/test_bug470377_1_strictcompat.js | 49 - .../extensions/test/xpcshell/test_bug470377_2.js | 49 - .../extensions/test/xpcshell/test_bug470377_3.js | 95 - .../test/xpcshell/test_bug470377_3_strictcompat.js | 94 - .../extensions/test/xpcshell/test_bug470377_4.js | 92 - .../extensions/test/xpcshell/test_bug514327_1.js | 59 - .../extensions/test/xpcshell/test_bug514327_2.js | 41 - .../extensions/test/xpcshell/test_bug514327_3.js | 139 - .../extensions/test/xpcshell/test_bug521905.js | 59 - .../extensions/test/xpcshell/test_bug526598.js | 54 - .../extensions/test/xpcshell/test_bug541420.js | 37 - .../extensions/test/xpcshell/test_bug542391.js | 464 - .../extensions/test/xpcshell/test_bug554133.js | 86 - .../extensions/test/xpcshell/test_bug559800.js | 71 - .../extensions/test/xpcshell/test_bug563256.js | 259 - .../extensions/test/xpcshell/test_bug564030.js | 63 - .../extensions/test/xpcshell/test_bug566626.js | 112 - .../extensions/test/xpcshell/test_bug567184.js | 53 - .../extensions/test/xpcshell/test_bug569138.js | 147 - .../extensions/test/xpcshell/test_bug570173.js | 61 - .../extensions/test/xpcshell/test_bug576735.js | 66 - .../extensions/test/xpcshell/test_bug587088.js | 174 - .../extensions/test/xpcshell/test_bug594058.js | 88 - .../extensions/test/xpcshell/test_bug595081.js | 27 - .../extensions/test/xpcshell/test_bug595573.js | 40 - .../extensions/test/xpcshell/test_bug596607.js | 147 - .../extensions/test/xpcshell/test_bug616841.js | 26 - .../extensions/test/xpcshell/test_bug619730.js | 64 - .../extensions/test/xpcshell/test_bug620837.js | 145 - .../extensions/test/xpcshell/test_bug655254.js | 164 - .../extensions/test/xpcshell/test_bug659772.js | 340 - .../extensions/test/xpcshell/test_bug675371.js | 91 - .../extensions/test/xpcshell/test_bug740612.js | 40 - .../extensions/test/xpcshell/test_bug753900.js | 86 - .../extensions/test/xpcshell/test_bug757663.js | 112 - .../extensions/test/xpcshell/test_bug953156.js | 51 - .../extensions/test/xpcshell/test_cache_certdb.js | 82 - .../extensions/test/xpcshell/test_cacheflush.js | 127 - .../test_checkCompatibility_themeOverride.js | 93 - .../test/xpcshell/test_checkcompatibility.js | 196 - .../extensions/test/xpcshell/test_childprocess.js | 21 - .../test/xpcshell/test_compatoverrides.js | 259 - .../extensions/test/xpcshell/test_corrupt.js | 406 - .../test/xpcshell/test_corrupt_strictcompat.js | 405 - .../extensions/test/xpcshell/test_corruptfile.js | 83 - .../extensions/test/xpcshell/test_dataDirectory.js | 50 - .../test/xpcshell/test_default_providers_pref.js | 13 - .../extensions/test/xpcshell/test_delay_update.js | 260 - .../xpcshell/test_delay_update_webextension.js | 344 - .../extensions/test/xpcshell/test_dependencies.js | 144 - .../extensions/test/xpcshell/test_dictionary.js | 811 -- .../extensions/test/xpcshell/test_disable.js | 194 - .../extensions/test/xpcshell/test_distribution.js | 273 - .../mozapps/extensions/test/xpcshell/test_dss.js | 824 -- .../test/xpcshell/test_duplicateplugins.js | 187 - .../test/xpcshell/test_e10s_restartless.js | 429 - .../mozapps/extensions/test/xpcshell/test_error.js | 90 - .../extensions/test/xpcshell/test_experiment.js | 131 - .../test/xpcshell/test_ext_management.js | 137 - .../extensions/test/xpcshell/test_filepointer.js | 403 - .../mozapps/extensions/test/xpcshell/test_fuel.js | 165 - .../extensions/test/xpcshell/test_general.js | 58 - .../extensions/test/xpcshell/test_getresource.js | 94 - .../test/xpcshell/test_gfxBlacklist_Device.js | 96 - .../test/xpcshell/test_gfxBlacklist_DriverNew.js | 92 - .../xpcshell/test_gfxBlacklist_Equal_DriverNew.js | 123 - .../xpcshell/test_gfxBlacklist_Equal_DriverOld.js | 93 - .../test/xpcshell/test_gfxBlacklist_Equal_OK.js | 93 - .../xpcshell/test_gfxBlacklist_GTE_DriverOld.js | 93 - .../test/xpcshell/test_gfxBlacklist_GTE_OK.js | 93 - .../xpcshell/test_gfxBlacklist_No_Comparison.js | 89 - .../test/xpcshell/test_gfxBlacklist_OK.js | 94 - .../test/xpcshell/test_gfxBlacklist_OS.js | 93 - .../xpcshell/test_gfxBlacklist_OSVersion_match.js | 95 - ...fxBlacklist_OSVersion_mismatch_DriverVersion.js | 95 - ...st_gfxBlacklist_OSVersion_mismatch_OSVersion.js | 96 - .../test/xpcshell/test_gfxBlacklist_Vendor.js | 93 - .../test/xpcshell/test_gfxBlacklist_Version.js | 145 - .../test/xpcshell/test_gfxBlacklist_prefs.js | 135 - .../extensions/test/xpcshell/test_gmpProvider.js | 416 - .../test/xpcshell/test_hasbinarycomponents.js | 82 - .../extensions/test/xpcshell/test_hotfix.js | 309 - .../extensions/test/xpcshell/test_hotfix_cert.js | 167 - .../extensions/test/xpcshell/test_install.js | 1843 ---- .../test/xpcshell/test_install_from_sources.js | 80 - .../extensions/test/xpcshell/test_install_icons.js | 61 - .../test/xpcshell/test_install_strictcompat.js | 1726 ---- .../extensions/test/xpcshell/test_isDebuggable.js | 36 - .../extensions/test/xpcshell/test_isReady.js | 49 - .../test/xpcshell/test_json_updatecheck.js | 372 - .../extensions/test/xpcshell/test_langpack.js | 339 - .../extensions/test/xpcshell/test_locale.js | 149 - .../extensions/test/xpcshell/test_locked.js | 544 -- .../extensions/test/xpcshell/test_locked2.js | 297 - .../test/xpcshell/test_locked_strictcompat.js | 567 -- .../extensions/test/xpcshell/test_manifest.js | 562 -- .../test/xpcshell/test_mapURIToAddonID.js | 347 - .../test/xpcshell/test_metadata_update.js | 159 - .../extensions/test/xpcshell/test_migrate1.js | 231 - .../extensions/test/xpcshell/test_migrate2.js | 267 - .../extensions/test/xpcshell/test_migrate3.js | 229 - .../extensions/test/xpcshell/test_migrate4.js | 321 - .../extensions/test/xpcshell/test_migrate5.js | 139 - .../test/xpcshell/test_migrateAddonRepository.js | 127 - .../test/xpcshell/test_migrate_max_version.js | 103 - .../test/xpcshell/test_multiprocessCompatible.js | 120 - .../extensions/test/xpcshell/test_no_addons.js | 98 - .../test/xpcshell/test_nodisable_hidden.js | 107 - .../xpcshell/test_onPropertyChanged_appDisabled.js | 66 - .../test/xpcshell/test_overrideblocklist.js | 200 - .../extensions/test/xpcshell/test_pass_symbol.js | 43 - .../extensions/test/xpcshell/test_permissions.js | 86 - .../test/xpcshell/test_permissions_prefs.js | 74 - .../test/xpcshell/test_pluginBlocklistCtp.js | 182 - .../extensions/test/xpcshell/test_pluginInfoURL.js | 90 - .../extensions/test/xpcshell/test_pluginchange.js | 283 - .../extensions/test/xpcshell/test_plugins.js | 210 - .../test/xpcshell/test_pref_properties.js | 221 - .../test/xpcshell/test_provider_markSafe.js | 49 - .../test/xpcshell/test_provider_shutdown.js | 99 - .../test_provider_unsafe_access_shutdown.js | 64 - .../test_provider_unsafe_access_startup.js | 55 - .../extensions/test/xpcshell/test_proxies.js | 240 - .../mozapps/extensions/test/xpcshell/test_proxy.js | 106 - .../extensions/test/xpcshell/test_registry.js | 158 - .../extensions/test/xpcshell/test_reload.js | 235 - .../extensions/test/xpcshell/test_safemode.js | 115 - .../extensions/test/xpcshell/test_schema_change.js | 317 - .../mozapps/extensions/test/xpcshell/test_seen.js | 211 - .../test/xpcshell/test_seen_newprofile.js | 41 - .../extensions/test/xpcshell/test_shutdown.js | 85 - .../extensions/test/xpcshell/test_signed_inject.js | 382 - .../test/xpcshell/test_signed_install.js | 265 - .../extensions/test/xpcshell/test_signed_long.js | 49 - .../test/xpcshell/test_signed_migrate.js | 194 - .../extensions/test/xpcshell/test_signed_multi.js | 55 - .../test/xpcshell/test_signed_updatepref.js | 136 - .../extensions/test/xpcshell/test_signed_verify.js | 234 - .../extensions/test/xpcshell/test_softblocked.js | 109 - .../extensions/test/xpcshell/test_sourceURI.js | 66 - .../extensions/test/xpcshell/test_startup.js | 932 -- .../test/xpcshell/test_strictcompatibility.js | 203 - .../extensions/test/xpcshell/test_switch_os.js | 52 - .../extensions/test/xpcshell/test_syncGUID.js | 156 - .../test/xpcshell/test_system_delay_update.js | 461 - .../extensions/test/xpcshell/test_system_reset.js | 418 - .../extensions/test/xpcshell/test_system_update.js | 788 -- .../test/xpcshell/test_targetPlatforms.js | 146 - .../extensions/test/xpcshell/test_temporary.js | 760 -- .../mozapps/extensions/test/xpcshell/test_theme.js | 1139 --- .../mozapps/extensions/test/xpcshell/test_types.js | 65 - .../test/xpcshell/test_undothemeuninstall.js | 423 - .../extensions/test/xpcshell/test_undouninstall.js | 792 -- .../extensions/test/xpcshell/test_uninstall.js | 216 - .../extensions/test/xpcshell/test_update.js | 1398 --- .../extensions/test/xpcshell/test_updateCancel.js | 138 - .../test/xpcshell/test_update_compatmode.js | 184 - .../test/xpcshell/test_update_ignorecompat.js | 107 - .../test/xpcshell/test_update_strictcompat.js | 1126 --- .../test/xpcshell/test_update_webextensions.js | 248 - .../extensions/test/xpcshell/test_updatecheck.js | 236 - .../extensions/test/xpcshell/test_updateid.js | 86 - .../extensions/test/xpcshell/test_upgrade.js | 206 - .../test/xpcshell/test_upgrade_strictcompat.js | 209 - .../extensions/test/xpcshell/test_webextension.js | 421 - .../test/xpcshell/test_webextension_embedded.js | 306 - .../test/xpcshell/test_webextension_icons.js | 169 - .../test/xpcshell/test_webextension_install.js | 478 - .../test/xpcshell/test_webextension_paths.js | 55 - .../extensions/test/xpcshell/xpcshell-shared.ini | 334 - .../extensions/test/xpcshell/xpcshell-unpack.ini | 12 - .../mozapps/extensions/test/xpcshell/xpcshell.ini | 50 - .../mozapps/extensions/test/xpinstall/.eslintrc.js | 7 - .../extensions/test/xpinstall/amosigned.xpi | Bin 4420 -> 0 bytes .../extensions/test/xpinstall/amosigned2.xpi | Bin 4421 -> 0 bytes .../extensions/test/xpinstall/authRedirect.sjs | 21 - .../mozapps/extensions/test/xpinstall/browser.ini | 119 - .../test/xpinstall/browser_amosigned_trigger.js | 56 - .../xpinstall/browser_amosigned_trigger_iframe.js | 57 - .../test/xpinstall/browser_amosigned_url.js | 35 - .../extensions/test/xpinstall/browser_auth.js | 47 - .../extensions/test/xpinstall/browser_auth2.js | 46 - .../extensions/test/xpinstall/browser_auth3.js | 53 - .../extensions/test/xpinstall/browser_auth4.js | 52 - .../extensions/test/xpinstall/browser_badargs.js | 38 - .../extensions/test/xpinstall/browser_badargs2.js | 42 - .../extensions/test/xpinstall/browser_badhash.js | 33 - .../test/xpinstall/browser_badhashtype.js | 33 - .../extensions/test/xpinstall/browser_bug540558.js | 25 - .../extensions/test/xpinstall/browser_bug611242.js | 17 - .../extensions/test/xpinstall/browser_bug638292.js | 40 - .../extensions/test/xpinstall/browser_bug645699.js | 36 - .../extensions/test/xpinstall/browser_bug672485.js | 52 - .../extensions/test/xpinstall/browser_cancel.js | 60 - .../test/xpinstall/browser_concurrent_installs.js | 127 - .../extensions/test/xpinstall/browser_cookies.js | 30 - .../extensions/test/xpinstall/browser_cookies2.js | 40 - .../extensions/test/xpinstall/browser_cookies3.js | 44 - .../extensions/test/xpinstall/browser_cookies4.js | 43 - .../extensions/test/xpinstall/browser_corrupt.js | 38 - .../extensions/test/xpinstall/browser_datauri.js | 37 - .../extensions/test/xpinstall/browser_empty.js | 28 - .../extensions/test/xpinstall/browser_enabled.js | 29 - .../extensions/test/xpinstall/browser_enabled2.js | 32 - .../extensions/test/xpinstall/browser_enabled3.js | 52 - .../extensions/test/xpinstall/browser_hash.js | 34 - .../extensions/test/xpinstall/browser_hash2.js | 34 - .../extensions/test/xpinstall/browser_httphash.js | 39 - .../extensions/test/xpinstall/browser_httphash2.js | 39 - .../extensions/test/xpinstall/browser_httphash3.js | 39 - .../extensions/test/xpinstall/browser_httphash4.js | 36 - .../extensions/test/xpinstall/browser_httphash5.js | 40 - .../extensions/test/xpinstall/browser_httphash6.js | 83 - .../test/xpinstall/browser_installchrome.js | 25 - .../extensions/test/xpinstall/browser_localfile.js | 35 - .../test/xpinstall/browser_localfile2.js | 38 - .../test/xpinstall/browser_localfile3.js | 41 - .../test/xpinstall/browser_localfile4.js | 41 - .../test/xpinstall/browser_multipackage.js | 52 - .../test/xpinstall/browser_navigateaway.js | 36 - .../test/xpinstall/browser_navigateaway2.js | 34 - .../test/xpinstall/browser_navigateaway3.js | 38 - .../test/xpinstall/browser_navigateaway4.js | 44 - .../extensions/test/xpinstall/browser_offline.js | 62 - .../extensions/test/xpinstall/browser_relative.js | 55 - .../test/xpinstall/browser_signed_multipackage.js | 53 - .../test/xpinstall/browser_signed_multiple.js | 72 - .../test/xpinstall/browser_signed_naming.js | 67 - .../test/xpinstall/browser_signed_tampered.js | 33 - .../test/xpinstall/browser_signed_trigger.js | 41 - .../test/xpinstall/browser_signed_untrusted.js | 41 - .../test/xpinstall/browser_signed_url.js | 34 - .../test/xpinstall/browser_softwareupdate.js | 25 - .../extensions/test/xpinstall/browser_switchtab.js | 49 - .../test/xpinstall/browser_trigger_redirect.js | 41 - .../test/xpinstall/browser_unsigned_trigger.js | 56 - .../xpinstall/browser_unsigned_trigger_iframe.js | 57 - .../xpinstall/browser_unsigned_trigger_xorigin.js | 38 - .../test/xpinstall/browser_unsigned_url.js | 35 - .../extensions/test/xpinstall/browser_whitelist.js | 53 - .../test/xpinstall/browser_whitelist2.js | 31 - .../test/xpinstall/browser_whitelist3.js | 28 - .../test/xpinstall/browser_whitelist4.js | 30 - .../test/xpinstall/browser_whitelist5.js | 25 - .../test/xpinstall/browser_whitelist6.js | 25 - .../test/xpinstall/browser_whitelist7.js | 32 - .../extensions/test/xpinstall/bug540558.html | 23 - .../extensions/test/xpinstall/bug638292.html | 17 - .../extensions/test/xpinstall/bug645699.html | 31 - .../test/xpinstall/concurrent_installs.html | 40 - .../extensions/test/xpinstall/cookieRedirect.sjs | 24 - .../mozapps/extensions/test/xpinstall/corrupt.xpi | 1 - .../mozapps/extensions/test/xpinstall/empty.xpi | Bin 197 -> 0 bytes .../mozapps/extensions/test/xpinstall/enabled.html | 24 - .../extensions/test/xpinstall/hashRedirect.sjs | 15 - toolkit/mozapps/extensions/test/xpinstall/head.js | 434 - .../extensions/test/xpinstall/incompatible.xpi | Bin 4442 -> 0 bytes .../extensions/test/xpinstall/installchrome.html | 22 - .../extensions/test/xpinstall/installtrigger.html | 44 - .../test/xpinstall/installtrigger_frame.html | 29 - .../extensions/test/xpinstall/multipackage.xpi | Bin 9589 -> 0 bytes .../extensions/test/xpinstall/navigate.html | 26 - .../mozapps/extensions/test/xpinstall/redirect.sjs | 45 - .../test/xpinstall/restartless-unsigned.xpi | Bin 528 -> 0 bytes .../extensions/test/xpinstall/restartless.xpi | Bin 4447 -> 0 bytes .../test/xpinstall/signed-multipackage.xpi | Bin 2976 -> 0 bytes .../extensions/test/xpinstall/signed-no-cn.xpi | Bin 2241 -> 0 bytes .../extensions/test/xpinstall/signed-no-o.xpi | Bin 2247 -> 0 bytes .../extensions/test/xpinstall/signed-tampered.xpi | Bin 2260 -> 0 bytes .../extensions/test/xpinstall/signed-untrusted.xpi | Bin 2237 -> 0 bytes .../mozapps/extensions/test/xpinstall/signed.xpi | Bin 2250 -> 0 bytes .../mozapps/extensions/test/xpinstall/signed2.xpi | Bin 2938 -> 0 bytes .../extensions/test/xpinstall/slowinstall.sjs | 101 - .../test/xpinstall/startsoftwareupdate.html | 20 - .../mozapps/extensions/test/xpinstall/theme.xpi | Bin 4450 -> 0 bytes .../extensions/test/xpinstall/triggerredirect.html | 36 - .../mozapps/extensions/test/xpinstall/unsigned.xpi | Bin 452 -> 0 bytes toolkit/mozapps/webextensions/.eslintrc.js | 8 + .../mozapps/webextensions/AddonContentPolicy.cpp | 478 + toolkit/mozapps/webextensions/AddonContentPolicy.h | 22 + toolkit/mozapps/webextensions/AddonManager.jsm | 3674 ++++++++ .../mozapps/webextensions/AddonManagerWebAPI.cpp | 171 + toolkit/mozapps/webextensions/AddonManagerWebAPI.h | 33 + toolkit/mozapps/webextensions/AddonPathService.cpp | 258 + toolkit/mozapps/webextensions/AddonPathService.h | 55 + .../mozapps/webextensions/ChromeManifestParser.jsm | 157 + toolkit/mozapps/webextensions/DeferredSave.jsm | 275 + .../webextensions/LightweightThemeManager.jsm | 909 ++ toolkit/mozapps/webextensions/addonManager.js | 296 + toolkit/mozapps/webextensions/amContentHandler.js | 100 + toolkit/mozapps/webextensions/amIAddonManager.idl | 29 + .../mozapps/webextensions/amIAddonPathService.idl | 37 + .../webextensions/amIWebInstallListener.idl | 134 + toolkit/mozapps/webextensions/amIWebInstaller.idl | 82 + toolkit/mozapps/webextensions/amInstallTrigger.js | 240 + toolkit/mozapps/webextensions/amWebAPI.js | 269 + .../mozapps/webextensions/amWebInstallListener.js | 348 + .../webextensions/content/OpenH264-license.txt | 59 + toolkit/mozapps/webextensions/content/about.js | 103 + toolkit/mozapps/webextensions/content/about.xul | 57 + .../mozapps/webextensions/content/blocklist.css | 11 + toolkit/mozapps/webextensions/content/blocklist.js | 72 + .../mozapps/webextensions/content/blocklist.xml | 58 + .../mozapps/webextensions/content/blocklist.xul | 46 + toolkit/mozapps/webextensions/content/eula.js | 25 + toolkit/mozapps/webextensions/content/eula.xul | 35 + .../mozapps/webextensions/content/extensions.css | 270 + .../mozapps/webextensions/content/extensions.js | 3915 ++++++++ .../mozapps/webextensions/content/extensions.xml | 2008 +++++ .../mozapps/webextensions/content/extensions.xul | 715 ++ toolkit/mozapps/webextensions/content/gmpPrefs.xul | 8 + toolkit/mozapps/webextensions/content/list.js | 165 + toolkit/mozapps/webextensions/content/list.xul | 44 + toolkit/mozapps/webextensions/content/newaddon.js | 137 + toolkit/mozapps/webextensions/content/newaddon.xul | 67 + .../mozapps/webextensions/content/pluginPrefs.xul | 20 + toolkit/mozapps/webextensions/content/setting.xml | 486 + toolkit/mozapps/webextensions/content/update.js | 663 ++ toolkit/mozapps/webextensions/content/update.xul | 194 + .../mozapps/webextensions/content/updateinfo.xsl | 41 + .../webextensions/content/xpinstallConfirm.css | 8 + .../webextensions/content/xpinstallConfirm.js | 196 + .../webextensions/content/xpinstallConfirm.xul | 37 + .../webextensions/content/xpinstallItem.xml | 51 + .../mozapps/webextensions/docs/SystemAddons.rst | 224 + toolkit/mozapps/webextensions/docs/index.rst | 14 + toolkit/mozapps/webextensions/extensions.manifest | 27 + .../internal/APIExtensionBootstrap.js | 39 + .../webextensions/internal/AddonConstants.jsm | 31 + .../webextensions/internal/AddonLogging.jsm | 192 + .../webextensions/internal/AddonRepository.jsm | 1988 +++++ .../internal/AddonRepository_SQLiteMigrator.jsm | 522 ++ .../webextensions/internal/AddonTestUtils.jsm | 1231 +++ .../webextensions/internal/AddonUpdateChecker.jsm | 934 ++ toolkit/mozapps/webextensions/internal/Content.js | 38 + .../webextensions/internal/E10SAddonsRollout.jsm | 982 +++ .../mozapps/webextensions/internal/GMPProvider.jsm | 699 ++ .../internal/LightweightThemeImageOptimizer.jsm | 180 + .../webextensions/internal/PluginProvider.jsm | 600 ++ .../webextensions/internal/ProductAddonChecker.jsm | 467 + .../internal/SpellCheckDictionaryBootstrap.js | 17 + .../internal/WebExtensionBootstrap.js | 39 + .../mozapps/webextensions/internal/XPIProvider.jsm | 9305 ++++++++++++++++++++ .../webextensions/internal/XPIProviderUtils.js | 2255 +++++ toolkit/mozapps/webextensions/internal/moz.build | 36 + toolkit/mozapps/webextensions/jar.mn | 35 + toolkit/mozapps/webextensions/moz.build | 66 + .../mozapps/webextensions/nsBlocklistService.js | 1667 ++++ .../webextensions/nsBlocklistServiceContent.js | 113 + .../webextensions/test/AddonManagerTesting.jsm | 115 + toolkit/mozapps/webextensions/test/Makefile.in | 20 + .../test/addons/blocklist_hard1_1/install.rdf | 18 + .../test/addons/blocklist_hard1_2/install.rdf | 18 + .../test/addons/blocklist_hard1_3/install.rdf | 18 + .../test/addons/blocklist_regexp1_1/install.rdf | 18 + .../test/addons/blocklist_regexp1_2/install.rdf | 18 + .../test/addons/blocklist_regexp1_3/install.rdf | 18 + .../test/addons/blocklist_soft1_1/install.rdf | 18 + .../test/addons/blocklist_soft1_2/install.rdf | 18 + .../test/addons/blocklist_soft1_3/install.rdf | 18 + .../test/addons/blocklist_soft2_1/install.rdf | 18 + .../test/addons/blocklist_soft2_2/install.rdf | 18 + .../test/addons/blocklist_soft2_3/install.rdf | 18 + .../test/addons/blocklist_soft3_1/install.rdf | 18 + .../test/addons/blocklist_soft3_2/install.rdf | 18 + .../test/addons/blocklist_soft3_3/install.rdf | 18 + .../test/addons/blocklist_soft4_1/install.rdf | 18 + .../test/addons/blocklist_soft4_2/install.rdf | 18 + .../test/addons/blocklist_soft4_3/install.rdf | 18 + .../test/addons/blocklist_soft5_1/install.rdf | 19 + .../test/addons/blocklist_soft5_2/install.rdf | 19 + .../test/addons/blocklist_soft5_3/install.rdf | 19 + .../test/addons/bootstrap_globals/bootstrap.js | 29 + .../test/addons/bootstrap_globals/install.rdf | 23 + .../webextensions/test/addons/min1max1/install.rdf | 22 + .../webextensions/test/addons/min1max2/install.rdf | 22 + .../webextensions/test/addons/min1max3/install.rdf | 22 + .../test/addons/min1max3b/install.rdf | 22 + .../test/addons/override1x2-1x3/install.rdf | 23 + .../test/addons/test_AddonRepository_1/install.rdf | 33 + .../test/addons/test_AddonRepository_2/install.rdf | 23 + .../test/addons/test_AddonRepository_3/icon.png | 1 + .../test/addons/test_AddonRepository_3/install.rdf | 23 + .../test/addons/test_AddonRepository_3/preview.png | 1 + .../test/addons/test_bootstrap1_1/bootstrap.js | 1 + .../test/addons/test_bootstrap1_1/install.rdf | 28 + .../test/addons/test_bootstrap1_1/version.jsm | 3 + .../test/addons/test_bootstrap1_2/bootstrap.js | 1 + .../test/addons/test_bootstrap1_2/install.rdf | 24 + .../test/addons/test_bootstrap1_2/version.jsm | 3 + .../test/addons/test_bootstrap1_3/bootstrap.js | 1 + .../test/addons/test_bootstrap1_3/install.rdf | 24 + .../test/addons/test_bootstrap1_3/version.jsm | 3 + .../test/addons/test_bootstrap1_4/install.rdf | 23 + .../test/addons/test_bootstrap2_1/bootstrap.js | 1 + .../test/addons/test_bootstrap2_1/install.rdf | 28 + .../test/addons/test_bootstrap_const/bootstrap.js | 5 + .../test/addons/test_bootstrap_const/install.rdf | 24 + .../test/addons/test_bug299716_2/install.rdf | 30 + .../test/addons/test_bug299716_a_1/install.rdf | 21 + .../test/addons/test_bug299716_a_2/install.rdf | 21 + .../test/addons/test_bug299716_b_1/install.rdf | 20 + .../test/addons/test_bug299716_b_2/install.rdf | 20 + .../test/addons/test_bug299716_c_1/install.rdf | 30 + .../test/addons/test_bug299716_c_2/install.rdf | 30 + .../test/addons/test_bug299716_d_1/install.rdf | 30 + .../test/addons/test_bug299716_d_2/install.rdf | 30 + .../test/addons/test_bug299716_e_1/install.rdf | 30 + .../test/addons/test_bug299716_e_2/install.rdf | 30 + .../test/addons/test_bug299716_f_1/install.rdf | 30 + .../test/addons/test_bug299716_f_2/install.rdf | 30 + .../test/addons/test_bug299716_g_1/install.rdf | 21 + .../test/addons/test_bug299716_g_2/install.rdf | 21 + .../test/addons/test_bug324121_1/install.rdf | 25 + .../test/addons/test_bug324121_2/install.rdf | 25 + .../test/addons/test_bug324121_3/install.rdf | 25 + .../test/addons/test_bug324121_4/install.rdf | 25 + .../test/addons/test_bug324121_5/install.rdf | 25 + .../test/addons/test_bug324121_6/install.rdf | 25 + .../test/addons/test_bug324121_7/install.rdf | 25 + .../test/addons/test_bug324121_8/install.rdf | 25 + .../test/addons/test_bug324121_9/install.rdf | 25 + .../test/addons/test_bug335238_1/install.rdf | 22 + .../test/addons/test_bug335238_2/install.rdf | 30 + .../test/addons/test_bug335238_3/install.rdf | 30 + .../test/addons/test_bug335238_4/install.rdf | 30 + .../test/addons/test_bug371495/install.rdf | 26 + .../test/addons/test_bug394300_1/install.rdf | 22 + .../test/addons/test_bug394300_2/install.rdf | 22 + .../test/addons/test_bug397778/install.rdf | 78 + .../test/addons/test_bug425657/install.rdf | 17 + .../test/addons/test_bug470377_1/install.rdf | 17 + .../test/addons/test_bug470377_2/install.rdf | 17 + .../test/addons/test_bug470377_3/install.rdf | 17 + .../test/addons/test_bug470377_4/install.rdf | 17 + .../test/addons/test_bug470377_5/install.rdf | 17 + .../test/addons/test_bug521905/install.rdf | 22 + .../test/addons/test_bug567173/install.rdf | 22 + .../test/addons/test_bug567184/bootstrap.js | 7 + .../test/addons/test_bug567184/install.rdf | 24 + .../test/addons/test_bug587088_1/install.rdf | 22 + .../test/addons/test_bug587088_1/testfile | 1 + .../test/addons/test_bug587088_1/testfile1 | 0 .../test/addons/test_bug587088_2/install.rdf | 22 + .../test/addons/test_bug587088_2/testfile | 1 + .../test/addons/test_bug587088_2/testfile2 | 0 .../test/addons/test_bug594058/directory/file1 | 0 .../test/addons/test_bug594058/install.rdf | 21 + .../test/addons/test_bug595573/install.rdf | 24 + .../test/addons/test_bug655254/install.rdf | 18 + .../test/addons/test_bug655254_2/bootstrap.js | 9 + .../test/addons/test_bug655254_2/install.rdf | 19 + .../test/addons/test_bug659772/install.rdf | 24 + .../test/addons/test_bug675371/chrome.manifest | 1 + .../test/addons/test_bug675371/install.rdf | 24 + .../test/addons/test_bug675371/test.js | 1 + .../test/addons/test_bug740612_1/bootstrap.js | 1 + .../test/addons/test_bug740612_1/install.rdf | 24 + .../test/addons/test_bug740612_2/bootstrap.js | 23 + .../test/addons/test_bug740612_2/install.rdf | 24 + .../test/addons/test_bug757663/install.rdf | 24 + .../test/addons/test_cacheflush1/install.rdf | 22 + .../test/addons/test_cacheflush2/install.rdf | 23 + .../addons/test_chromemanifest_1/chrome.manifest | 6 + .../test/addons/test_chromemanifest_1/install.rdf | 23 + .../addons/test_chromemanifest_2/chrome.manifest | 7 + .../test/addons/test_chromemanifest_2/install.rdf | 24 + .../addons/test_chromemanifest_3/chrome.manifest | 9 + .../test/addons/test_chromemanifest_3/inner.jar | Bin 0 -> 180 bytes .../test/addons/test_chromemanifest_3/install.rdf | 24 + .../addons/test_chromemanifest_4/chrome.manifest | 6 + .../components/components.manifest | 2 + .../components/other/something.manifest | 1 + .../test/addons/test_chromemanifest_4/install.rdf | 24 + .../addons/test_chromemanifest_5/chrome.manifest | 7 + .../test/addons/test_chromemanifest_5/install.rdf | 24 + .../addons/test_chromemanifest_6/chrome.manifest | 1 + .../test/addons/test_chromemanifest_6/install.rdf | 24 + .../test/addons/test_data_directory/install.rdf | 22 + .../test/addons/test_db_sanity_1_1/install.rdf | 58 + .../test/addons/test_db_sanity_1_2/install.rdf | 59 + .../test_delay_update_complete_v2/bootstrap.js | 10 + .../test_delay_update_complete_v2/install.rdf | 27 + .../manifest.json | 10 + .../addons/test_delay_update_defer_v2/bootstrap.js | 10 + .../addons/test_delay_update_defer_v2/install.rdf | 27 + .../manifest.json | 10 + .../test_delay_update_ignore_v2/bootstrap.js | 8 + .../addons/test_delay_update_ignore_v2/install.rdf | 28 + .../manifest.json | 10 + .../test/addons/test_dictionary/chrome.manifest | 1 + .../addons/test_dictionary/dictionaries/ab-CD.dic | 2 + .../test/addons/test_dictionary/install.rdf | 25 + .../test_dictionary_2/dictionaries/ab-CD.dic | 2 + .../test/addons/test_dictionary_2/install.rdf | 24 + .../test/addons/test_dictionary_3/install.rdf | 25 + .../test/addons/test_dictionary_4/install.rdf | 24 + .../test/addons/test_dictionary_5/install.rdf | 25 + .../test/addons/test_distribution1_2/install.rdf | 23 + .../test/addons/test_experiment1/bootstrap.js | 1 + .../test/addons/test_experiment1/install.rdf | 16 + .../test/addons/test_filepointer/install.rdf | 22 + .../test/addons/test_getresource/icon.png | 1 + .../test/addons/test_getresource/install.rdf | 23 + .../addons/test_getresource/subdir/subfile.txt | 1 + .../test/addons/test_hotfix_1/install.rdf | 23 + .../test/addons/test_hotfix_2/install.rdf | 23 + .../test/addons/test_install1/icon.png | 1 + .../test/addons/test_install1/icon64.png | 1 + .../test/addons/test_install1/install.rdf | 24 + .../test/addons/test_install2_1/icon.png | 1 + .../test/addons/test_install2_1/install.rdf | 24 + .../test/addons/test_install2_2/install.rdf | 24 + .../test/addons/test_install3/install.rdf | 27 + .../test/addons/test_install4/addon4.xpi | Bin 0 -> 509 bytes .../test/addons/test_install4/addon5.jar | Bin 0 -> 512 bytes .../test/addons/test_install4/addon6.xpi | Bin 0 -> 512 bytes .../test/addons/test_install4/addon7.jar | Bin 0 -> 512 bytes .../test/addons/test_install4/badaddon.jar | 1 + .../test/addons/test_install4/badaddon.xpi | 1 + .../test/addons/test_install4/icon.png | 1 + .../test/addons/test_install4/install.rdf | 10 + .../test/addons/test_install5/chrome.manifest | 1 + .../test/addons/test_install5/install.rdf | 26 + .../test/addons/test_install6/install.rdf | 24 + .../test/addons/test_install7/addon1.xpi | 1 + .../test/addons/test_install7/addon2.xpi | 1 + .../test/addons/test_install7/install.rdf | 10 + .../test/addons/test_install8/install.rdf | 10 + .../test/addons/test_jetpack/bootstrap.js | 17 + .../test/addons/test_jetpack/harness-options.json | 1 + .../test/addons/test_jetpack/install.rdf | 28 + .../test/addons/test_langpack/chrome.manifest | 1 + .../test/addons/test_langpack/install.rdf | 23 + .../test/addons/test_locale/install.rdf | 61 + .../test/addons/test_locked2_5/install.rdf | 23 + .../test/addons/test_locked2_6/install.rdf | 23 + .../test/addons/test_migrate4_6/install.rdf | 23 + .../test/addons/test_migrate4_7/install.rdf | 23 + .../test/addons/test_migrate6/install.rdf | 23 + .../test/addons/test_migrate7/install.rdf | 24 + .../test/addons/test_migrate8/chrome.manifest | 6 + .../test/addons/test_migrate8/install.rdf | 24 + .../test/addons/test_migrate9/install.rdf | 26 + .../test/addons/test_symbol/bootstrap.js | 62 + .../test/addons/test_symbol/install.rdf | 28 + .../test/addons/test_theme/install.rdf | 26 + .../test/addons/test_theme/preview.png | 1 + .../test/addons/test_undoincompatible/bootstrap.js | 1 + .../test/addons/test_undoincompatible/install.rdf | 28 + .../test/addons/test_undouninstall1/bootstrap.js | 1 + .../test/addons/test_undouninstall1/install.rdf | 28 + .../test/addons/test_update/install.rdf | 23 + .../test/addons/test_update12/install.rdf | 23 + .../test/addons/test_update8/install.rdf | 23 + .../test/addons/test_update_multi1/bootstrap.js | 5 + .../test/addons/test_update_multi1/install.rdf | 16 + .../test/addons/test_update_multi2/addon.xpi | Bin 0 -> 693 bytes .../test/addons/test_update_multi2/install.rdf | 9 + .../test/addons/test_updateid1/bootstrap.js | 5 + .../test/addons/test_updateid1/install.rdf | 16 + .../test/addons/test_updateid2/bootstrap.js | 5 + .../test/addons/test_updateid2/install.rdf | 16 + .../test/addons/upgradeable1x2-3_1/install.rdf | 22 + .../test/addons/upgradeable1x2-3_2/install.rdf | 22 + .../test/addons/webextension_1/chrome.manifest | 1 + .../test/addons/webextension_1/manifest.json | 14 + .../test/addons/webextension_2/install.rdf | 30 + .../test/addons/webextension_2/manifest.json | 10 + .../webextension_3/_locales/en/messages.json | 10 + .../webextension_3/_locales/fr/messages.json | 10 + .../test/addons/webextension_3/manifest.json | 12 + .../webextensions/test/browser/.eslintrc.js | 7 + .../webextensions/test/browser/addon_about.xul | 6 + .../webextensions/test/browser/addon_prefs.xul | 6 + .../test/browser/addons/browser_bug557956_1.xpi | Bin 0 -> 4426 bytes .../browser/addons/browser_bug557956_1/install.rdf | 31 + .../test/browser/addons/browser_bug557956_10.xpi | Bin 0 -> 4425 bytes .../addons/browser_bug557956_10/install.rdf | 31 + .../test/browser/addons/browser_bug557956_2.xpi | Bin 0 -> 4427 bytes .../browser/addons/browser_bug557956_2/install.rdf | 31 + .../test/browser/addons/browser_bug557956_3.xpi | Bin 0 -> 4425 bytes .../browser/addons/browser_bug557956_3/install.rdf | 31 + .../test/browser/addons/browser_bug557956_4.xpi | Bin 0 -> 4432 bytes .../browser/addons/browser_bug557956_4/install.rdf | 31 + .../test/browser/addons/browser_bug557956_5.xpi | Bin 0 -> 4427 bytes .../browser/addons/browser_bug557956_5/install.rdf | 31 + .../test/browser/addons/browser_bug557956_6.xpi | Bin 0 -> 4424 bytes .../browser/addons/browser_bug557956_6/install.rdf | 31 + .../test/browser/addons/browser_bug557956_7.xpi | Bin 0 -> 4424 bytes .../browser/addons/browser_bug557956_7/install.rdf | 31 + .../test/browser/addons/browser_bug557956_8_1.xpi | Bin 0 -> 4427 bytes .../addons/browser_bug557956_8_1/install.rdf | 31 + .../test/browser/addons/browser_bug557956_9_1.xpi | Bin 0 -> 4421 bytes .../addons/browser_bug557956_9_1/install.rdf | 31 + .../test/browser/addons/browser_bug567127_1.xpi | Bin 0 -> 4425 bytes .../browser/addons/browser_bug567127_1/install.rdf | 30 + .../test/browser/addons/browser_bug567127_2.xpi | Bin 0 -> 4427 bytes .../browser/addons/browser_bug567127_2/install.rdf | 30 + .../test/browser/addons/browser_bug596336_1.xpi | Bin 0 -> 4449 bytes .../browser/addons/browser_bug596336_1/install.rdf | 31 + .../test/browser/addons/browser_bug596336_2.xpi | Bin 0 -> 4440 bytes .../browser/addons/browser_bug596336_2/install.rdf | 31 + .../test/browser/addons/browser_dragdrop1.xpi | Bin 0 -> 4424 bytes .../browser/addons/browser_dragdrop1/install.rdf | 30 + .../test/browser/addons/browser_dragdrop2.xpi | Bin 0 -> 4420 bytes .../browser/addons/browser_dragdrop2/install.rdf | 30 + .../test/browser/addons/browser_experiment1.xpi | Bin 0 -> 4328 bytes .../browser/addons/browser_experiment1/install.rdf | 16 + .../browser/addons/browser_inlinesettings1.xpi | Bin 0 -> 5811 bytes .../addons/browser_inlinesettings1/bootstrap.js | 8 + .../addons/browser_inlinesettings1/chrome.manifest | 1 + .../addons/browser_inlinesettings1/install.rdf | 27 + .../addons/browser_inlinesettings1/options.xul | 23 + .../addons/browser_inlinesettings1/settings.dtd | 1 + .../addons/browser_inlinesettings1_custom.xpi | Bin 0 -> 6155 bytes .../browser_inlinesettings1_custom/binding.xml | 19 + .../browser_inlinesettings1_custom/bootstrap.js | 8 + .../browser_inlinesettings1_custom/chrome.manifest | 2 + .../browser_inlinesettings1_custom/install.rdf | 27 + .../browser_inlinesettings1_custom/options.xul | 5 + .../browser_inlinesettings1_custom/string.dtd | 1 + .../addons/browser_inlinesettings1_info.xpi | Bin 0 -> 5279 bytes .../browser_inlinesettings1_info/bootstrap.js | 8 + .../browser_inlinesettings1_info/install.rdf | 28 + .../browser_inlinesettings1_info/options.xul | 19 + .../test/browser/addons/browser_install1_1.xpi | Bin 0 -> 4489 bytes .../browser/addons/browser_install1_1/install.rdf | 32 + .../test/browser/addons/browser_install1_2.xpi | Bin 0 -> 4415 bytes .../browser/addons/browser_install1_2/install.rdf | 30 + .../test/browser/addons/browser_installssl.xpi | Bin 0 -> 4430 bytes .../browser/addons/browser_installssl/install.rdf | 30 + .../test/browser/addons/browser_searching.xpi | Bin 0 -> 4808 bytes .../browser/addons/browser_searching/bootstrap.js | 9 + .../browser/addons/browser_searching/install.rdf | 25 + .../test/browser/addons/browser_update1_1.xpi | Bin 0 -> 5479 bytes .../browser/addons/browser_update1_1/bootstrap.js | 12 + .../addons/browser_update1_1/chrome.manifest | 1 + .../addons/browser_update1_1/frame-script.js | 6 + .../browser/addons/browser_update1_1/install.rdf | 31 + .../test/browser/addons/browser_update1_2.xpi | Bin 0 -> 5481 bytes .../browser/addons/browser_update1_2/bootstrap.js | 12 + .../addons/browser_update1_2/chrome.manifest | 1 + .../addons/browser_update1_2/frame-script.js | 6 + .../browser/addons/browser_update1_2/install.rdf | 31 + .../test/browser/addons/browser_webapi_install.xpi | Bin 0 -> 4782 bytes .../addons/browser_webapi_install/bootstrap.js | 9 + .../addons/browser_webapi_install/install.rdf | 29 + .../test/browser/addons/options_signed.xpi | Bin 0 -> 4560 bytes .../browser/addons/options_signed/manifest.json | 11 + .../browser/addons/options_signed/options.html | 9 + .../webextensions/test/browser/blockNoPlugins.xml | 7 + .../webextensions/test/browser/blockPluginHard.xml | 11 + .../webextensions/test/browser/browser-common.ini | 67 + .../webextensions/test/browser/browser-window.ini | 52 + .../mozapps/webextensions/test/browser/browser.ini | 75 + .../test/browser/browser_CTP_plugins.js | 172 + .../webextensions/test/browser/browser_about.js | 84 + .../browser/browser_addonrepository_performance.js | 99 + .../test/browser/browser_bug523784.js | 120 + .../test/browser/browser_bug557943.js | 80 + .../test/browser/browser_bug557956.js | 524 ++ .../test/browser/browser_bug557956.rdf | 310 + .../test/browser/browser_bug557956.xml | 20 + .../test/browser/browser_bug557956_8_2.xpi | Bin 0 -> 4438 bytes .../test/browser/browser_bug557956_9_2.xpi | Bin 0 -> 4426 bytes .../test/browser/browser_bug562797.js | 975 ++ .../test/browser/browser_bug562854.js | 129 + .../test/browser/browser_bug562890.js | 78 + .../test/browser/browser_bug562899.js | 88 + .../test/browser/browser_bug562992.js | 70 + .../test/browser/browser_bug567127.js | 136 + .../test/browser/browser_bug567137.js | 40 + .../test/browser/browser_bug570760.js | 44 + .../test/browser/browser_bug572561.js | 99 + .../test/browser/browser_bug573062.js | 116 + .../test/browser/browser_bug577990.js | 132 + .../test/browser/browser_bug580298.js | 98 + .../test/browser/browser_bug581076.js | 132 + .../test/browser/browser_bug586574.js | 286 + .../test/browser/browser_bug587970.js | 180 + .../test/browser/browser_bug590347.js | 121 + .../test/browser/browser_bug591465.js | 512 ++ .../test/browser/browser_bug591465.xml | 35 + .../test/browser/browser_bug591663.js | 161 + .../test/browser/browser_bug593535.js | 119 + .../test/browser/browser_bug593535.xml | 34 + .../test/browser/browser_bug596336.js | 156 + .../test/browser/browser_bug608316.js | 65 + .../test/browser/browser_bug610764.js | 34 + .../test/browser/browser_bug616841.js | 21 + .../test/browser/browser_bug618502.js | 44 + .../test/browser/browser_bug679604.js | 29 + .../test/browser/browser_bug714593.js | 140 + .../test/browser/browser_cancelCompatCheck.js | 462 + .../browser/browser_checkAddonCompatibility.js | 34 + .../webextensions/test/browser/browser_details.js | 1053 +++ .../test/browser/browser_discovery.js | 651 ++ .../test/browser/browser_discovery_install.js | 133 + .../webextensions/test/browser/browser_dragdrop.js | 234 + .../webextensions/test/browser/browser_eula.js | 85 + .../webextensions/test/browser/browser_eula.xml | 35 + .../test/browser/browser_experiments.js | 654 ++ .../test/browser/browser_globalwarnings.js | 63 + .../test/browser/browser_gmpProvider.js | 418 + .../webextensions/test/browser/browser_hotfix.js | 171 + .../test/browser/browser_inlinesettings.js | 680 ++ .../test/browser/browser_inlinesettings_browser.js | 207 + .../test/browser/browser_inlinesettings_custom.js | 92 + .../test/browser/browser_inlinesettings_info.js | 574 ++ .../webextensions/test/browser/browser_install.js | 312 + .../webextensions/test/browser/browser_install.rdf | 27 + .../test/browser/browser_install.rdf^headers^ | 1 + .../webextensions/test/browser/browser_install.xml | 34 + .../test/browser/browser_install1_3.xpi | Bin 0 -> 4419 bytes .../test/browser/browser_installssl.js | 374 + .../webextensions/test/browser/browser_list.js | 956 ++ .../test/browser/browser_manualupdates.js | 246 + .../test/browser/browser_metadataTimeout.js | 114 + .../webextensions/test/browser/browser_newaddon.js | 232 + .../test/browser/browser_openDialog.js | 173 + .../browser/browser_plugin_enabled_state_locked.js | 124 + .../test/browser/browser_pluginprefs.js | 61 + .../webextensions/test/browser/browser_purchase.js | 197 + .../test/browser/browser_purchase.xml | 180 + .../test/browser/browser_recentupdates.js | 125 + .../test/browser/browser_searching.js | 698 ++ .../test/browser/browser_searching.xml | 277 + .../test/browser/browser_searching_empty.xml | 3 + .../webextensions/test/browser/browser_sorting.js | 372 + .../test/browser/browser_sorting_plugins.js | 95 + .../test/browser/browser_system_addons_are_e10s.js | 13 + .../test/browser/browser_tabsettings.js | 100 + .../test/browser/browser_task_next_test.js | 17 + .../webextensions/test/browser/browser_types.js | 473 + .../test/browser/browser_uninstalling.js | 1098 +++ .../webextensions/test/browser/browser_update.js | 53 + .../webextensions/test/browser/browser_updateid.js | 84 + .../test/browser/browser_updatessl.js | 370 + .../test/browser/browser_updatessl.rdf | 25 + .../test/browser/browser_updatessl.rdf^headers^ | 1 + .../webextensions/test/browser/browser_webapi.js | 106 + .../test/browser/browser_webapi_access.js | 127 + .../test/browser/browser_webapi_addon_listener.js | 174 + .../test/browser/browser_webapi_enable.js | 62 + .../test/browser/browser_webapi_install.js | 311 + .../test/browser/browser_webapi_uninstall.js | 51 + .../test/browser/browser_webext_options.js | 70 + .../test/browser/cancelCompatCheck.sjs | 43 + .../webextensions/test/browser/discovery.html | 10 + .../test/browser/discovery_frame.html | 6 + .../test/browser/discovery_install.html | 19 + toolkit/mozapps/webextensions/test/browser/head.js | 1468 +++ .../webextensions/test/browser/more_options.xul | 32 + .../mozapps/webextensions/test/browser/moz.build | 10 + .../mozapps/webextensions/test/browser/options.xul | 12 + .../webextensions/test/browser/plugin_test.html | 7 + .../webextensions/test/browser/redirect.sjs | 5 + .../webextensions/test/browser/releaseNotes.xhtml | 15 + .../webextensions/test/browser/signed_hotfix.rdf | 26 + .../webextensions/test/browser/signed_hotfix.xpi | Bin 0 -> 2745 bytes .../webextensions/test/browser/unsigned_hotfix.rdf | 26 + .../webextensions/test/browser/unsigned_hotfix.xpi | Bin 0 -> 560 bytes .../test/browser/webapi_addon_listener.html | 30 + .../test/browser/webapi_checkavailable.html | 13 + .../test/browser/webapi_checkchromeframe.xul | 6 + .../test/browser/webapi_checkframed.html | 7 + .../test/browser/webapi_checknavigatedwindow.html | 28 + .../webextensions/test/mochitest/.eslintrc.js | 7 + .../test/mochitest/file_bug687194.xpi | Bin 0 -> 5659 bytes .../webextensions/test/mochitest/file_empty.html | 2 + .../webextensions/test/mochitest/mochitest.ini | 9 + .../test/mochitest/test_bug609794.html | 27 + .../test/mochitest/test_bug687194.html | 133 + .../test/mochitest/test_bug887098.html | 52 + toolkit/mozapps/webextensions/test/moz.build | 19 + .../webextensions/test/xpcshell/.eslintrc.js | 7 + .../test/xpcshell/data/BootstrapMonitor.jsm | 30 + .../xpcshell/data/blocklistchange/addon_change.xml | 31 + .../data/blocklistchange/addon_update1.rdf | 144 + .../data/blocklistchange/addon_update2.rdf | 144 + .../data/blocklistchange/addon_update3.rdf | 144 + .../xpcshell/data/blocklistchange/app_update.xml | 62 + .../data/blocklistchange/blocklist_update1.xml | 3 + .../data/blocklistchange/blocklist_update2.xml | 26 + .../data/blocklistchange/manual_update.xml | 27 + .../test/xpcshell/data/bug455906_block.xml | 18 + .../test/xpcshell/data/bug455906_empty.xml | 7 + .../test/xpcshell/data/bug455906_start.xml | 30 + .../test/xpcshell/data/bug455906_warn.xml | 33 + .../webextensions/test/xpcshell/data/corrupt.xpi | 1 + .../test/xpcshell/data/corruptfile.xpi | Bin 0 -> 633 bytes .../webextensions/test/xpcshell/data/empty.xpi | Bin 0 -> 197 bytes .../test/xpcshell/data/from_sources/bootstrap.js | 1 + .../test/xpcshell/data/from_sources/install.rdf | 28 + .../test/xpcshell/data/pluginInfoURL_block.xml | 45 + .../test/xpcshell/data/productaddons/bad.txt | 1 + .../test/xpcshell/data/productaddons/bad.xml | 3 + .../test/xpcshell/data/productaddons/bad2.xml | 3 + .../test/xpcshell/data/productaddons/empty.xml | 5 + .../test/xpcshell/data/productaddons/good.xml | 11 + .../test/xpcshell/data/productaddons/missing.xml | 3 + .../test/xpcshell/data/productaddons/unsigned.xpi | Bin 0 -> 452 bytes .../data/signing_checks/bootstrap_1/bootstrap.js | 29 + .../data/signing_checks/bootstrap_1/install.rdf | 24 + .../data/signing_checks/bootstrap_1/test.txt | 1 + .../data/signing_checks/bootstrap_2/bootstrap.js | 29 + .../data/signing_checks/bootstrap_2/install.rdf | 24 + .../data/signing_checks/bootstrap_2/test.txt | 1 + .../xpcshell/data/signing_checks/hotfix_badid.xpi | Bin 0 -> 5151 bytes .../xpcshell/data/signing_checks/hotfix_broken.xpi | Bin 0 -> 5298 bytes .../xpcshell/data/signing_checks/hotfix_good.xpi | Bin 0 -> 5158 bytes .../xpcshell/data/signing_checks/long_63_hash.xpi | Bin 0 -> 4471 bytes .../xpcshell/data/signing_checks/long_63_plain.xpi | Bin 0 -> 4433 bytes .../xpcshell/data/signing_checks/long_64_hash.xpi | Bin 0 -> 4474 bytes .../xpcshell/data/signing_checks/long_64_plain.xpi | Bin 0 -> 4436 bytes .../xpcshell/data/signing_checks/long_65_hash.xpi | Bin 0 -> 4487 bytes .../xpcshell/data/signing_checks/multi_badid.xpi | Bin 0 -> 6443 bytes .../xpcshell/data/signing_checks/multi_broken.xpi | Bin 0 -> 6563 bytes .../xpcshell/data/signing_checks/multi_signed.xpi | Bin 0 -> 6425 bytes .../data/signing_checks/multi_unsigned.xpi | Bin 0 -> 2436 bytes .../data/signing_checks/nonbootstrap_1/install.rdf | 23 + .../data/signing_checks/nonbootstrap_1/test.txt | 1 + .../data/signing_checks/nonbootstrap_2/install.rdf | 23 + .../data/signing_checks/nonbootstrap_2/test.txt | 1 + .../signing_checks/preliminary_bootstrap_2.xpi | Bin 0 -> 5161 bytes .../data/signing_checks/signed_bootstrap_1.xpi | Bin 0 -> 5150 bytes .../data/signing_checks/signed_bootstrap_2.xpi | Bin 0 -> 5149 bytes .../signing_checks/signed_bootstrap_badid_2.xpi | Bin 0 -> 5155 bytes .../data/signing_checks/signed_nonbootstrap_2.xpi | Bin 0 -> 4627 bytes .../signing_checks/signed_nonbootstrap_badid_2.xpi | Bin 0 -> 4634 bytes .../data/signing_checks/unsigned_bootstrap_2.xpi | Bin 0 -> 1156 bytes .../signing_checks/unsigned_nonbootstrap_2.xpi | Bin 0 -> 691 bytes .../test/xpcshell/data/system_addons/bootstrap.js | 1 + .../test/xpcshell/data/system_addons/system1_1.xpi | Bin 0 -> 4692 bytes .../data/system_addons/system1_1_badcert.xpi | Bin 0 -> 4808 bytes .../test/xpcshell/data/system_addons/system1_2.xpi | Bin 0 -> 4695 bytes .../test/xpcshell/data/system_addons/system2_1.xpi | Bin 0 -> 4692 bytes .../test/xpcshell/data/system_addons/system2_2.xpi | Bin 0 -> 4695 bytes .../test/xpcshell/data/system_addons/system2_3.xpi | Bin 0 -> 4697 bytes .../test/xpcshell/data/system_addons/system3_1.xpi | Bin 0 -> 4689 bytes .../test/xpcshell/data/system_addons/system3_2.xpi | Bin 0 -> 4691 bytes .../test/xpcshell/data/system_addons/system3_3.xpi | Bin 0 -> 4693 bytes .../test/xpcshell/data/system_addons/system4_1.xpi | Bin 0 -> 4692 bytes .../test/xpcshell/data/system_addons/system5_1.xpi | Bin 0 -> 4691 bytes .../data/system_addons/system6_1_unpack.xpi | Bin 0 -> 4708 bytes .../data/system_addons/system6_2_notBootstrap.xpi | Bin 0 -> 4682 bytes .../system_addons/system6_3_notMultiprocess.xpi | Bin 0 -> 4675 bytes .../data/system_addons/system_delay_complete.xpi | Bin 0 -> 5090 bytes .../data/system_addons/system_delay_complete_2.xpi | Bin 0 -> 4706 bytes .../data/system_addons/system_delay_defer.xpi | Bin 0 -> 5095 bytes .../data/system_addons/system_delay_defer_2.xpi | Bin 0 -> 4701 bytes .../data/system_addons/system_delay_defer_also.xpi | Bin 0 -> 5117 bytes .../system_addons/system_delay_defer_also_2.xpi | Bin 0 -> 4716 bytes .../data/system_addons/system_delay_ignore.xpi | Bin 0 -> 5098 bytes .../data/system_addons/system_delay_ignore_2.xpi | Bin 0 -> 4707 bytes .../data/system_addons/system_failed_update.xpi | Bin 0 -> 735 bytes .../test/xpcshell/data/test_AddonRepository.xml | 820 ++ .../xpcshell/data/test_AddonRepository_cache.xml | 182 + .../test_AddonRepository_compatmode_ignore.xml | 23 + .../test_AddonRepository_compatmode_normal.xml | 23 + .../test_AddonRepository_compatmode_strict.xml | 23 + .../xpcshell/data/test_AddonRepository_empty.xml | 3 + .../xpcshell/data/test_AddonRepository_failed.xml | 21 + .../data/test_AddonRepository_getAddonsByIDs.xml | 187 + .../test/xpcshell/data/test_backgroundupdate.rdf | 70 + .../data/test_blocklist_metadata_filters_1.xml | 21 + .../test/xpcshell/data/test_blocklist_prefs_1.xml | 28 + .../test/xpcshell/data/test_blocklist_regexp_1.xml | 20 + .../test/xpcshell/data/test_bug299716.rdf | 181 + .../test/xpcshell/data/test_bug299716_2.rdf | 23 + .../test/xpcshell/data/test_bug324121.rdf | 91 + .../test/xpcshell/data/test_bug393285.xml | 30 + .../test/xpcshell/data/test_bug394300.rdf | 159 + .../test/xpcshell/data/test_bug424262.xml | 185 + .../test/xpcshell/data/test_bug449027_app.xml | 333 + .../test/xpcshell/data/test_bug449027_toolkit.xml | 208 + .../test/xpcshell/data/test_bug468528.xml | 15 + .../xpcshell/data/test_bug470377/install_1.rdf | 17 + .../xpcshell/data/test_bug470377/install_2.rdf | 17 + .../xpcshell/data/test_bug470377/install_3.rdf | 17 + .../xpcshell/data/test_bug470377/install_4.rdf | 17 + .../xpcshell/data/test_bug470377/install_5.rdf | 17 + .../test/xpcshell/data/test_bug470377/update_1.rdf | 26 + .../test/xpcshell/data/test_bug470377/update_2.rdf | 26 + .../test/xpcshell/data/test_bug470377/update_3.rdf | 26 + .../test/xpcshell/data/test_bug470377/update_4.rdf | 26 + .../test/xpcshell/data/test_bug470377/update_5.rdf | 26 + .../test/xpcshell/data/test_bug514327_1.xml | 17 + .../test/xpcshell/data/test_bug514327_2.xml | 10 + .../test/xpcshell/data/test_bug514327_3_empty.xml | 4 + .../xpcshell/data/test_bug514327_3_outdated_1.xml | 13 + .../xpcshell/data/test_bug514327_3_outdated_2.xml | 13 + .../test/xpcshell/data/test_bug526598_1.xpi | Bin 0 -> 458 bytes .../test/xpcshell/data/test_bug526598_2.xpi | Bin 0 -> 458 bytes .../test/xpcshell/data/test_bug541420.xpi | Bin 0 -> 577 bytes .../test/xpcshell/data/test_bug542391.rdf | 25 + .../test/xpcshell/data/test_bug554133.xml | 292 + .../test/xpcshell/data/test_bug619730.xml | 7 + .../test/xpcshell/data/test_bug655254.rdf | 26 + .../test/xpcshell/data/test_compatoverrides.xml | 228 + .../test/xpcshell/data/test_corrupt.rdf | 44 + .../data/test_delay_update_complete/bootstrap.js | 24 + .../data/test_delay_update_defer/bootstrap.js | 34 + .../data/test_delay_update_ignore/bootstrap.js | 26 + .../xpcshell/data/test_delay_updates_complete.json | 11 + .../xpcshell/data/test_delay_updates_complete.rdf | 26 + .../xpcshell/data/test_delay_updates_defer.json | 11 + .../xpcshell/data/test_delay_updates_defer.rdf | 26 + .../xpcshell/data/test_delay_updates_ignore.json | 11 + .../xpcshell/data/test_delay_updates_ignore.rdf | 26 + .../test/xpcshell/data/test_dictionary.rdf | 65 + .../data/test_distribution2_2/bootstrap.js | 21 + .../xpcshell/data/test_distribution2_2/install.rdf | 23 + .../data/test_distribution2_2/subdir/dummy.txt | 1 + .../test_distribution2_2/subdir/subdir2/dummy2.txt | 1 + .../test/xpcshell/data/test_gfxBlacklist.xml | 304 + .../test/xpcshell/data/test_gfxBlacklist2.xml | 31 + .../test/xpcshell/data/test_gfxBlacklist_AllOS.xml | 783 ++ .../xpcshell/data/test_gfxBlacklist_OSVersion.xml | 32 + .../test/xpcshell/data/test_hotfix_1.rdf | 26 + .../test/xpcshell/data/test_hotfix_2.rdf | 26 + .../test/xpcshell/data/test_hotfix_3.rdf | 26 + .../test/xpcshell/data/test_install.rdf | 63 + .../test/xpcshell/data/test_install.xml | 53 + .../test/xpcshell/data/test_migrate.rdf | 125 + .../test/xpcshell/data/test_migrate4.rdf | 46 + .../test/xpcshell/data/test_no_update.json | 7 + .../data/test_overrideblocklist/ancient.xml | 8 + .../xpcshell/data/test_overrideblocklist/new.xml | 8 + .../xpcshell/data/test_overrideblocklist/old.xml | 8 + .../test/xpcshell/data/test_pluginBlocklistCtp.xml | 26 + .../xpcshell/data/test_pluginBlocklistCtpUndo.xml | 10 + .../test/xpcshell/data/test_proxy/bootstrap.js | 1 + .../test/xpcshell/data/test_softblocked1.xml | 9 + .../test/xpcshell/data/test_sourceURI.xml | 18 + .../test/xpcshell/data/test_temporary/bootstrap.js | 1 + .../test/xpcshell/data/test_update.json | 215 + .../test/xpcshell/data/test_update.rdf | 270 + .../test/xpcshell/data/test_update.xml | 26 + .../test/xpcshell/data/test_update_multi.rdf | 26 + .../test/xpcshell/data/test_updatecheck.json | 327 + .../test/xpcshell/data/test_updatecheck.rdf | 419 + .../xpcshell/data/test_updatecompatmode_ignore.rdf | 26 + .../xpcshell/data/test_updatecompatmode_normal.rdf | 26 + .../xpcshell/data/test_updatecompatmode_strict.rdf | 26 + .../test/xpcshell/data/test_updateid.rdf | 26 + .../webextensions/test/xpcshell/data/unsigned.xpi | Bin 0 -> 452 bytes .../test/xpcshell/data/webext-implicit-id.xpi | Bin 0 -> 4182 bytes .../webextensions/test/xpcshell/head_addons.js | 1345 +++ .../webextensions/test/xpcshell/head_unpack.js | 3 + .../test/xpcshell/test_AddonRepository.js | 625 ++ .../test/xpcshell/test_AddonRepository_cache.js | 704 ++ .../xpcshell/test_AddonRepository_compatmode.js | 90 + .../test/xpcshell/test_ChromeManifestParser.js | 108 + .../test/xpcshell/test_DeferredSave.js | 549 ++ .../test/xpcshell/test_LightweightThemeManager.js | 598 ++ .../test/xpcshell/test_ProductAddonChecker.js | 244 + .../webextensions/test/xpcshell/test_XPIStates.js | 299 + .../webextensions/test/xpcshell/test_XPIcancel.js | 66 + .../test/xpcshell/test_addon_path_service.js | 38 + .../test/xpcshell/test_asyncBlocklistLoad.js | 44 + .../test/xpcshell/test_backgroundupdate.js | 126 + .../webextensions/test/xpcshell/test_bad_json.js | 54 + .../webextensions/test/xpcshell/test_badschema.js | 404 + .../test/xpcshell/test_blocklist_gfx.js | 157 + .../xpcshell/test_blocklist_metadata_filters.js | 147 + .../test/xpcshell/test_blocklist_prefs.js | 148 + .../test/xpcshell/test_blocklist_regexp.js | 114 + .../test/xpcshell/test_blocklistchange.js | 1305 +++ .../webextensions/test/xpcshell/test_bootstrap.js | 1403 +++ .../test/xpcshell/test_bootstrap_const.js | 17 + .../test/xpcshell/test_bootstrap_globals.js | 37 + .../test/xpcshell/test_bootstrap_resource.js | 56 + .../webextensions/test/xpcshell/test_bug1180901.js | 35 + .../test/xpcshell/test_bug1180901_2.js | 60 + .../webextensions/test/xpcshell/test_bug299716.js | 208 + .../test/xpcshell/test_bug299716_2.js | 50 + .../webextensions/test/xpcshell/test_bug324121.js | 178 + .../webextensions/test/xpcshell/test_bug335238.js | 173 + .../webextensions/test/xpcshell/test_bug371495.js | 35 + .../webextensions/test/xpcshell/test_bug384052.js | 103 + .../webextensions/test/xpcshell/test_bug393285.js | 316 + .../webextensions/test/xpcshell/test_bug394300.js | 56 + .../webextensions/test/xpcshell/test_bug397778.js | 117 + .../webextensions/test/xpcshell/test_bug406118.js | 155 + .../webextensions/test/xpcshell/test_bug424262.js | 62 + .../webextensions/test/xpcshell/test_bug425657.js | 27 + .../webextensions/test/xpcshell/test_bug430120.js | 135 + .../webextensions/test/xpcshell/test_bug449027.js | 429 + .../webextensions/test/xpcshell/test_bug455906.js | 517 ++ .../webextensions/test/xpcshell/test_bug465190.js | 39 + .../webextensions/test/xpcshell/test_bug468528.js | 58 + .../test/xpcshell/test_bug470377_1.js | 49 + .../test/xpcshell/test_bug470377_1_strictcompat.js | 49 + .../test/xpcshell/test_bug470377_2.js | 49 + .../test/xpcshell/test_bug470377_3.js | 95 + .../test/xpcshell/test_bug470377_3_strictcompat.js | 94 + .../test/xpcshell/test_bug470377_4.js | 92 + .../test/xpcshell/test_bug514327_1.js | 59 + .../test/xpcshell/test_bug514327_2.js | 41 + .../test/xpcshell/test_bug514327_3.js | 139 + .../webextensions/test/xpcshell/test_bug521905.js | 59 + .../webextensions/test/xpcshell/test_bug526598.js | 54 + .../webextensions/test/xpcshell/test_bug541420.js | 37 + .../webextensions/test/xpcshell/test_bug542391.js | 464 + .../webextensions/test/xpcshell/test_bug554133.js | 86 + .../webextensions/test/xpcshell/test_bug559800.js | 71 + .../webextensions/test/xpcshell/test_bug563256.js | 259 + .../webextensions/test/xpcshell/test_bug564030.js | 63 + .../webextensions/test/xpcshell/test_bug566626.js | 112 + .../webextensions/test/xpcshell/test_bug567184.js | 53 + .../webextensions/test/xpcshell/test_bug569138.js | 147 + .../webextensions/test/xpcshell/test_bug570173.js | 61 + .../webextensions/test/xpcshell/test_bug576735.js | 66 + .../webextensions/test/xpcshell/test_bug587088.js | 174 + .../webextensions/test/xpcshell/test_bug594058.js | 88 + .../webextensions/test/xpcshell/test_bug595081.js | 27 + .../webextensions/test/xpcshell/test_bug595573.js | 40 + .../webextensions/test/xpcshell/test_bug596607.js | 147 + .../webextensions/test/xpcshell/test_bug616841.js | 26 + .../webextensions/test/xpcshell/test_bug619730.js | 64 + .../webextensions/test/xpcshell/test_bug620837.js | 145 + .../webextensions/test/xpcshell/test_bug655254.js | 164 + .../webextensions/test/xpcshell/test_bug659772.js | 340 + .../webextensions/test/xpcshell/test_bug675371.js | 91 + .../webextensions/test/xpcshell/test_bug740612.js | 40 + .../webextensions/test/xpcshell/test_bug753900.js | 86 + .../webextensions/test/xpcshell/test_bug757663.js | 112 + .../webextensions/test/xpcshell/test_bug953156.js | 51 + .../test/xpcshell/test_cache_certdb.js | 82 + .../webextensions/test/xpcshell/test_cacheflush.js | 127 + .../test_checkCompatibility_themeOverride.js | 93 + .../test/xpcshell/test_checkcompatibility.js | 196 + .../test/xpcshell/test_childprocess.js | 21 + .../test/xpcshell/test_compatoverrides.js | 259 + .../webextensions/test/xpcshell/test_corrupt.js | 406 + .../test/xpcshell/test_corrupt_strictcompat.js | 405 + .../test/xpcshell/test_corruptfile.js | 83 + .../test/xpcshell/test_dataDirectory.js | 50 + .../test/xpcshell/test_default_providers_pref.js | 13 + .../test/xpcshell/test_delay_update.js | 260 + .../xpcshell/test_delay_update_webextension.js | 344 + .../test/xpcshell/test_dependencies.js | 144 + .../webextensions/test/xpcshell/test_dictionary.js | 811 ++ .../webextensions/test/xpcshell/test_disable.js | 194 + .../test/xpcshell/test_distribution.js | 273 + .../webextensions/test/xpcshell/test_dss.js | 824 ++ .../test/xpcshell/test_duplicateplugins.js | 187 + .../test/xpcshell/test_e10s_restartless.js | 429 + .../webextensions/test/xpcshell/test_error.js | 90 + .../webextensions/test/xpcshell/test_experiment.js | 131 + .../test/xpcshell/test_ext_management.js | 137 + .../test/xpcshell/test_filepointer.js | 403 + .../webextensions/test/xpcshell/test_fuel.js | 165 + .../webextensions/test/xpcshell/test_general.js | 58 + .../test/xpcshell/test_getresource.js | 94 + .../test/xpcshell/test_gfxBlacklist_Device.js | 96 + .../test/xpcshell/test_gfxBlacklist_DriverNew.js | 92 + .../xpcshell/test_gfxBlacklist_Equal_DriverNew.js | 123 + .../xpcshell/test_gfxBlacklist_Equal_DriverOld.js | 93 + .../test/xpcshell/test_gfxBlacklist_Equal_OK.js | 93 + .../xpcshell/test_gfxBlacklist_GTE_DriverOld.js | 93 + .../test/xpcshell/test_gfxBlacklist_GTE_OK.js | 93 + .../xpcshell/test_gfxBlacklist_No_Comparison.js | 89 + .../test/xpcshell/test_gfxBlacklist_OK.js | 94 + .../test/xpcshell/test_gfxBlacklist_OS.js | 93 + .../xpcshell/test_gfxBlacklist_OSVersion_match.js | 95 + ...fxBlacklist_OSVersion_mismatch_DriverVersion.js | 95 + ...st_gfxBlacklist_OSVersion_mismatch_OSVersion.js | 96 + .../test/xpcshell/test_gfxBlacklist_Vendor.js | 93 + .../test/xpcshell/test_gfxBlacklist_Version.js | 145 + .../test/xpcshell/test_gfxBlacklist_prefs.js | 135 + .../test/xpcshell/test_gmpProvider.js | 416 + .../test/xpcshell/test_hasbinarycomponents.js | 82 + .../webextensions/test/xpcshell/test_hotfix.js | 309 + .../test/xpcshell/test_hotfix_cert.js | 167 + .../webextensions/test/xpcshell/test_install.js | 1843 ++++ .../test/xpcshell/test_install_from_sources.js | 80 + .../test/xpcshell/test_install_icons.js | 61 + .../test/xpcshell/test_install_strictcompat.js | 1726 ++++ .../test/xpcshell/test_isDebuggable.js | 36 + .../webextensions/test/xpcshell/test_isReady.js | 49 + .../test/xpcshell/test_json_updatecheck.js | 372 + .../webextensions/test/xpcshell/test_langpack.js | 339 + .../webextensions/test/xpcshell/test_locale.js | 149 + .../webextensions/test/xpcshell/test_locked.js | 544 ++ .../webextensions/test/xpcshell/test_locked2.js | 297 + .../test/xpcshell/test_locked_strictcompat.js | 567 ++ .../webextensions/test/xpcshell/test_manifest.js | 562 ++ .../test/xpcshell/test_mapURIToAddonID.js | 347 + .../test/xpcshell/test_metadata_update.js | 159 + .../webextensions/test/xpcshell/test_migrate1.js | 231 + .../webextensions/test/xpcshell/test_migrate2.js | 267 + .../webextensions/test/xpcshell/test_migrate3.js | 229 + .../webextensions/test/xpcshell/test_migrate4.js | 321 + .../webextensions/test/xpcshell/test_migrate5.js | 139 + .../test/xpcshell/test_migrateAddonRepository.js | 127 + .../test/xpcshell/test_migrate_max_version.js | 103 + .../test/xpcshell/test_multiprocessCompatible.js | 120 + .../webextensions/test/xpcshell/test_no_addons.js | 98 + .../test/xpcshell/test_nodisable_hidden.js | 107 + .../xpcshell/test_onPropertyChanged_appDisabled.js | 66 + .../test/xpcshell/test_overrideblocklist.js | 200 + .../test/xpcshell/test_pass_symbol.js | 43 + .../test/xpcshell/test_permissions.js | 86 + .../test/xpcshell/test_permissions_prefs.js | 74 + .../test/xpcshell/test_pluginBlocklistCtp.js | 182 + .../test/xpcshell/test_pluginInfoURL.js | 90 + .../test/xpcshell/test_pluginchange.js | 283 + .../webextensions/test/xpcshell/test_plugins.js | 210 + .../test/xpcshell/test_pref_properties.js | 221 + .../test/xpcshell/test_provider_markSafe.js | 49 + .../test/xpcshell/test_provider_shutdown.js | 99 + .../test_provider_unsafe_access_shutdown.js | 64 + .../test_provider_unsafe_access_startup.js | 55 + .../webextensions/test/xpcshell/test_proxies.js | 240 + .../webextensions/test/xpcshell/test_proxy.js | 106 + .../webextensions/test/xpcshell/test_registry.js | 158 + .../webextensions/test/xpcshell/test_reload.js | 235 + .../webextensions/test/xpcshell/test_safemode.js | 115 + .../test/xpcshell/test_schema_change.js | 317 + .../webextensions/test/xpcshell/test_seen.js | 211 + .../test/xpcshell/test_seen_newprofile.js | 41 + .../webextensions/test/xpcshell/test_shutdown.js | 85 + .../test/xpcshell/test_signed_inject.js | 382 + .../test/xpcshell/test_signed_install.js | 265 + .../test/xpcshell/test_signed_long.js | 49 + .../test/xpcshell/test_signed_migrate.js | 194 + .../test/xpcshell/test_signed_multi.js | 55 + .../test/xpcshell/test_signed_updatepref.js | 136 + .../test/xpcshell/test_signed_verify.js | 234 + .../test/xpcshell/test_softblocked.js | 109 + .../webextensions/test/xpcshell/test_sourceURI.js | 66 + .../webextensions/test/xpcshell/test_startup.js | 932 ++ .../test/xpcshell/test_strictcompatibility.js | 203 + .../webextensions/test/xpcshell/test_switch_os.js | 52 + .../webextensions/test/xpcshell/test_syncGUID.js | 156 + .../test/xpcshell/test_system_delay_update.js | 461 + .../test/xpcshell/test_system_reset.js | 418 + .../test/xpcshell/test_system_update.js | 788 ++ .../test/xpcshell/test_targetPlatforms.js | 146 + .../webextensions/test/xpcshell/test_temporary.js | 760 ++ .../webextensions/test/xpcshell/test_theme.js | 1139 +++ .../webextensions/test/xpcshell/test_types.js | 65 + .../test/xpcshell/test_undothemeuninstall.js | 423 + .../test/xpcshell/test_undouninstall.js | 792 ++ .../webextensions/test/xpcshell/test_uninstall.js | 216 + .../webextensions/test/xpcshell/test_update.js | 1398 +++ .../test/xpcshell/test_updateCancel.js | 138 + .../test/xpcshell/test_update_compatmode.js | 184 + .../test/xpcshell/test_update_ignorecompat.js | 107 + .../test/xpcshell/test_update_strictcompat.js | 1126 +++ .../test/xpcshell/test_update_webextensions.js | 248 + .../test/xpcshell/test_updatecheck.js | 236 + .../webextensions/test/xpcshell/test_updateid.js | 86 + .../webextensions/test/xpcshell/test_upgrade.js | 206 + .../test/xpcshell/test_upgrade_strictcompat.js | 209 + .../test/xpcshell/test_webextension.js | 421 + .../test/xpcshell/test_webextension_embedded.js | 306 + .../test/xpcshell/test_webextension_icons.js | 169 + .../test/xpcshell/test_webextension_install.js | 478 + .../test/xpcshell/test_webextension_paths.js | 55 + .../test/xpcshell/xpcshell-shared.ini | 334 + .../test/xpcshell/xpcshell-unpack.ini | 12 + .../webextensions/test/xpcshell/xpcshell.ini | 50 + .../webextensions/test/xpinstall/.eslintrc.js | 7 + .../webextensions/test/xpinstall/amosigned.xpi | Bin 0 -> 4420 bytes .../webextensions/test/xpinstall/amosigned2.xpi | Bin 0 -> 4421 bytes .../webextensions/test/xpinstall/authRedirect.sjs | 21 + .../webextensions/test/xpinstall/browser.ini | 119 + .../test/xpinstall/browser_amosigned_trigger.js | 56 + .../xpinstall/browser_amosigned_trigger_iframe.js | 57 + .../test/xpinstall/browser_amosigned_url.js | 35 + .../webextensions/test/xpinstall/browser_auth.js | 47 + .../webextensions/test/xpinstall/browser_auth2.js | 46 + .../webextensions/test/xpinstall/browser_auth3.js | 53 + .../webextensions/test/xpinstall/browser_auth4.js | 52 + .../test/xpinstall/browser_badargs.js | 38 + .../test/xpinstall/browser_badargs2.js | 42 + .../test/xpinstall/browser_badhash.js | 33 + .../test/xpinstall/browser_badhashtype.js | 33 + .../test/xpinstall/browser_bug540558.js | 25 + .../test/xpinstall/browser_bug611242.js | 17 + .../test/xpinstall/browser_bug638292.js | 40 + .../test/xpinstall/browser_bug645699.js | 36 + .../test/xpinstall/browser_bug672485.js | 52 + .../webextensions/test/xpinstall/browser_cancel.js | 60 + .../test/xpinstall/browser_concurrent_installs.js | 127 + .../test/xpinstall/browser_cookies.js | 30 + .../test/xpinstall/browser_cookies2.js | 40 + .../test/xpinstall/browser_cookies3.js | 44 + .../test/xpinstall/browser_cookies4.js | 43 + .../test/xpinstall/browser_corrupt.js | 38 + .../test/xpinstall/browser_datauri.js | 37 + .../webextensions/test/xpinstall/browser_empty.js | 28 + .../test/xpinstall/browser_enabled.js | 29 + .../test/xpinstall/browser_enabled2.js | 32 + .../test/xpinstall/browser_enabled3.js | 52 + .../webextensions/test/xpinstall/browser_hash.js | 34 + .../webextensions/test/xpinstall/browser_hash2.js | 34 + .../test/xpinstall/browser_httphash.js | 39 + .../test/xpinstall/browser_httphash2.js | 39 + .../test/xpinstall/browser_httphash3.js | 39 + .../test/xpinstall/browser_httphash4.js | 36 + .../test/xpinstall/browser_httphash5.js | 40 + .../test/xpinstall/browser_httphash6.js | 83 + .../test/xpinstall/browser_installchrome.js | 25 + .../test/xpinstall/browser_localfile.js | 35 + .../test/xpinstall/browser_localfile2.js | 38 + .../test/xpinstall/browser_localfile3.js | 41 + .../test/xpinstall/browser_localfile4.js | 41 + .../test/xpinstall/browser_multipackage.js | 52 + .../test/xpinstall/browser_navigateaway.js | 36 + .../test/xpinstall/browser_navigateaway2.js | 34 + .../test/xpinstall/browser_navigateaway3.js | 38 + .../test/xpinstall/browser_navigateaway4.js | 44 + .../test/xpinstall/browser_offline.js | 62 + .../test/xpinstall/browser_relative.js | 55 + .../test/xpinstall/browser_signed_multipackage.js | 53 + .../test/xpinstall/browser_signed_multiple.js | 72 + .../test/xpinstall/browser_signed_naming.js | 67 + .../test/xpinstall/browser_signed_tampered.js | 33 + .../test/xpinstall/browser_signed_trigger.js | 41 + .../test/xpinstall/browser_signed_untrusted.js | 41 + .../test/xpinstall/browser_signed_url.js | 34 + .../test/xpinstall/browser_softwareupdate.js | 25 + .../test/xpinstall/browser_switchtab.js | 49 + .../test/xpinstall/browser_trigger_redirect.js | 41 + .../test/xpinstall/browser_unsigned_trigger.js | 56 + .../xpinstall/browser_unsigned_trigger_iframe.js | 57 + .../xpinstall/browser_unsigned_trigger_xorigin.js | 38 + .../test/xpinstall/browser_unsigned_url.js | 35 + .../test/xpinstall/browser_whitelist.js | 53 + .../test/xpinstall/browser_whitelist2.js | 31 + .../test/xpinstall/browser_whitelist3.js | 28 + .../test/xpinstall/browser_whitelist4.js | 30 + .../test/xpinstall/browser_whitelist5.js | 25 + .../test/xpinstall/browser_whitelist6.js | 25 + .../test/xpinstall/browser_whitelist7.js | 32 + .../webextensions/test/xpinstall/bug540558.html | 23 + .../webextensions/test/xpinstall/bug638292.html | 17 + .../webextensions/test/xpinstall/bug645699.html | 31 + .../test/xpinstall/concurrent_installs.html | 40 + .../test/xpinstall/cookieRedirect.sjs | 24 + .../webextensions/test/xpinstall/corrupt.xpi | 1 + .../mozapps/webextensions/test/xpinstall/empty.xpi | Bin 0 -> 197 bytes .../webextensions/test/xpinstall/enabled.html | 24 + .../webextensions/test/xpinstall/hashRedirect.sjs | 15 + .../mozapps/webextensions/test/xpinstall/head.js | 434 + .../webextensions/test/xpinstall/incompatible.xpi | Bin 0 -> 4442 bytes .../test/xpinstall/installchrome.html | 22 + .../test/xpinstall/installtrigger.html | 44 + .../test/xpinstall/installtrigger_frame.html | 29 + .../webextensions/test/xpinstall/multipackage.xpi | Bin 0 -> 9589 bytes .../webextensions/test/xpinstall/navigate.html | 26 + .../webextensions/test/xpinstall/redirect.sjs | 45 + .../test/xpinstall/restartless-unsigned.xpi | Bin 0 -> 528 bytes .../webextensions/test/xpinstall/restartless.xpi | Bin 0 -> 4447 bytes .../test/xpinstall/signed-multipackage.xpi | Bin 0 -> 2976 bytes .../webextensions/test/xpinstall/signed-no-cn.xpi | Bin 0 -> 2241 bytes .../webextensions/test/xpinstall/signed-no-o.xpi | Bin 0 -> 2247 bytes .../test/xpinstall/signed-tampered.xpi | Bin 0 -> 2260 bytes .../test/xpinstall/signed-untrusted.xpi | Bin 0 -> 2237 bytes .../webextensions/test/xpinstall/signed.xpi | Bin 0 -> 2250 bytes .../webextensions/test/xpinstall/signed2.xpi | Bin 0 -> 2938 bytes .../webextensions/test/xpinstall/slowinstall.sjs | 101 + .../test/xpinstall/startsoftwareupdate.html | 20 + .../mozapps/webextensions/test/xpinstall/theme.xpi | Bin 0 -> 4450 bytes .../test/xpinstall/triggerredirect.html | 36 + .../webextensions/test/xpinstall/unsigned.xpi | Bin 0 -> 452 bytes .../mozapps/extensions/category-available.png | Bin 1092 -> 0 bytes .../linux/mozapps/extensions/category-discover.png | Bin 1482 -> 0 bytes .../linux/mozapps/extensions/category-plugins.png | Bin 1172 -> 0 bytes .../linux/mozapps/extensions/category-recent.png | Bin 2020 -> 0 bytes .../linux/mozapps/extensions/category-search.png | Bin 2600 -> 0 bytes .../linux/mozapps/extensions/category-service.png | Bin 2063 -> 0 bytes .../mozapps/extensions/dictionaryGeneric-16.png | Bin 584 -> 0 bytes .../linux/mozapps/extensions/dictionaryGeneric.png | Bin 1290 -> 0 bytes .../mozapps/extensions/extensionGeneric-16.png | Bin 713 -> 0 bytes .../themes/linux/mozapps/extensions/extensions.css | 42 - toolkit/themes/linux/mozapps/extensions/heart.png | Bin 2949 -> 0 bytes .../linux/mozapps/extensions/localeGeneric.png | Bin 1860 -> 0 bytes .../themes/linux/mozapps/extensions/newaddon.css | 5 - .../linux/mozapps/extensions/themeGeneric-16.png | Bin 638 -> 0 bytes .../linux/mozapps/extensions/themeGeneric.png | Bin 1734 -> 0 bytes toolkit/themes/linux/mozapps/jar.mn | 32 +- .../mozapps/webextensions/category-available.png | Bin 0 -> 1092 bytes .../mozapps/webextensions/category-discover.png | Bin 0 -> 1482 bytes .../mozapps/webextensions/category-plugins.png | Bin 0 -> 1172 bytes .../mozapps/webextensions/category-recent.png | Bin 0 -> 2020 bytes .../mozapps/webextensions/category-search.png | Bin 0 -> 2600 bytes .../mozapps/webextensions/category-service.png | Bin 0 -> 2063 bytes .../mozapps/webextensions/dictionaryGeneric-16.png | Bin 0 -> 584 bytes .../mozapps/webextensions/dictionaryGeneric.png | Bin 0 -> 1290 bytes .../mozapps/webextensions/extensionGeneric-16.png | Bin 0 -> 713 bytes .../linux/mozapps/webextensions/extensions.css | 42 + .../themes/linux/mozapps/webextensions/heart.png | Bin 0 -> 2949 bytes .../linux/mozapps/webextensions/localeGeneric.png | Bin 0 -> 1860 bytes .../linux/mozapps/webextensions/newaddon.css | 5 + .../mozapps/webextensions/themeGeneric-16.png | Bin 0 -> 638 bytes .../linux/mozapps/webextensions/themeGeneric.png | Bin 0 -> 1734 bytes toolkit/themes/osx/mozapps/extensions/about.css | 78 - .../themes/osx/mozapps/extensions/blocklist.css | 20 - toolkit/themes/osx/mozapps/extensions/cancel.png | Bin 115 -> 0 bytes .../osx/mozapps/extensions/category-available.png | Bin 1671 -> 0 bytes .../mozapps/extensions/category-dictionaries.png | Bin 1769 -> 0 bytes .../osx/mozapps/extensions/category-discover.png | Bin 1324 -> 0 bytes .../mozapps/extensions/category-experiments.png | Bin 822 -> 0 bytes .../osx/mozapps/extensions/category-plugins.png | Bin 886 -> 0 bytes .../osx/mozapps/extensions/category-recent.png | Bin 1642 -> 0 bytes .../osx/mozapps/extensions/category-search.png | Bin 2600 -> 0 bytes .../osx/mozapps/extensions/category-service.png | Bin 2063 -> 0 bytes .../mozapps/extensions/dictionaryGeneric-16.png | Bin 742 -> 0 bytes .../osx/mozapps/extensions/dictionaryGeneric.png | Bin 1769 -> 0 bytes .../osx/mozapps/extensions/discover-logo.png | Bin 12007 -> 0 bytes toolkit/themes/osx/mozapps/extensions/eula.css | 47 - .../osx/mozapps/extensions/experimentGeneric.png | Bin 822 -> 0 bytes .../osx/mozapps/extensions/extensionGeneric-16.png | Bin 554 -> 0 bytes .../themes/osx/mozapps/extensions/extensions.css | 51 - toolkit/themes/osx/mozapps/extensions/heart.png | Bin 2949 -> 0 bytes .../osx/mozapps/extensions/localeGeneric.png | Bin 2410 -> 0 bytes toolkit/themes/osx/mozapps/extensions/newaddon.css | 5 - .../osx/mozapps/extensions/rating-not-won.png | Bin 1559 -> 0 bytes .../themes/osx/mozapps/extensions/rating-won.png | Bin 1662 -> 0 bytes toolkit/themes/osx/mozapps/extensions/search.png | Bin 423 -> 0 bytes .../osx/mozapps/extensions/themeGeneric-16.png | Bin 710 -> 0 bytes .../themes/osx/mozapps/extensions/themeGeneric.png | Bin 2185 -> 0 bytes .../extensions/toolbarbutton-dropmarker.png | Bin 147 -> 0 bytes toolkit/themes/osx/mozapps/extensions/update.css | 28 - .../osx/mozapps/extensions/xpinstallConfirm.css | 90 - toolkit/themes/osx/mozapps/jar.mn | 64 +- toolkit/themes/osx/mozapps/webextensions/about.css | 78 + .../themes/osx/mozapps/webextensions/blocklist.css | 20 + .../themes/osx/mozapps/webextensions/cancel.png | Bin 0 -> 115 bytes .../mozapps/webextensions/category-available.png | Bin 0 -> 1671 bytes .../webextensions/category-dictionaries.png | Bin 0 -> 1769 bytes .../mozapps/webextensions/category-discover.png | Bin 0 -> 1324 bytes .../mozapps/webextensions/category-experiments.png | Bin 0 -> 822 bytes .../osx/mozapps/webextensions/category-plugins.png | Bin 0 -> 886 bytes .../osx/mozapps/webextensions/category-recent.png | Bin 0 -> 1642 bytes .../osx/mozapps/webextensions/category-search.png | Bin 0 -> 2600 bytes .../osx/mozapps/webextensions/category-service.png | Bin 0 -> 2063 bytes .../mozapps/webextensions/dictionaryGeneric-16.png | Bin 0 -> 742 bytes .../mozapps/webextensions/dictionaryGeneric.png | Bin 0 -> 1769 bytes .../osx/mozapps/webextensions/discover-logo.png | Bin 0 -> 12007 bytes toolkit/themes/osx/mozapps/webextensions/eula.css | 47 + .../mozapps/webextensions/experimentGeneric.png | Bin 0 -> 822 bytes .../mozapps/webextensions/extensionGeneric-16.png | Bin 0 -> 554 bytes .../osx/mozapps/webextensions/extensions.css | 51 + toolkit/themes/osx/mozapps/webextensions/heart.png | Bin 0 -> 2949 bytes .../osx/mozapps/webextensions/localeGeneric.png | Bin 0 -> 2410 bytes .../themes/osx/mozapps/webextensions/newaddon.css | 5 + .../osx/mozapps/webextensions/rating-not-won.png | Bin 0 -> 1559 bytes .../osx/mozapps/webextensions/rating-won.png | Bin 0 -> 1662 bytes .../themes/osx/mozapps/webextensions/search.png | Bin 0 -> 423 bytes .../osx/mozapps/webextensions/themeGeneric-16.png | Bin 0 -> 710 bytes .../osx/mozapps/webextensions/themeGeneric.png | Bin 0 -> 2185 bytes .../webextensions/toolbarbutton-dropmarker.png | Bin 0 -> 147 bytes .../themes/osx/mozapps/webextensions/update.css | 28 + .../osx/mozapps/webextensions/xpinstallConfirm.css | 90 + .../themes/shared/extensions/alerticon-error.svg | 6 - .../shared/extensions/alerticon-info-negative.svg | 6 - .../shared/extensions/alerticon-info-positive.svg | 6 - .../themes/shared/extensions/alerticon-warning.svg | 6 - .../themes/shared/extensions/extensionGeneric.svg | 12 - .../themes/shared/extensions/extensions.inc.css | 1082 --- toolkit/themes/shared/extensions/navigation.png | Bin 663 -> 0 bytes toolkit/themes/shared/extensions/newaddon.inc.css | 114 - toolkit/themes/shared/extensions/utilities.svg | 30 - toolkit/themes/shared/mozapps.inc.mn | 16 +- toolkit/themes/shared/non-mac.jar.inc.mn | 27 +- .../shared/webextensions/alerticon-error.svg | 6 + .../webextensions/alerticon-info-negative.svg | 6 + .../webextensions/alerticon-info-positive.svg | 6 + .../shared/webextensions/alerticon-warning.svg | 6 + .../shared/webextensions/extensionGeneric.svg | 12 + .../themes/shared/webextensions/extensions.inc.css | 1082 +++ toolkit/themes/shared/webextensions/navigation.png | Bin 0 -> 663 bytes .../themes/shared/webextensions/newaddon.inc.css | 114 + toolkit/themes/shared/webextensions/utilities.svg | 30 + .../themes/windows/mozapps/extensions/about.css | 91 - .../windows/mozapps/extensions/blocklist.css | 20 - .../themes/windows/mozapps/extensions/cancel.png | Bin 115 -> 0 bytes .../mozapps/extensions/category-available-XP.png | Bin 1671 -> 0 bytes .../mozapps/extensions/category-available.png | Bin 2235 -> 0 bytes .../mozapps/extensions/category-discover-XP.png | Bin 1324 -> 0 bytes .../mozapps/extensions/category-discover.png | Bin 1355 -> 0 bytes .../mozapps/extensions/category-plugins-XP.png | Bin 886 -> 0 bytes .../mozapps/extensions/category-plugins.png | Bin 962 -> 0 bytes .../mozapps/extensions/category-recent-XP.png | Bin 1642 -> 0 bytes .../windows/mozapps/extensions/category-recent.png | Bin 2251 -> 0 bytes .../windows/mozapps/extensions/category-search.png | Bin 2600 -> 0 bytes .../mozapps/extensions/category-service.png | Bin 2063 -> 0 bytes .../mozapps/extensions/dictionaryGeneric-16.png | Bin 733 -> 0 bytes .../mozapps/extensions/dictionaryGeneric.png | Bin 1665 -> 0 bytes .../windows/mozapps/extensions/discover-logo.png | Bin 12007 -> 0 bytes toolkit/themes/windows/mozapps/extensions/eula.css | 47 - .../mozapps/extensions/experimentGeneric.png | Bin 822 -> 0 bytes .../mozapps/extensions/extensionGeneric-16-XP.png | Bin 398 -> 0 bytes .../mozapps/extensions/extensionGeneric-16.png | Bin 418 -> 0 bytes .../windows/mozapps/extensions/extensions.css | 42 - .../themes/windows/mozapps/extensions/heart.png | Bin 2949 -> 0 bytes .../mozapps/extensions/localeGeneric-XP.png | Bin 2410 -> 0 bytes .../windows/mozapps/extensions/localeGeneric.png | Bin 2518 -> 0 bytes .../themes/windows/mozapps/extensions/newaddon.css | 5 - .../windows/mozapps/extensions/rating-not-won.png | Bin 1559 -> 0 bytes .../windows/mozapps/extensions/rating-won.png | Bin 1662 -> 0 bytes .../mozapps/extensions/themeGeneric-16-XP.png | Bin 842 -> 0 bytes .../windows/mozapps/extensions/themeGeneric-16.png | Bin 837 -> 0 bytes .../windows/mozapps/extensions/themeGeneric-XP.png | Bin 2185 -> 0 bytes .../windows/mozapps/extensions/themeGeneric.png | Bin 2094 -> 0 bytes .../themes/windows/mozapps/extensions/update.css | 28 - .../mozapps/extensions/xpinstallConfirm.css | 101 - toolkit/themes/windows/mozapps/jar.mn | 52 +- .../themes/windows/mozapps/webextensions/about.css | 91 + .../windows/mozapps/webextensions/blocklist.css | 20 + .../windows/mozapps/webextensions/cancel.png | Bin 0 -> 115 bytes .../webextensions/category-available-XP.png | Bin 0 -> 1671 bytes .../mozapps/webextensions/category-available.png | Bin 0 -> 2235 bytes .../mozapps/webextensions/category-discover-XP.png | Bin 0 -> 1324 bytes .../mozapps/webextensions/category-discover.png | Bin 0 -> 1355 bytes .../mozapps/webextensions/category-plugins-XP.png | Bin 0 -> 886 bytes .../mozapps/webextensions/category-plugins.png | Bin 0 -> 962 bytes .../mozapps/webextensions/category-recent-XP.png | Bin 0 -> 1642 bytes .../mozapps/webextensions/category-recent.png | Bin 0 -> 2251 bytes .../mozapps/webextensions/category-search.png | Bin 0 -> 2600 bytes .../mozapps/webextensions/category-service.png | Bin 0 -> 2063 bytes .../mozapps/webextensions/dictionaryGeneric-16.png | Bin 0 -> 733 bytes .../mozapps/webextensions/dictionaryGeneric.png | Bin 0 -> 1665 bytes .../mozapps/webextensions/discover-logo.png | Bin 0 -> 12007 bytes .../themes/windows/mozapps/webextensions/eula.css | 47 + .../mozapps/webextensions/experimentGeneric.png | Bin 0 -> 822 bytes .../webextensions/extensionGeneric-16-XP.png | Bin 0 -> 398 bytes .../mozapps/webextensions/extensionGeneric-16.png | Bin 0 -> 418 bytes .../windows/mozapps/webextensions/extensions.css | 42 + .../themes/windows/mozapps/webextensions/heart.png | Bin 0 -> 2949 bytes .../mozapps/webextensions/localeGeneric-XP.png | Bin 0 -> 2410 bytes .../mozapps/webextensions/localeGeneric.png | Bin 0 -> 2518 bytes .../windows/mozapps/webextensions/newaddon.css | 5 + .../mozapps/webextensions/rating-not-won.png | Bin 0 -> 1559 bytes .../windows/mozapps/webextensions/rating-won.png | Bin 0 -> 1662 bytes .../mozapps/webextensions/themeGeneric-16-XP.png | Bin 0 -> 842 bytes .../mozapps/webextensions/themeGeneric-16.png | Bin 0 -> 837 bytes .../mozapps/webextensions/themeGeneric-XP.png | Bin 0 -> 2185 bytes .../windows/mozapps/webextensions/themeGeneric.png | Bin 0 -> 2094 bytes .../windows/mozapps/webextensions/update.css | 28 + .../mozapps/webextensions/xpinstallConfirm.css | 101 + 2190 files changed, 129251 insertions(+), 129224 deletions(-) delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/about.dtd delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/newaddon.dtd delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/newaddon.properties delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/update.dtd delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/update.properties delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/xpinstallConfirm.dtd delete mode 100644 toolkit/locales/en-US/chrome/mozapps/extensions/xpinstallConfirm.properties create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/about.dtd create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.dtd create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.properties create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/newaddon.dtd create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/newaddon.properties create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/update.dtd create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/update.properties create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/xpinstallConfirm.dtd create mode 100644 toolkit/locales/en-US/chrome/mozapps/webextensions/xpinstallConfirm.properties delete mode 100644 toolkit/mozapps/extensions/.eslintrc.js delete mode 100644 toolkit/mozapps/extensions/AddonContentPolicy.cpp delete mode 100644 toolkit/mozapps/extensions/AddonContentPolicy.h delete mode 100644 toolkit/mozapps/extensions/AddonManager.jsm delete mode 100644 toolkit/mozapps/extensions/AddonManagerWebAPI.cpp delete mode 100644 toolkit/mozapps/extensions/AddonManagerWebAPI.h delete mode 100644 toolkit/mozapps/extensions/AddonPathService.cpp delete mode 100644 toolkit/mozapps/extensions/AddonPathService.h delete mode 100644 toolkit/mozapps/extensions/ChromeManifestParser.jsm delete mode 100644 toolkit/mozapps/extensions/DeferredSave.jsm delete mode 100644 toolkit/mozapps/extensions/LightweightThemeManager.jsm delete mode 100644 toolkit/mozapps/extensions/addonManager.js delete mode 100644 toolkit/mozapps/extensions/amContentHandler.js delete mode 100644 toolkit/mozapps/extensions/amIAddonManager.idl delete mode 100644 toolkit/mozapps/extensions/amIAddonPathService.idl delete mode 100644 toolkit/mozapps/extensions/amIWebInstallListener.idl delete mode 100644 toolkit/mozapps/extensions/amIWebInstaller.idl delete mode 100644 toolkit/mozapps/extensions/amInstallTrigger.js delete mode 100644 toolkit/mozapps/extensions/amWebAPI.js delete mode 100644 toolkit/mozapps/extensions/amWebInstallListener.js delete mode 100644 toolkit/mozapps/extensions/content/OpenH264-license.txt delete mode 100644 toolkit/mozapps/extensions/content/about.js delete mode 100644 toolkit/mozapps/extensions/content/about.xul delete mode 100644 toolkit/mozapps/extensions/content/blocklist.css delete mode 100644 toolkit/mozapps/extensions/content/blocklist.js delete mode 100644 toolkit/mozapps/extensions/content/blocklist.xml delete mode 100644 toolkit/mozapps/extensions/content/blocklist.xul delete mode 100644 toolkit/mozapps/extensions/content/eula.js delete mode 100644 toolkit/mozapps/extensions/content/eula.xul delete mode 100644 toolkit/mozapps/extensions/content/extensions.css delete mode 100644 toolkit/mozapps/extensions/content/extensions.js delete mode 100644 toolkit/mozapps/extensions/content/extensions.xml delete mode 100644 toolkit/mozapps/extensions/content/extensions.xul delete mode 100644 toolkit/mozapps/extensions/content/gmpPrefs.xul delete mode 100644 toolkit/mozapps/extensions/content/list.js delete mode 100644 toolkit/mozapps/extensions/content/list.xul delete mode 100644 toolkit/mozapps/extensions/content/newaddon.js delete mode 100644 toolkit/mozapps/extensions/content/newaddon.xul delete mode 100644 toolkit/mozapps/extensions/content/pluginPrefs.xul delete mode 100644 toolkit/mozapps/extensions/content/setting.xml delete mode 100644 toolkit/mozapps/extensions/content/update.js delete mode 100644 toolkit/mozapps/extensions/content/update.xul delete mode 100644 toolkit/mozapps/extensions/content/updateinfo.xsl delete mode 100644 toolkit/mozapps/extensions/content/xpinstallConfirm.css delete mode 100644 toolkit/mozapps/extensions/content/xpinstallConfirm.js delete mode 100644 toolkit/mozapps/extensions/content/xpinstallConfirm.xul delete mode 100644 toolkit/mozapps/extensions/content/xpinstallItem.xml delete mode 100644 toolkit/mozapps/extensions/docs/SystemAddons.rst delete mode 100644 toolkit/mozapps/extensions/docs/index.rst delete mode 100644 toolkit/mozapps/extensions/extensions.manifest delete mode 100644 toolkit/mozapps/extensions/internal/APIExtensionBootstrap.js delete mode 100644 toolkit/mozapps/extensions/internal/AddonConstants.jsm delete mode 100644 toolkit/mozapps/extensions/internal/AddonLogging.jsm delete mode 100644 toolkit/mozapps/extensions/internal/AddonRepository.jsm delete mode 100644 toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm delete mode 100644 toolkit/mozapps/extensions/internal/AddonTestUtils.jsm delete mode 100644 toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm delete mode 100644 toolkit/mozapps/extensions/internal/Content.js delete mode 100644 toolkit/mozapps/extensions/internal/E10SAddonsRollout.jsm delete mode 100644 toolkit/mozapps/extensions/internal/GMPProvider.jsm delete mode 100644 toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm delete mode 100644 toolkit/mozapps/extensions/internal/PluginProvider.jsm delete mode 100644 toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm delete mode 100644 toolkit/mozapps/extensions/internal/SpellCheckDictionaryBootstrap.js delete mode 100644 toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js delete mode 100644 toolkit/mozapps/extensions/internal/XPIProvider.jsm delete mode 100644 toolkit/mozapps/extensions/internal/XPIProviderUtils.js delete mode 100644 toolkit/mozapps/extensions/internal/moz.build delete mode 100644 toolkit/mozapps/extensions/jar.mn delete mode 100644 toolkit/mozapps/extensions/moz.build delete mode 100644 toolkit/mozapps/extensions/nsBlocklistService.js delete mode 100644 toolkit/mozapps/extensions/nsBlocklistServiceContent.js delete mode 100644 toolkit/mozapps/extensions/test/AddonManagerTesting.jsm delete mode 100644 toolkit/mozapps/extensions/test/Makefile.in delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_hard1_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_hard1_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_hard1_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_regexp1_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_regexp1_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_regexp1_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft1_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft1_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft1_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft2_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft2_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft2_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft3_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft3_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft3_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft4_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft4_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft4_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft5_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft5_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/blocklist_soft5_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/bootstrap_globals/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/bootstrap_globals/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/min1max1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/min1max2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/min1max3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/min1max3b/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/override1x2-1x3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_3/icon.png delete mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_AddonRepository_3/preview.png delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_1/version.jsm delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_2/version.jsm delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_3/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_3/version.jsm delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap1_4/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap2_1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap2_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap_const/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bootstrap_const/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_a_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_a_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_b_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_b_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_c_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_c_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_d_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_d_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_e_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_e_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_f_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_f_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_g_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug299716_g_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_4/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_5/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_6/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_7/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_8/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug324121_9/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug335238_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug335238_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug335238_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug335238_4/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug371495/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug394300_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug394300_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug397778/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug425657/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_4/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug470377_5/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug521905/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug567173/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug567184/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug567184/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_1/testfile delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_1/testfile1 delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_2/testfile delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug587088_2/testfile2 delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug594058/directory/file1 delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug594058/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug595573/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug655254/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug655254_2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug655254_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug659772/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug675371/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug675371/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug675371/test.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug740612_1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug740612_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug740612_2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug740612_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_bug757663/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_cacheflush1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_cacheflush2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_1/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_2/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_3/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_3/inner.jar delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/components/components.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/components/other/something.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_4/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_5/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_5/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_6/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_chromemanifest_6/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_data_directory/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_db_sanity_1_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_db_sanity_1_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_complete_v2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_complete_v2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_complete_webextension_v2/manifest.json delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_defer_v2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_defer_v2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_defer_webextension_v2/manifest.json delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_ignore_v2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_ignore_v2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_delay_update_ignore_webextension_v2/manifest.json delete mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary/dictionaries/ab-CD.dic delete mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_2/dictionaries/ab-CD.dic delete mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_4/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_dictionary_5/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_distribution1_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_experiment1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_experiment1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_filepointer/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_getresource/icon.png delete mode 100644 toolkit/mozapps/extensions/test/addons/test_getresource/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_getresource/subdir/subfile.txt delete mode 100644 toolkit/mozapps/extensions/test/addons/test_hotfix_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_hotfix_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install1/icon.png delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install1/icon64.png delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install2_1/icon.png delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install2_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install2_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/addon4.xpi delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/addon5.jar delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/addon6.xpi delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/addon7.jar delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/badaddon.jar delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/badaddon.xpi delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/icon.png delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install4/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install5/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install5/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install6/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install7/addon1.xpi delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install7/addon2.xpi delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install7/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_install8/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_jetpack/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_jetpack/harness-options.json delete mode 100644 toolkit/mozapps/extensions/test/addons/test_jetpack/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_langpack/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_langpack/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_locale/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_locked2_5/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_locked2_6/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate4_6/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate4_7/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate6/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate7/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate8/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate8/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_migrate9/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_symbol/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_symbol/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_theme/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_theme/preview.png delete mode 100644 toolkit/mozapps/extensions/test/addons/test_undoincompatible/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_undoincompatible/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_undouninstall1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_undouninstall1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_update/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_update12/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_update8/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_update_multi1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_update_multi1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_update_multi2/addon.xpi delete mode 100644 toolkit/mozapps/extensions/test/addons/test_update_multi2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_updateid1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_updateid1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/test_updateid2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/addons/test_updateid2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/upgradeable1x2-3_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/upgradeable1x2-3_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/webextension_1/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/addons/webextension_1/manifest.json delete mode 100644 toolkit/mozapps/extensions/test/addons/webextension_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/addons/webextension_2/manifest.json delete mode 100644 toolkit/mozapps/extensions/test/addons/webextension_3/_locales/en/messages.json delete mode 100644 toolkit/mozapps/extensions/test/addons/webextension_3/_locales/fr/messages.json delete mode 100644 toolkit/mozapps/extensions/test/addons/webextension_3/manifest.json delete mode 100644 toolkit/mozapps/extensions/test/browser/.eslintrc.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addon_about.xul delete mode 100644 toolkit/mozapps/extensions/test/browser/addon_prefs.xul delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_10.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_10/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_3.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_3/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_4.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_4/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_5.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_5/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_6.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_6/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_7.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_7/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_8_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_8_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_9_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug557956_9_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug567127_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug567127_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug567127_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug567127_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug596336_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug596336_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug596336_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_bug596336_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_dragdrop1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_dragdrop1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_dragdrop2.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_dragdrop2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_experiment1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_experiment1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/options.xul delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/settings.dtd delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/binding.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/options.xul delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_custom/string.dtd delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_info.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_info/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_info/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1_info/options.xul delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_install1_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_install1_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_install1_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_install1_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_installssl.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_installssl/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_searching.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_searching/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_searching/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/frame-script.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/chrome.manifest delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/frame-script.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_update1_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/options_signed.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/options_signed/manifest.json delete mode 100644 toolkit/mozapps/extensions/test/browser/addons/options_signed/options.html delete mode 100644 toolkit/mozapps/extensions/test/browser/blockNoPlugins.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/blockPluginHard.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser-common.ini delete mode 100644 toolkit/mozapps/extensions/test/browser/browser-window.ini delete mode 100644 toolkit/mozapps/extensions/test/browser/browser.ini delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_about.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_addonrepository_performance.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug523784.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557943.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956_8_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug557956_9_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562797.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562854.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562890.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562899.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug562992.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug567127.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug567137.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug570760.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug572561.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug573062.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug577990.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug580298.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug581076.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug586574.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug587970.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug590347.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug591465.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug591465.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug591663.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug593535.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug593535.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug596336.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug608316.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug610764.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug616841.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug618502.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug679604.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_bug714593.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_cancelCompatCheck.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_checkAddonCompatibility.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_details.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_discovery.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_discovery_install.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_dragdrop.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_eula.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_eula.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_experiments.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_globalwarnings.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_hotfix.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_inlinesettings_custom.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_inlinesettings_info.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_install.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_install.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_install.rdf^headers^ delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_install.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_install1_3.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_installssl.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_list.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_manualupdates.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_metadataTimeout.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_newaddon.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_openDialog.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_pluginprefs.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_purchase.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_purchase.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_recentupdates.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_searching.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_searching.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_searching_empty.xml delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_sorting.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_sorting_plugins.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_system_addons_are_e10s.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_tabsettings.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_task_next_test.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_types.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_uninstalling.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_update.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_updateid.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_updatessl.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_updatessl.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_updatessl.rdf^headers^ delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_access.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_addon_listener.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_enable.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_install.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_webapi_uninstall.js delete mode 100644 toolkit/mozapps/extensions/test/browser/browser_webext_options.js delete mode 100644 toolkit/mozapps/extensions/test/browser/cancelCompatCheck.sjs delete mode 100644 toolkit/mozapps/extensions/test/browser/discovery.html delete mode 100644 toolkit/mozapps/extensions/test/browser/discovery_frame.html delete mode 100644 toolkit/mozapps/extensions/test/browser/discovery_install.html delete mode 100644 toolkit/mozapps/extensions/test/browser/head.js delete mode 100644 toolkit/mozapps/extensions/test/browser/more_options.xul delete mode 100644 toolkit/mozapps/extensions/test/browser/moz.build delete mode 100644 toolkit/mozapps/extensions/test/browser/options.xul delete mode 100644 toolkit/mozapps/extensions/test/browser/plugin_test.html delete mode 100644 toolkit/mozapps/extensions/test/browser/redirect.sjs delete mode 100644 toolkit/mozapps/extensions/test/browser/releaseNotes.xhtml delete mode 100644 toolkit/mozapps/extensions/test/browser/signed_hotfix.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/signed_hotfix.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/unsigned_hotfix.rdf delete mode 100644 toolkit/mozapps/extensions/test/browser/unsigned_hotfix.xpi delete mode 100644 toolkit/mozapps/extensions/test/browser/webapi_addon_listener.html delete mode 100644 toolkit/mozapps/extensions/test/browser/webapi_checkavailable.html delete mode 100644 toolkit/mozapps/extensions/test/browser/webapi_checkchromeframe.xul delete mode 100644 toolkit/mozapps/extensions/test/browser/webapi_checkframed.html delete mode 100644 toolkit/mozapps/extensions/test/browser/webapi_checknavigatedwindow.html delete mode 100644 toolkit/mozapps/extensions/test/mochitest/.eslintrc.js delete mode 100644 toolkit/mozapps/extensions/test/mochitest/file_bug687194.xpi delete mode 100644 toolkit/mozapps/extensions/test/mochitest/file_empty.html delete mode 100644 toolkit/mozapps/extensions/test/mochitest/mochitest.ini delete mode 100644 toolkit/mozapps/extensions/test/mochitest/test_bug609794.html delete mode 100644 toolkit/mozapps/extensions/test/mochitest/test_bug687194.html delete mode 100644 toolkit/mozapps/extensions/test/mochitest/test_bug887098.html delete mode 100644 toolkit/mozapps/extensions/test/moz.build delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_change.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update1.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update2.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/addon_update3.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/app_update.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/blocklist_update1.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/blocklist_update2.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/blocklistchange/manual_update.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/bug455906_block.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/bug455906_empty.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/bug455906_start.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/bug455906_warn.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/corrupt.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/corruptfile.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/empty.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/from_sources/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/from_sources/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/pluginInfoURL_block.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.txt delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/bad2.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/empty.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/good.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/missing.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/productaddons/unsigned.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_1/test.txt delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/bootstrap_2/test.txt delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/hotfix_badid.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/hotfix_broken.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/hotfix_good.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_63_hash.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_63_plain.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_64_hash.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_64_plain.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/long_65_hash.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/multi_badid.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/multi_broken.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/multi_signed.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/multi_unsigned.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_1/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_1/test.txt delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/nonbootstrap_2/test.txt delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/preliminary_bootstrap_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_bootstrap_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_bootstrap_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_bootstrap_badid_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_nonbootstrap_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/signed_nonbootstrap_badid_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/unsigned_bootstrap_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/signing_checks/unsigned_nonbootstrap_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_1_badcert.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system1_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system2_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system2_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system2_3.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system3_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system3_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system3_3.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system4_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system5_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system6_1_unpack.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system6_2_notBootstrap.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system6_3_notMultiprocess.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_complete.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_complete_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_defer.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_defer_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_defer_also.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_defer_also_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_ignore.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_ignore_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_failed_update.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_cache.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_compatmode_ignore.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_compatmode_normal.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_compatmode_strict.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_empty.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_failed.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_backgroundupdate.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_metadata_filters_1.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_prefs_1.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_regexp_1.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug299716.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug299716_2.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug324121.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug393285.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug394300.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug424262.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug468528.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_1.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_2.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_3.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_4.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/install_5.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_1.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_2.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_3.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_4.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug470377/update_5.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_1.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_2.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_empty.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_1.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug514327_3_outdated_2.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug526598_1.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug526598_2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug541420.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug542391.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug554133.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug619730.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_bug655254.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_compatoverrides.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_corrupt.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_complete/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_defer/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_update_ignore/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete.json delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_complete.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer.json delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_defer.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore.json delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_delay_updates_ignore.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_dictionary.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/install.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/subdir/dummy.txt delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/subdir/subdir2/dummy2.txt delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist2.xml delete mode 100755 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_AllOS.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_OSVersion.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_hotfix_1.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_hotfix_2.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_hotfix_3.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_install.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_install.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_migrate4.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_no_update.json delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/ancient.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/new.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/old.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtp.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtpUndo.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_proxy/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_softblocked1.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_sourceURI.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_temporary/bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.json delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.xml delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update_multi.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_ignore.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_normal.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecompatmode_strict.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updateid.rdf delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/unsigned.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/webext-implicit-id.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_addons.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_unpack.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_compatmode.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_XPIcancel.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_addon_path_service.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_backgroundupdate.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_badschema.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklist_metadata_filters.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklist_prefs.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklist_regexp.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_const.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_globals.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_resource.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug1180901.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug1180901_2.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug299716.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug299716_2.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug393285.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug394300.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug406118.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug424262.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug430120.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug449027.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug465190.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug468528.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_1.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_1_strictcompat.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_2.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug514327_1.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug514327_2.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug514327_3.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug554133.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug594058.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug595081.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug619730.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug620837.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_bug953156.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_cache_certdb.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_checkCompatibility_themeOverride.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_childprocess.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_corruptfile.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_dataDirectory.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_delay_update.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_dependencies.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_disable.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_distribution.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_dss.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_error.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_experiment.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_fuel.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_general.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_getresource.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Device.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_DriverNew.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_DriverNew.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_DriverOld.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Equal_OK.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_GTE_DriverOld.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_GTE_OK.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_No_Comparison.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OK.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OS.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_match.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Vendor.js delete mode 100755 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Version.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_prefs.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_hotfix_cert.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_from_sources.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_icons.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_install_strictcompat.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_isDebuggable.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_isReady.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_langpack.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_locale.js delete mode 100755 toolkit/mozapps/extensions/test/xpcshell/test_locked.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_locked2.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_manifest.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_mapURIToAddonID.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrateAddonRepository.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_multiprocessCompatible.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_no_addons.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_nodisable_hidden.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_onPropertyChanged_appDisabled.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pass_symbol.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_permissions.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pluginInfoURL.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_plugins.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_pref_properties.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_provider_markSafe.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_provider_shutdown.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_provider_unsafe_access_shutdown.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_provider_unsafe_access_startup.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_proxies.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_proxy.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_registry.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_reload.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_safemode.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_schema_change.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_seen.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_seen_newprofile.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_long.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_multi.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_updatepref.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_softblocked.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_sourceURI.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_startup.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_switch_os.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_system_update.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_targetPlatforms.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_temporary.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_theme.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_types.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updateCancel.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_compatmode.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_update_webextensions.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_updateid.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_upgrade.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_upgrade_strictcompat.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_webextension_paths.js delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/.eslintrc.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/amosigned2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/authRedirect.sjs delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser.ini delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_trigger_iframe.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_amosigned_url.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_auth.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_auth2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_auth3.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_auth4.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_badargs.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_badargs2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_badhash.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_badhashtype.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug540558.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug611242.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug645699.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_bug672485.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_concurrent_installs.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cookies.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_corrupt.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_datauri.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_empty.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_enabled.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_enabled2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_hash.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_hash2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash3.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash4.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash5.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_installchrome.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_multipackage.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_navigateaway.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_navigateaway2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_navigateaway3.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_navigateaway4.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_offline.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_relative.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_multipackage.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_multiple.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_naming.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_tampered.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_trigger.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_untrusted.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_signed_url.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_softwareupdate.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_switchtab.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_trigger_redirect.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_iframe.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_trigger_xorigin.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_unsigned_url.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist2.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist3.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist4.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist5.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist6.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/browser_whitelist7.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/bug540558.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/bug638292.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/bug645699.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/concurrent_installs.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/empty.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/enabled.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/head.js delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/installchrome.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/installtrigger.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/multipackage.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/navigate.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/redirect.sjs delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/restartless.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-multipackage.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-no-cn.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-no-o.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-tampered.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed-untrusted.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/signed2.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/theme.xpi delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html delete mode 100644 toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi create mode 100644 toolkit/mozapps/webextensions/.eslintrc.js create mode 100644 toolkit/mozapps/webextensions/AddonContentPolicy.cpp create mode 100644 toolkit/mozapps/webextensions/AddonContentPolicy.h create mode 100644 toolkit/mozapps/webextensions/AddonManager.jsm create mode 100644 toolkit/mozapps/webextensions/AddonManagerWebAPI.cpp create mode 100644 toolkit/mozapps/webextensions/AddonManagerWebAPI.h create mode 100644 toolkit/mozapps/webextensions/AddonPathService.cpp create mode 100644 toolkit/mozapps/webextensions/AddonPathService.h create mode 100644 toolkit/mozapps/webextensions/ChromeManifestParser.jsm create mode 100644 toolkit/mozapps/webextensions/DeferredSave.jsm create mode 100644 toolkit/mozapps/webextensions/LightweightThemeManager.jsm create mode 100644 toolkit/mozapps/webextensions/addonManager.js create mode 100644 toolkit/mozapps/webextensions/amContentHandler.js create mode 100644 toolkit/mozapps/webextensions/amIAddonManager.idl create mode 100644 toolkit/mozapps/webextensions/amIAddonPathService.idl create mode 100644 toolkit/mozapps/webextensions/amIWebInstallListener.idl create mode 100644 toolkit/mozapps/webextensions/amIWebInstaller.idl create mode 100644 toolkit/mozapps/webextensions/amInstallTrigger.js create mode 100644 toolkit/mozapps/webextensions/amWebAPI.js create mode 100644 toolkit/mozapps/webextensions/amWebInstallListener.js create mode 100644 toolkit/mozapps/webextensions/content/OpenH264-license.txt create mode 100644 toolkit/mozapps/webextensions/content/about.js create mode 100644 toolkit/mozapps/webextensions/content/about.xul create mode 100644 toolkit/mozapps/webextensions/content/blocklist.css create mode 100644 toolkit/mozapps/webextensions/content/blocklist.js create mode 100644 toolkit/mozapps/webextensions/content/blocklist.xml create mode 100644 toolkit/mozapps/webextensions/content/blocklist.xul create mode 100644 toolkit/mozapps/webextensions/content/eula.js create mode 100644 toolkit/mozapps/webextensions/content/eula.xul create mode 100644 toolkit/mozapps/webextensions/content/extensions.css create mode 100644 toolkit/mozapps/webextensions/content/extensions.js create mode 100644 toolkit/mozapps/webextensions/content/extensions.xml create mode 100644 toolkit/mozapps/webextensions/content/extensions.xul create mode 100644 toolkit/mozapps/webextensions/content/gmpPrefs.xul create mode 100644 toolkit/mozapps/webextensions/content/list.js create mode 100644 toolkit/mozapps/webextensions/content/list.xul create mode 100644 toolkit/mozapps/webextensions/content/newaddon.js create mode 100644 toolkit/mozapps/webextensions/content/newaddon.xul create mode 100644 toolkit/mozapps/webextensions/content/pluginPrefs.xul create mode 100644 toolkit/mozapps/webextensions/content/setting.xml create mode 100644 toolkit/mozapps/webextensions/content/update.js create mode 100644 toolkit/mozapps/webextensions/content/update.xul create mode 100644 toolkit/mozapps/webextensions/content/updateinfo.xsl create mode 100644 toolkit/mozapps/webextensions/content/xpinstallConfirm.css create mode 100644 toolkit/mozapps/webextensions/content/xpinstallConfirm.js create mode 100644 toolkit/mozapps/webextensions/content/xpinstallConfirm.xul create mode 100644 toolkit/mozapps/webextensions/content/xpinstallItem.xml create mode 100644 toolkit/mozapps/webextensions/docs/SystemAddons.rst create mode 100644 toolkit/mozapps/webextensions/docs/index.rst create mode 100644 toolkit/mozapps/webextensions/extensions.manifest create mode 100644 toolkit/mozapps/webextensions/internal/APIExtensionBootstrap.js create mode 100644 toolkit/mozapps/webextensions/internal/AddonConstants.jsm create mode 100644 toolkit/mozapps/webextensions/internal/AddonLogging.jsm create mode 100644 toolkit/mozapps/webextensions/internal/AddonRepository.jsm create mode 100644 toolkit/mozapps/webextensions/internal/AddonRepository_SQLiteMigrator.jsm create mode 100644 toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm create mode 100644 toolkit/mozapps/webextensions/internal/AddonUpdateChecker.jsm create mode 100644 toolkit/mozapps/webextensions/internal/Content.js create mode 100644 toolkit/mozapps/webextensions/internal/E10SAddonsRollout.jsm create mode 100644 toolkit/mozapps/webextensions/internal/GMPProvider.jsm create mode 100644 toolkit/mozapps/webextensions/internal/LightweightThemeImageOptimizer.jsm create mode 100644 toolkit/mozapps/webextensions/internal/PluginProvider.jsm create mode 100644 toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm create mode 100644 toolkit/mozapps/webextensions/internal/SpellCheckDictionaryBootstrap.js create mode 100644 toolkit/mozapps/webextensions/internal/WebExtensionBootstrap.js create mode 100644 toolkit/mozapps/webextensions/internal/XPIProvider.jsm create mode 100644 toolkit/mozapps/webextensions/internal/XPIProviderUtils.js create mode 100644 toolkit/mozapps/webextensions/internal/moz.build create mode 100644 toolkit/mozapps/webextensions/jar.mn create mode 100644 toolkit/mozapps/webextensions/moz.build create mode 100644 toolkit/mozapps/webextensions/nsBlocklistService.js create mode 100644 toolkit/mozapps/webextensions/nsBlocklistServiceContent.js create mode 100644 toolkit/mozapps/webextensions/test/AddonManagerTesting.jsm create mode 100644 toolkit/mozapps/webextensions/test/Makefile.in create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_hard1_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_hard1_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_hard1_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_regexp1_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_regexp1_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_regexp1_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft1_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft1_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft1_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft2_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft2_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft2_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft3_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft3_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft3_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft4_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft4_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft4_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft5_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft5_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/blocklist_soft5_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/bootstrap_globals/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/bootstrap_globals/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/min1max1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/min1max2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/min1max3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/min1max3b/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/override1x2-1x3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_AddonRepository_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_AddonRepository_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_AddonRepository_3/icon.png create mode 100644 toolkit/mozapps/webextensions/test/addons/test_AddonRepository_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_AddonRepository_3/preview.png create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_1/version.jsm create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_2/version.jsm create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_3/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_3/version.jsm create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap1_4/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap2_1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap2_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap_const/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bootstrap_const/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_a_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_a_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_b_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_b_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_c_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_c_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_d_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_d_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_e_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_e_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_f_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_f_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_g_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug299716_g_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_4/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_5/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_6/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_7/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_8/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug324121_9/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug335238_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug335238_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug335238_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug335238_4/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug371495/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug394300_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug394300_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug397778/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug425657/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug470377_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug470377_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug470377_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug470377_4/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug470377_5/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug521905/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug567173/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug567184/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug567184/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug587088_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug587088_1/testfile create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug587088_1/testfile1 create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug587088_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug587088_2/testfile create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug587088_2/testfile2 create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug594058/directory/file1 create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug594058/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug595573/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug655254/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug655254_2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug655254_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug659772/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug675371/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug675371/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug675371/test.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug740612_1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug740612_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug740612_2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug740612_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_bug757663/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_cacheflush1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_cacheflush2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_1/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_2/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_3/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_3/inner.jar create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_4/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_4/components/components.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_4/components/other/something.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_4/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_5/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_5/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_6/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_chromemanifest_6/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_data_directory/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_db_sanity_1_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_db_sanity_1_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_complete_v2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_complete_v2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_complete_webextension_v2/manifest.json create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_defer_v2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_defer_v2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_defer_webextension_v2/manifest.json create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_ignore_v2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_ignore_v2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_delay_update_ignore_webextension_v2/manifest.json create mode 100644 toolkit/mozapps/webextensions/test/addons/test_dictionary/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_dictionary/dictionaries/ab-CD.dic create mode 100644 toolkit/mozapps/webextensions/test/addons/test_dictionary/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_dictionary_2/dictionaries/ab-CD.dic create mode 100644 toolkit/mozapps/webextensions/test/addons/test_dictionary_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_dictionary_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_dictionary_4/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_dictionary_5/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_distribution1_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_experiment1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_experiment1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_filepointer/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_getresource/icon.png create mode 100644 toolkit/mozapps/webextensions/test/addons/test_getresource/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_getresource/subdir/subfile.txt create mode 100644 toolkit/mozapps/webextensions/test/addons/test_hotfix_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_hotfix_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install1/icon.png create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install1/icon64.png create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install2_1/icon.png create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install2_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install2_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install4/addon4.xpi create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install4/addon5.jar create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install4/addon6.xpi create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install4/addon7.jar create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install4/badaddon.jar create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install4/badaddon.xpi create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install4/icon.png create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install4/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install5/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install5/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install6/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install7/addon1.xpi create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install7/addon2.xpi create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install7/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_install8/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_jetpack/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_jetpack/harness-options.json create mode 100644 toolkit/mozapps/webextensions/test/addons/test_jetpack/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_langpack/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_langpack/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_locale/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_locked2_5/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_locked2_6/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_migrate4_6/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_migrate4_7/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_migrate6/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_migrate7/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_migrate8/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/test_migrate8/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_migrate9/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_symbol/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_symbol/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_theme/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_theme/preview.png create mode 100644 toolkit/mozapps/webextensions/test/addons/test_undoincompatible/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_undoincompatible/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_undouninstall1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_undouninstall1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_update/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_update12/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_update8/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_update_multi1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_update_multi1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_update_multi2/addon.xpi create mode 100644 toolkit/mozapps/webextensions/test/addons/test_update_multi2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_updateid1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_updateid1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/test_updateid2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/addons/test_updateid2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/upgradeable1x2-3_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/upgradeable1x2-3_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/webextension_1/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/addons/webextension_1/manifest.json create mode 100644 toolkit/mozapps/webextensions/test/addons/webextension_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/addons/webextension_2/manifest.json create mode 100644 toolkit/mozapps/webextensions/test/addons/webextension_3/_locales/en/messages.json create mode 100644 toolkit/mozapps/webextensions/test/addons/webextension_3/_locales/fr/messages.json create mode 100644 toolkit/mozapps/webextensions/test/addons/webextension_3/manifest.json create mode 100644 toolkit/mozapps/webextensions/test/browser/.eslintrc.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addon_about.xul create mode 100644 toolkit/mozapps/webextensions/test/browser/addon_prefs.xul create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_10.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_10/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_3.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_3/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_4.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_4/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_5.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_5/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_6.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_6/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_7.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_7/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_8_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_8_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_9_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug557956_9_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug567127_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug567127_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug567127_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug567127_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug596336_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug596336_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug596336_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_bug596336_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_dragdrop1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_dragdrop1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_dragdrop2.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_dragdrop2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_experiment1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_experiment1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1/options.xul create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1/settings.dtd create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_custom.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_custom/binding.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_custom/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_custom/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_custom/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_custom/options.xul create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_custom/string.dtd create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_info.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_info/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_info/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_inlinesettings1_info/options.xul create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_install1_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_install1_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_install1_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_install1_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_installssl.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_installssl/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_searching.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_searching/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_searching/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_1/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_1/frame-script.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_2/chrome.manifest create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_2/frame-script.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_update1_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_webapi_install.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_webapi_install/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/browser_webapi_install/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/options_signed.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/options_signed/manifest.json create mode 100644 toolkit/mozapps/webextensions/test/browser/addons/options_signed/options.html create mode 100644 toolkit/mozapps/webextensions/test/browser/blockNoPlugins.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/blockPluginHard.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser-common.ini create mode 100644 toolkit/mozapps/webextensions/test/browser/browser-window.ini create mode 100644 toolkit/mozapps/webextensions/test/browser/browser.ini create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_CTP_plugins.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_about.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_addonrepository_performance.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug523784.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug557943.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug557956.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug557956.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug557956.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug557956_8_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug557956_9_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug562797.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug562854.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug562890.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug562899.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug562992.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug567127.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug567137.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug570760.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug572561.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug573062.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug577990.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug580298.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug581076.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug586574.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug587970.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug590347.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug591465.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug591465.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug591663.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug593535.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug593535.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug596336.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug608316.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug610764.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug616841.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug618502.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug679604.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_bug714593.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_cancelCompatCheck.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_checkAddonCompatibility.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_details.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_discovery.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_discovery_install.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_dragdrop.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_eula.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_eula.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_experiments.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_globalwarnings.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_gmpProvider.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_hotfix.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_inlinesettings.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_inlinesettings_browser.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_inlinesettings_custom.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_inlinesettings_info.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_install.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_install.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_install.rdf^headers^ create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_install.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_install1_3.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_installssl.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_list.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_manualupdates.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_metadataTimeout.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_newaddon.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_openDialog.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_plugin_enabled_state_locked.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_pluginprefs.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_purchase.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_purchase.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_recentupdates.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_searching.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_searching.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_searching_empty.xml create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_sorting.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_sorting_plugins.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_system_addons_are_e10s.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_tabsettings.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_task_next_test.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_types.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_uninstalling.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_update.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_updateid.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_updatessl.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_updatessl.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_updatessl.rdf^headers^ create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_webapi.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_webapi_access.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_webapi_addon_listener.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_webapi_enable.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_webapi_install.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_webapi_uninstall.js create mode 100644 toolkit/mozapps/webextensions/test/browser/browser_webext_options.js create mode 100644 toolkit/mozapps/webextensions/test/browser/cancelCompatCheck.sjs create mode 100644 toolkit/mozapps/webextensions/test/browser/discovery.html create mode 100644 toolkit/mozapps/webextensions/test/browser/discovery_frame.html create mode 100644 toolkit/mozapps/webextensions/test/browser/discovery_install.html create mode 100644 toolkit/mozapps/webextensions/test/browser/head.js create mode 100644 toolkit/mozapps/webextensions/test/browser/more_options.xul create mode 100644 toolkit/mozapps/webextensions/test/browser/moz.build create mode 100644 toolkit/mozapps/webextensions/test/browser/options.xul create mode 100644 toolkit/mozapps/webextensions/test/browser/plugin_test.html create mode 100644 toolkit/mozapps/webextensions/test/browser/redirect.sjs create mode 100644 toolkit/mozapps/webextensions/test/browser/releaseNotes.xhtml create mode 100644 toolkit/mozapps/webextensions/test/browser/signed_hotfix.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/signed_hotfix.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/unsigned_hotfix.rdf create mode 100644 toolkit/mozapps/webextensions/test/browser/unsigned_hotfix.xpi create mode 100644 toolkit/mozapps/webextensions/test/browser/webapi_addon_listener.html create mode 100644 toolkit/mozapps/webextensions/test/browser/webapi_checkavailable.html create mode 100644 toolkit/mozapps/webextensions/test/browser/webapi_checkchromeframe.xul create mode 100644 toolkit/mozapps/webextensions/test/browser/webapi_checkframed.html create mode 100644 toolkit/mozapps/webextensions/test/browser/webapi_checknavigatedwindow.html create mode 100644 toolkit/mozapps/webextensions/test/mochitest/.eslintrc.js create mode 100644 toolkit/mozapps/webextensions/test/mochitest/file_bug687194.xpi create mode 100644 toolkit/mozapps/webextensions/test/mochitest/file_empty.html create mode 100644 toolkit/mozapps/webextensions/test/mochitest/mochitest.ini create mode 100644 toolkit/mozapps/webextensions/test/mochitest/test_bug609794.html create mode 100644 toolkit/mozapps/webextensions/test/mochitest/test_bug687194.html create mode 100644 toolkit/mozapps/webextensions/test/mochitest/test_bug887098.html create mode 100644 toolkit/mozapps/webextensions/test/moz.build create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/.eslintrc.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/BootstrapMonitor.jsm create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/blocklistchange/addon_change.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/blocklistchange/addon_update1.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/blocklistchange/addon_update2.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/blocklistchange/addon_update3.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/blocklistchange/app_update.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/blocklistchange/blocklist_update1.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/blocklistchange/blocklist_update2.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/blocklistchange/manual_update.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/bug455906_block.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/bug455906_empty.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/bug455906_start.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/bug455906_warn.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/corrupt.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/corruptfile.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/empty.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/from_sources/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/from_sources/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/pluginInfoURL_block.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/productaddons/bad.txt create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/productaddons/bad.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/productaddons/bad2.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/productaddons/empty.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/productaddons/good.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/productaddons/missing.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/productaddons/unsigned.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/bootstrap_1/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/bootstrap_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/bootstrap_1/test.txt create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/bootstrap_2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/bootstrap_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/bootstrap_2/test.txt create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/hotfix_badid.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/hotfix_broken.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/hotfix_good.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/long_63_hash.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/long_63_plain.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/long_64_hash.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/long_64_plain.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/long_65_hash.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/multi_badid.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/multi_broken.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/multi_signed.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/multi_unsigned.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/nonbootstrap_1/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/nonbootstrap_1/test.txt create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/nonbootstrap_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/nonbootstrap_2/test.txt create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/preliminary_bootstrap_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/signed_bootstrap_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/signed_bootstrap_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/signed_bootstrap_badid_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/signed_nonbootstrap_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/signed_nonbootstrap_badid_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/unsigned_bootstrap_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/signing_checks/unsigned_nonbootstrap_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system1_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system1_1_badcert.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system1_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system2_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system2_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system2_3.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system3_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system3_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system3_3.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system4_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system5_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system6_1_unpack.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system6_2_notBootstrap.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system6_3_notMultiprocess.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_delay_complete.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_delay_complete_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_delay_defer.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_delay_defer_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_delay_defer_also.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_delay_defer_also_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_delay_ignore.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_delay_ignore_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/system_addons/system_failed_update.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_AddonRepository.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_AddonRepository_cache.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_AddonRepository_compatmode_ignore.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_AddonRepository_compatmode_normal.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_AddonRepository_compatmode_strict.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_AddonRepository_empty.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_AddonRepository_failed.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_AddonRepository_getAddonsByIDs.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_backgroundupdate.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_blocklist_metadata_filters_1.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_blocklist_prefs_1.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_blocklist_regexp_1.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug299716.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug299716_2.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug324121.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug393285.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug394300.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug424262.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug449027_app.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug449027_toolkit.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug468528.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/install_1.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/install_2.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/install_3.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/install_4.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/install_5.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/update_1.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/update_2.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/update_3.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/update_4.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug470377/update_5.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug514327_1.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug514327_2.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug514327_3_empty.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug514327_3_outdated_1.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug514327_3_outdated_2.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug526598_1.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug526598_2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug541420.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug542391.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug554133.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug619730.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_bug655254.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_compatoverrides.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_corrupt.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_update_complete/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_update_defer/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_update_ignore/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_updates_complete.json create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_updates_complete.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_updates_defer.json create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_updates_defer.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_updates_ignore.json create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_delay_updates_ignore.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_dictionary.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_distribution2_2/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_distribution2_2/install.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_distribution2_2/subdir/dummy.txt create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_distribution2_2/subdir/subdir2/dummy2.txt create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_gfxBlacklist.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_gfxBlacklist2.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_gfxBlacklist_AllOS.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_gfxBlacklist_OSVersion.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_hotfix_1.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_hotfix_2.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_hotfix_3.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_install.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_install.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_migrate.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_migrate4.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_no_update.json create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_overrideblocklist/ancient.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_overrideblocklist/new.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_overrideblocklist/old.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_pluginBlocklistCtp.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_pluginBlocklistCtpUndo.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_proxy/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_softblocked1.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_sourceURI.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_temporary/bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_update.json create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_update.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_update.xml create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_update_multi.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_updatecheck.json create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_updatecheck.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_updatecompatmode_ignore.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_updatecompatmode_normal.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_updatecompatmode_strict.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/test_updateid.rdf create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/unsigned.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/data/webext-implicit-id.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/head_addons.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/head_unpack.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_AddonRepository.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_AddonRepository_cache.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_AddonRepository_compatmode.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_ChromeManifestParser.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_DeferredSave.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_LightweightThemeManager.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_ProductAddonChecker.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_XPIStates.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_XPIcancel.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_addon_path_service.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_asyncBlocklistLoad.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_backgroundupdate.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bad_json.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_badschema.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_blocklist_gfx.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_blocklist_metadata_filters.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_blocklist_prefs.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_blocklist_regexp.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_blocklistchange.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bootstrap.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bootstrap_const.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bootstrap_globals.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bootstrap_resource.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug1180901.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug1180901_2.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug299716.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug299716_2.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug324121.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug335238.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug371495.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug384052.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug393285.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug394300.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug397778.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug406118.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug424262.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug425657.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug430120.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug449027.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug455906.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug465190.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug468528.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug470377_1.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug470377_1_strictcompat.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug470377_2.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug470377_3.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug470377_3_strictcompat.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug470377_4.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug514327_1.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug514327_2.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug514327_3.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug521905.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug526598.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug541420.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug542391.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug554133.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug559800.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug563256.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug564030.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug566626.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug567184.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug569138.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug570173.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug576735.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug587088.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug594058.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug595081.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug595573.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug596607.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug616841.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug619730.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug620837.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug655254.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug659772.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug675371.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug740612.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug753900.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug757663.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_bug953156.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_cache_certdb.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_cacheflush.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_checkCompatibility_themeOverride.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_checkcompatibility.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_childprocess.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_compatoverrides.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_corrupt.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_corrupt_strictcompat.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_corruptfile.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_dataDirectory.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_default_providers_pref.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_delay_update.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_delay_update_webextension.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_dependencies.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_dictionary.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_disable.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_distribution.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_dss.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_duplicateplugins.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_e10s_restartless.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_error.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_experiment.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_ext_management.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_filepointer.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_fuel.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_general.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_getresource.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_Device.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_DriverNew.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_Equal_DriverNew.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_Equal_DriverOld.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_Equal_OK.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_GTE_DriverOld.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_GTE_OK.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_No_Comparison.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_OK.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_OS.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_OSVersion_match.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_Vendor.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_Version.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gfxBlacklist_prefs.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_gmpProvider.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_hasbinarycomponents.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_hotfix.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_hotfix_cert.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_install.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_install_from_sources.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_install_icons.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_install_strictcompat.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_isDebuggable.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_isReady.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_json_updatecheck.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_langpack.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_locale.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_locked.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_locked2.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_locked_strictcompat.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_manifest.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_mapURIToAddonID.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_metadata_update.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_migrate1.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_migrate2.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_migrate3.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_migrate4.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_migrate5.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_migrateAddonRepository.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_migrate_max_version.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_multiprocessCompatible.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_no_addons.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_nodisable_hidden.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_onPropertyChanged_appDisabled.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_overrideblocklist.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_pass_symbol.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_permissions.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_permissions_prefs.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_pluginBlocklistCtp.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_pluginInfoURL.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_pluginchange.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_plugins.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_pref_properties.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_provider_markSafe.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_provider_shutdown.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_provider_unsafe_access_shutdown.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_provider_unsafe_access_startup.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_proxies.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_proxy.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_registry.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_reload.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_safemode.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_schema_change.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_seen.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_seen_newprofile.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_shutdown.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_signed_inject.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_signed_install.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_signed_long.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_signed_migrate.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_signed_multi.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_signed_updatepref.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_signed_verify.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_softblocked.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_sourceURI.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_startup.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_strictcompatibility.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_switch_os.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_syncGUID.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_system_delay_update.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_system_reset.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_system_update.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_targetPlatforms.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_temporary.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_theme.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_types.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_undothemeuninstall.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_undouninstall.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_uninstall.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_update.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_updateCancel.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_update_compatmode.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_update_ignorecompat.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_update_strictcompat.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_update_webextensions.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_updatecheck.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_updateid.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_upgrade.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_upgrade_strictcompat.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_webextension.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_webextension_embedded.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_webextension_icons.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_webextension_install.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/test_webextension_paths.js create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/xpcshell-shared.ini create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/xpcshell-unpack.ini create mode 100644 toolkit/mozapps/webextensions/test/xpcshell/xpcshell.ini create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/.eslintrc.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/amosigned.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/amosigned2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/authRedirect.sjs create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser.ini create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_amosigned_trigger.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_amosigned_trigger_iframe.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_amosigned_url.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_auth.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_auth2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_auth3.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_auth4.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_badargs.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_badargs2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_badhash.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_badhashtype.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_bug540558.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_bug611242.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_bug638292.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_bug645699.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_bug672485.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_cancel.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_concurrent_installs.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_cookies.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_cookies2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_cookies3.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_cookies4.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_corrupt.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_datauri.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_empty.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_enabled.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_enabled2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_enabled3.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_hash.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_hash2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_httphash.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_httphash2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_httphash3.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_httphash4.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_httphash5.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_httphash6.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_installchrome.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_localfile.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_localfile2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_localfile3.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_localfile4.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_multipackage.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_navigateaway.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_navigateaway2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_navigateaway3.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_navigateaway4.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_offline.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_relative.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_signed_multipackage.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_signed_multiple.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_signed_naming.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_signed_tampered.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_signed_trigger.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_signed_untrusted.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_signed_url.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_softwareupdate.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_switchtab.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_trigger_redirect.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_unsigned_trigger.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_unsigned_trigger_iframe.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_unsigned_trigger_xorigin.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_unsigned_url.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_whitelist.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_whitelist2.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_whitelist3.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_whitelist4.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_whitelist5.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_whitelist6.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/browser_whitelist7.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/bug540558.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/bug638292.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/bug645699.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/concurrent_installs.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/cookieRedirect.sjs create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/corrupt.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/empty.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/enabled.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/hashRedirect.sjs create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/head.js create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/incompatible.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/installchrome.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/installtrigger.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/installtrigger_frame.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/multipackage.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/navigate.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/redirect.sjs create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/restartless-unsigned.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/restartless.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/signed-multipackage.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/signed-no-cn.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/signed-no-o.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/signed-tampered.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/signed-untrusted.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/signed.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/signed2.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/slowinstall.sjs create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/startsoftwareupdate.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/theme.xpi create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/triggerredirect.html create mode 100644 toolkit/mozapps/webextensions/test/xpinstall/unsigned.xpi delete mode 100644 toolkit/themes/linux/mozapps/extensions/category-available.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/category-discover.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/category-plugins.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/category-recent.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/category-search.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/category-service.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/dictionaryGeneric-16.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/dictionaryGeneric.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/extensionGeneric-16.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/extensions.css delete mode 100644 toolkit/themes/linux/mozapps/extensions/heart.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/localeGeneric.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/newaddon.css delete mode 100644 toolkit/themes/linux/mozapps/extensions/themeGeneric-16.png delete mode 100644 toolkit/themes/linux/mozapps/extensions/themeGeneric.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/category-available.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/category-discover.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/category-plugins.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/category-recent.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/category-search.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/category-service.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/dictionaryGeneric-16.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/dictionaryGeneric.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/extensionGeneric-16.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/extensions.css create mode 100644 toolkit/themes/linux/mozapps/webextensions/heart.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/localeGeneric.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/newaddon.css create mode 100644 toolkit/themes/linux/mozapps/webextensions/themeGeneric-16.png create mode 100644 toolkit/themes/linux/mozapps/webextensions/themeGeneric.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/about.css delete mode 100644 toolkit/themes/osx/mozapps/extensions/blocklist.css delete mode 100644 toolkit/themes/osx/mozapps/extensions/cancel.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/category-available.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/category-dictionaries.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/category-discover.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/category-experiments.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/category-plugins.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/category-recent.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/category-search.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/category-service.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/discover-logo.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/eula.css delete mode 100644 toolkit/themes/osx/mozapps/extensions/experimentGeneric.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/extensions.css delete mode 100644 toolkit/themes/osx/mozapps/extensions/heart.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/localeGeneric.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/newaddon.css delete mode 100644 toolkit/themes/osx/mozapps/extensions/rating-not-won.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/rating-won.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/search.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/themeGeneric.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.png delete mode 100644 toolkit/themes/osx/mozapps/extensions/update.css delete mode 100644 toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css create mode 100644 toolkit/themes/osx/mozapps/webextensions/about.css create mode 100644 toolkit/themes/osx/mozapps/webextensions/blocklist.css create mode 100644 toolkit/themes/osx/mozapps/webextensions/cancel.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/category-available.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/category-dictionaries.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/category-discover.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/category-experiments.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/category-plugins.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/category-recent.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/category-search.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/category-service.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/dictionaryGeneric-16.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/dictionaryGeneric.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/discover-logo.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/eula.css create mode 100644 toolkit/themes/osx/mozapps/webextensions/experimentGeneric.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/extensionGeneric-16.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/extensions.css create mode 100644 toolkit/themes/osx/mozapps/webextensions/heart.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/localeGeneric.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/newaddon.css create mode 100644 toolkit/themes/osx/mozapps/webextensions/rating-not-won.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/rating-won.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/search.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/themeGeneric-16.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/themeGeneric.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/toolbarbutton-dropmarker.png create mode 100644 toolkit/themes/osx/mozapps/webextensions/update.css create mode 100644 toolkit/themes/osx/mozapps/webextensions/xpinstallConfirm.css delete mode 100644 toolkit/themes/shared/extensions/alerticon-error.svg delete mode 100644 toolkit/themes/shared/extensions/alerticon-info-negative.svg delete mode 100644 toolkit/themes/shared/extensions/alerticon-info-positive.svg delete mode 100644 toolkit/themes/shared/extensions/alerticon-warning.svg delete mode 100644 toolkit/themes/shared/extensions/extensionGeneric.svg delete mode 100644 toolkit/themes/shared/extensions/extensions.inc.css delete mode 100644 toolkit/themes/shared/extensions/navigation.png delete mode 100644 toolkit/themes/shared/extensions/newaddon.inc.css delete mode 100644 toolkit/themes/shared/extensions/utilities.svg create mode 100644 toolkit/themes/shared/webextensions/alerticon-error.svg create mode 100644 toolkit/themes/shared/webextensions/alerticon-info-negative.svg create mode 100644 toolkit/themes/shared/webextensions/alerticon-info-positive.svg create mode 100644 toolkit/themes/shared/webextensions/alerticon-warning.svg create mode 100644 toolkit/themes/shared/webextensions/extensionGeneric.svg create mode 100644 toolkit/themes/shared/webextensions/extensions.inc.css create mode 100644 toolkit/themes/shared/webextensions/navigation.png create mode 100644 toolkit/themes/shared/webextensions/newaddon.inc.css create mode 100644 toolkit/themes/shared/webextensions/utilities.svg delete mode 100644 toolkit/themes/windows/mozapps/extensions/about.css delete mode 100644 toolkit/themes/windows/mozapps/extensions/blocklist.css delete mode 100644 toolkit/themes/windows/mozapps/extensions/cancel.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-available-XP.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-available.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-discover-XP.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-discover.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-plugins-XP.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-plugins.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-recent-XP.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-recent.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-search.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/category-service.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/dictionaryGeneric-16.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/dictionaryGeneric.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/discover-logo.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/eula.css delete mode 100644 toolkit/themes/windows/mozapps/extensions/experimentGeneric.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/extensionGeneric-16-XP.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/extensionGeneric-16.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/extensions.css delete mode 100644 toolkit/themes/windows/mozapps/extensions/heart.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/localeGeneric-XP.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/localeGeneric.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/newaddon.css delete mode 100644 toolkit/themes/windows/mozapps/extensions/rating-not-won.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/rating-won.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/themeGeneric-16-XP.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/themeGeneric-16.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/themeGeneric-XP.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/themeGeneric.png delete mode 100644 toolkit/themes/windows/mozapps/extensions/update.css delete mode 100644 toolkit/themes/windows/mozapps/extensions/xpinstallConfirm.css create mode 100644 toolkit/themes/windows/mozapps/webextensions/about.css create mode 100644 toolkit/themes/windows/mozapps/webextensions/blocklist.css create mode 100644 toolkit/themes/windows/mozapps/webextensions/cancel.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-available-XP.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-available.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-discover-XP.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-discover.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-plugins-XP.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-plugins.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-recent-XP.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-recent.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-search.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/category-service.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/dictionaryGeneric-16.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/dictionaryGeneric.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/discover-logo.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/eula.css create mode 100644 toolkit/themes/windows/mozapps/webextensions/experimentGeneric.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/extensionGeneric-16-XP.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/extensionGeneric-16.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/extensions.css create mode 100644 toolkit/themes/windows/mozapps/webextensions/heart.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/localeGeneric-XP.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/localeGeneric.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/newaddon.css create mode 100644 toolkit/themes/windows/mozapps/webextensions/rating-not-won.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/rating-won.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/themeGeneric-16-XP.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/themeGeneric-16.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/themeGeneric-XP.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/themeGeneric.png create mode 100644 toolkit/themes/windows/mozapps/webextensions/update.css create mode 100644 toolkit/themes/windows/mozapps/webextensions/xpinstallConfirm.css diff --git a/browser/experiments/test/xpcshell/xpcshell.ini b/browser/experiments/test/xpcshell/xpcshell.ini index 5ea30976c..5921c9c47 100644 --- a/browser/experiments/test/xpcshell/xpcshell.ini +++ b/browser/experiments/test/xpcshell/xpcshell.ini @@ -10,7 +10,7 @@ support-files = experiment-1a.xpi experiment-2.xpi experiment-racybranch.xpi - !/toolkit/mozapps/extensions/test/xpcshell/head_addons.js + !/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js generated-files = experiment-1.xpi experiment-1a.xpi diff --git a/services/sync/tests/unit/xpcshell.ini b/services/sync/tests/unit/xpcshell.ini index e5b32e7b1..5a90652b8 100644 --- a/services/sync/tests/unit/xpcshell.ini +++ b/services/sync/tests/unit/xpcshell.ini @@ -13,8 +13,8 @@ support-files = sync_ping_schema.json systemaddon-search.xml !/services/common/tests/unit/head_helpers.js - !/toolkit/mozapps/extensions/test/xpcshell/head_addons.js - !/toolkit/components/extensions/test/xpcshell/head_sync.js + !/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js + !/toolkit/components/webextensions/test/xpcshell/head_sync.js # The manifest is roughly ordered from low-level to high-level. When making # systemic sweeping changes, this makes it easier to identify errors closer to diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini index 74067580a..fe58d362e 100644 --- a/toolkit/components/telemetry/tests/unit/xpcshell.ini +++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini @@ -15,7 +15,7 @@ support-files = system.xpi restartless.xpi theme.xpi - !/toolkit/mozapps/extensions/test/xpcshell/head_addons.js + !/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js generated-files = dictionary.xpi experiment.xpi diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/about.dtd b/toolkit/locales/en-US/chrome/mozapps/extensions/about.dtd deleted file mode 100644 index 4f9098966..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/about.dtd +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd b/toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd deleted file mode 100644 index f393cc906..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd deleted file mode 100644 index 99c940b33..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties deleted file mode 100644 index 10c0f81c3..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties +++ /dev/null @@ -1,184 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#LOCALIZATION NOTE (aboutWindowTitle) %S is the addon name -aboutWindowTitle=About %S -aboutWindowCloseButton=Close -#LOCALIZATION NOTE (aboutWindowVersionString) %S is the addon version -aboutWindowVersionString=version %S -#LOCALIZATION NOTE (aboutAddon) %S is the addon name -aboutAddon=About %S - -#LOCALIZATION NOTE (uninstallNotice) %S is the add-on name -uninstallNotice=%S has been removed. - -#LOCALIZATION NOTE (numReviews): Semicolon-separated list of plural forms. -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -# #1 is the number of reviews -numReviews=#1 review;#1 reviews - -#LOCALIZATION NOTE (dateUpdated) %S is the date the addon was last updated -dateUpdated=Updated %S - -#LOCALIZATION NOTE (notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version -notification.incompatible=%1$S is incompatible with %2$S %3$S. -#LOCALIZATION NOTE (notification.unsigned, notification.unsignedAndDisabled) %1$S is the add-on name, %2$S is brand name -notification.unsignedAndDisabled=%1$S could not be verified for use in %2$S and has been disabled. -notification.unsigned=%1$S could not be verified for use in %2$S. Proceed with caution. -notification.unsigned.link=More Information -#LOCALIZATION NOTE (notification.blocked) %1$S is the add-on name -notification.blocked=%1$S has been disabled due to security or stability issues. -notification.blocked.link=More Information -#LOCALIZATION NOTE (notification.softblocked) %1$S is the add-on name -notification.softblocked=%1$S is known to cause security or stability issues. -notification.softblocked.link=More Information -#LOCALIZATION NOTE (notification.outdated) %1$S is the add-on name -notification.outdated=An important update is available for %1$S. -notification.outdated.link=Update Now -#LOCALIZATION NOTE (notification.vulnerableUpdatable) %1$S is the add-on name -notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated. -notification.vulnerableUpdatable.link=Update Now -#LOCALIZATION NOTE (notification.vulnerableNoUpdate) %1$S is the add-on name -notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution. -notification.vulnerableNoUpdate.link=More Information -#LOCALIZATION NOTE (notification.enable) %1$S is the add-on name, %2$S is brand name -notification.enable=%1$S will be enabled after you restart %2$S. -#LOCALIZATION NOTE (notification.disable) %1$S is the add-on name, %2$S is brand name -notification.disable=%1$S will be disabled after you restart %2$S. -#LOCALIZATION NOTE (notification.install) %1$S is the add-on name, %2$S is brand name -notification.install=%1$S will be installed after you restart %2$S. -#LOCALIZATION NOTE (notification.uninstall) %1$S is the add-on name, %2$S is brand name -notification.uninstall=%1$S will be uninstalled after you restart %2$S. -#LOCALIZATION NOTE (notification.upgrade) %1$S is the add-on name, %2$S is brand name -notification.upgrade=%1$S will be updated after you restart %2$S. -#LOCALIZATION NOTE (notification.downloadError) %1$S is the add-on name. -notification.downloadError=There was an error downloading %1$S. -notification.downloadError.retry=Try again -notification.downloadError.retry.tooltip=Try downloading this add-on again -#LOCALIZATION NOTE (notification.installError) %1$S is the add-on name. -notification.installError=There was an error installing %1$S. -notification.installError.retry=Try again -notification.installError.retry.tooltip=Try downloading and installing this add-on again -#LOCALIZATION NOTE (notification.gmpPending) %1$S is the add-on name. -notification.gmpPending=%1$S will be installed shortly. - -#LOCALIZATION NOTE (contributionAmount2) %S is the currency amount recommended for contributions -contributionAmount2=Suggested Contribution: %S - -installDownloading=Downloading -installDownloaded=Downloaded -installDownloadFailed=Error downloading -installVerifying=Verifying -installInstalling=Installing -installEnablePending=Restart to enable -installDisablePending=Restart to disable -installFailed=Error installing -installCancelled=Install cancelled - -#LOCALIZATION NOTE (details.notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version -details.notification.incompatible=%1$S is incompatible with %2$S %3$S. -#LOCALIZATION NOTE (details.notification.unsigned, details.notification.unsignedAndDisabled) %1$S is the add-on name, %2$S is brand name -details.notification.unsignedAndDisabled=%1$S could not be verified for use in %2$S and has been disabled. -details.notification.unsigned=%1$S could not be verified for use in %2$S. Proceed with caution. -details.notification.unsigned.link=More Information -#LOCALIZATION NOTE (details.notification.blocked) %1$S is the add-on name -details.notification.blocked=%1$S has been disabled due to security or stability issues. -details.notification.blocked.link=More Information -#LOCALIZATION NOTE (details.notification.softblocked) %1$S is the add-on name -details.notification.softblocked=%1$S is known to cause security or stability issues. -details.notification.softblocked.link=More Information -#LOCALIZATION NOTE (details.notification.outdated) %1$S is the add-on name -details.notification.outdated=An important update is available for %1$S. -details.notification.outdated.link=Update Now -#LOCALIZATION NOTE (details.notification.vulnerableUpdatable) %1$S is the add-on name -details.notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated. -details.notification.vulnerableUpdatable.link=Update Now -#LOCALIZATION NOTE (details.notification.vulnerableNoUpdate) %1$S is the add-on name -details.notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution. -details.notification.vulnerableNoUpdate.link=More Information -#LOCALIZATION NOTE (details.notification.enable) %1$S is the add-on name, %2$S is brand name -details.notification.enable=%1$S will be enabled after you restart %2$S. -#LOCALIZATION NOTE (details.notification.disable) %1$S is the add-on name, %2$S is brand name -details.notification.disable=%1$S will be disabled after you restart %2$S. -#LOCALIZATION NOTE (details.notification.install) %1$S is the add-on name, %2$S is brand name -details.notification.install=%1$S will be installed after you restart %2$S. -#LOCALIZATION NOTE (details.notification.uninstall) %1$S is the add-on name, %2$S is brand name -details.notification.uninstall=%1$S will be uninstalled after you restart %2$S. -#LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name -details.notification.upgrade=%1$S will be updated after you restart %2$S. -#LOCALIZATION NOTE (details.notification.gmpPending) %1$S is the add-on name -details.notification.gmpPending=%1$S will be installed shortly. - -# LOCALIZATION NOTE (details.experiment.time.daysRemaining): -# Semicolon-separated list of plural forms. -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -# #1 is the number of days from now that the experiment will remain active (detail view). -details.experiment.time.daysRemaining=#1 day remaining;#1 days remaining -#LOCALIZATION NOTE (details.experiment.time.endsToday) The experiment will end in less than a day (detail view). -details.experiment.time.endsToday=Less than a day remaining -# LOCALIZATION NOTE (details.experiment.time.daysPassed): -# Semicolon-separated list of plural forms. -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -# #1 is the number of days since the experiment ran (detail view). -details.experiment.time.daysPassed=#1 day ago;#1 days ago -#LOCALIZATION NOTE (details.experiment.time.endedToday) The experiment ended less than a day ago (detail view). -details.experiment.time.endedToday=Less than a day ago -#LOCALIZATION NOTE (details.experiment.state.active) This experiment is active (detail view). -details.experiment.state.active=Active -#LOCALIZATION NOTE (details.experiment.state.complete) This experiment is complete (it was previously active) (detail view). -details.experiment.state.complete=Complete - -# LOCALIZATION NOTE (experiment.time.daysRemaining): -# Semicolon-separated list of plural forms. -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -# #1 is the number of days from now that the experiment will remain active (list view item). -experiment.time.daysRemaining=#1 day remaining;#1 days remaining -#LOCALIZATION NOTE (experiment.time.endsToday) The experiment will end in less than a day (list view item). -experiment.time.endsToday=Less than a day remaining -# LOCALIZATION NOTE (experiment.time.daysPassed): -# Semicolon-separated list of plural forms. -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -# #1 is the number of days since the experiment ran (list view item). -experiment.time.daysPassed=#1 day ago;#1 days ago -#LOCALIZATION NOTE (experiment.time.endedToday) The experiment ended less than a day ago (list view item). -experiment.time.endedToday=Less than a day ago -#LOCALIZATION NOTE (experiment.state.active) This experiment is active (list view item). -experiment.state.active=Active -#LOCALIZATION NOTE (experiment.state.complete) This experiment is complete (it was previously active) (list view item). -experiment.state.complete=Complete - -installFromFile.dialogTitle=Select add-on to install -installFromFile.filterName=Add-ons - -uninstallAddonTooltip=Uninstall this add-on -uninstallAddonRestartRequiredTooltip=Uninstall this add-on (restart required) -enableAddonTooltip=Enable this add-on -enableAddonRestartRequiredTooltip=Enable this add-on (restart required) -disableAddonTooltip=Disable this add-on -disableAddonRestartRequiredTooltip=Disable this add-on (restart required) - -#LOCALIZATION NOTE (showAllSearchResults): Semicolon-separated list of plural forms. -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -# #1 is the total number of search results -showAllSearchResults=See one result;See all #1 results - -#LOCALIZATION NOTE (addon.purchase.label) displayed on a button in the list -# view, %S is the price of the add-on including currency symbol -addon.purchase.label=Purchase for %S… -addon.purchase.tooltip=Visit the add-ons gallery to purchase this add-on -#LOCALIZATION NOTE (cmd.purchaseAddon.label) displayed on a button in the detail -# view, %S is the price of the add-on including currency symbol -cmd.purchaseAddon.label=Purchase for %S… -cmd.purchaseAddon.accesskey=u - -#LOCALIZATION NOTE (eulaHeader) %S is name of the add-on asking the user to agree to the EULA -eulaHeader=%S requires that you accept the following End User License Agreement before installation can proceed: - -type.extension.name=Extensions -type.theme.name=Appearance -type.locale.name=Languages -type.plugin.name=Plugins -type.dictionary.name=Dictionaries -type.service.name=Services -type.experiment.name=Experiments diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/newaddon.dtd b/toolkit/locales/en-US/chrome/mozapps/extensions/newaddon.dtd deleted file mode 100644 index 1307cebb9..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/newaddon.dtd +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/newaddon.properties b/toolkit/locales/en-US/chrome/mozapps/extensions/newaddon.properties deleted file mode 100644 index bd5997a26..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/newaddon.properties +++ /dev/null @@ -1,10 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#LOCALIZATION NOTE (name) %1$S is the add-on name, %2$S is the add-on version -name=%1$S %2$S -#LOCALIZATION NOTE (author) %S is the author of the add-on -author=By %S -#LOCALIZATION NOTE (location) %S is the path the add-on is installed in -location=Location: %S diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/update.dtd b/toolkit/locales/en-US/chrome/mozapps/extensions/update.dtd deleted file mode 100644 index 6dd64a9c6..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/update.dtd +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/update.properties b/toolkit/locales/en-US/chrome/mozapps/extensions/update.properties deleted file mode 100644 index c2d4ed90f..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/update.properties +++ /dev/null @@ -1,21 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -mismatchCheckNow=Check Now -mismatchCheckNowAccesskey=C -mismatchDontCheck=Don’t Check -mismatchDontCheckAccesskey=D -installButtonText=Install Now -installButtonTextAccesskey=I -nextButtonText=Next > -nextButtonTextAccesskey=N -cancelButtonText=Cancel -cancelButtonTextAccesskey=C -statusPrefix=Finished checking %S -downloadingPrefix=Downloading: %S -installingPrefix=Installing: %S -closeButton=Close -installErrors=%S was unable to install updates for the following add-ons: -checkingErrors=%S was unable to check for updates for the following add-ons: -installErrorItemFormat=%S (%S) diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/xpinstallConfirm.dtd b/toolkit/locales/en-US/chrome/mozapps/extensions/xpinstallConfirm.dtd deleted file mode 100644 index 6a7d17a16..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/xpinstallConfirm.dtd +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/toolkit/locales/en-US/chrome/mozapps/extensions/xpinstallConfirm.properties b/toolkit/locales/en-US/chrome/mozapps/extensions/xpinstallConfirm.properties deleted file mode 100644 index d0b4e46ad..000000000 --- a/toolkit/locales/en-US/chrome/mozapps/extensions/xpinstallConfirm.properties +++ /dev/null @@ -1,11 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -unverified=(Author not verified) -signed=(%S) - -itemWarnIntroMultiple=You have asked to install the following %S items: -itemWarnIntroSingle=You have asked to install the following item: -installButtonDisabledLabel=Install (%S) -installButtonLabel=Install Now diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/about.dtd b/toolkit/locales/en-US/chrome/mozapps/webextensions/about.dtd new file mode 100644 index 000000000..4f9098966 --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/about.dtd @@ -0,0 +1,9 @@ + + + + + + + diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd b/toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd new file mode 100644 index 000000000..f393cc906 --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/blocklist.dtd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.dtd b/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.dtd new file mode 100644 index 000000000..99c940b33 --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.dtd @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.properties b/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.properties new file mode 100644 index 000000000..10c0f81c3 --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/extensions.properties @@ -0,0 +1,184 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#LOCALIZATION NOTE (aboutWindowTitle) %S is the addon name +aboutWindowTitle=About %S +aboutWindowCloseButton=Close +#LOCALIZATION NOTE (aboutWindowVersionString) %S is the addon version +aboutWindowVersionString=version %S +#LOCALIZATION NOTE (aboutAddon) %S is the addon name +aboutAddon=About %S + +#LOCALIZATION NOTE (uninstallNotice) %S is the add-on name +uninstallNotice=%S has been removed. + +#LOCALIZATION NOTE (numReviews): Semicolon-separated list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 is the number of reviews +numReviews=#1 review;#1 reviews + +#LOCALIZATION NOTE (dateUpdated) %S is the date the addon was last updated +dateUpdated=Updated %S + +#LOCALIZATION NOTE (notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version +notification.incompatible=%1$S is incompatible with %2$S %3$S. +#LOCALIZATION NOTE (notification.unsigned, notification.unsignedAndDisabled) %1$S is the add-on name, %2$S is brand name +notification.unsignedAndDisabled=%1$S could not be verified for use in %2$S and has been disabled. +notification.unsigned=%1$S could not be verified for use in %2$S. Proceed with caution. +notification.unsigned.link=More Information +#LOCALIZATION NOTE (notification.blocked) %1$S is the add-on name +notification.blocked=%1$S has been disabled due to security or stability issues. +notification.blocked.link=More Information +#LOCALIZATION NOTE (notification.softblocked) %1$S is the add-on name +notification.softblocked=%1$S is known to cause security or stability issues. +notification.softblocked.link=More Information +#LOCALIZATION NOTE (notification.outdated) %1$S is the add-on name +notification.outdated=An important update is available for %1$S. +notification.outdated.link=Update Now +#LOCALIZATION NOTE (notification.vulnerableUpdatable) %1$S is the add-on name +notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated. +notification.vulnerableUpdatable.link=Update Now +#LOCALIZATION NOTE (notification.vulnerableNoUpdate) %1$S is the add-on name +notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution. +notification.vulnerableNoUpdate.link=More Information +#LOCALIZATION NOTE (notification.enable) %1$S is the add-on name, %2$S is brand name +notification.enable=%1$S will be enabled after you restart %2$S. +#LOCALIZATION NOTE (notification.disable) %1$S is the add-on name, %2$S is brand name +notification.disable=%1$S will be disabled after you restart %2$S. +#LOCALIZATION NOTE (notification.install) %1$S is the add-on name, %2$S is brand name +notification.install=%1$S will be installed after you restart %2$S. +#LOCALIZATION NOTE (notification.uninstall) %1$S is the add-on name, %2$S is brand name +notification.uninstall=%1$S will be uninstalled after you restart %2$S. +#LOCALIZATION NOTE (notification.upgrade) %1$S is the add-on name, %2$S is brand name +notification.upgrade=%1$S will be updated after you restart %2$S. +#LOCALIZATION NOTE (notification.downloadError) %1$S is the add-on name. +notification.downloadError=There was an error downloading %1$S. +notification.downloadError.retry=Try again +notification.downloadError.retry.tooltip=Try downloading this add-on again +#LOCALIZATION NOTE (notification.installError) %1$S is the add-on name. +notification.installError=There was an error installing %1$S. +notification.installError.retry=Try again +notification.installError.retry.tooltip=Try downloading and installing this add-on again +#LOCALIZATION NOTE (notification.gmpPending) %1$S is the add-on name. +notification.gmpPending=%1$S will be installed shortly. + +#LOCALIZATION NOTE (contributionAmount2) %S is the currency amount recommended for contributions +contributionAmount2=Suggested Contribution: %S + +installDownloading=Downloading +installDownloaded=Downloaded +installDownloadFailed=Error downloading +installVerifying=Verifying +installInstalling=Installing +installEnablePending=Restart to enable +installDisablePending=Restart to disable +installFailed=Error installing +installCancelled=Install cancelled + +#LOCALIZATION NOTE (details.notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version +details.notification.incompatible=%1$S is incompatible with %2$S %3$S. +#LOCALIZATION NOTE (details.notification.unsigned, details.notification.unsignedAndDisabled) %1$S is the add-on name, %2$S is brand name +details.notification.unsignedAndDisabled=%1$S could not be verified for use in %2$S and has been disabled. +details.notification.unsigned=%1$S could not be verified for use in %2$S. Proceed with caution. +details.notification.unsigned.link=More Information +#LOCALIZATION NOTE (details.notification.blocked) %1$S is the add-on name +details.notification.blocked=%1$S has been disabled due to security or stability issues. +details.notification.blocked.link=More Information +#LOCALIZATION NOTE (details.notification.softblocked) %1$S is the add-on name +details.notification.softblocked=%1$S is known to cause security or stability issues. +details.notification.softblocked.link=More Information +#LOCALIZATION NOTE (details.notification.outdated) %1$S is the add-on name +details.notification.outdated=An important update is available for %1$S. +details.notification.outdated.link=Update Now +#LOCALIZATION NOTE (details.notification.vulnerableUpdatable) %1$S is the add-on name +details.notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated. +details.notification.vulnerableUpdatable.link=Update Now +#LOCALIZATION NOTE (details.notification.vulnerableNoUpdate) %1$S is the add-on name +details.notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution. +details.notification.vulnerableNoUpdate.link=More Information +#LOCALIZATION NOTE (details.notification.enable) %1$S is the add-on name, %2$S is brand name +details.notification.enable=%1$S will be enabled after you restart %2$S. +#LOCALIZATION NOTE (details.notification.disable) %1$S is the add-on name, %2$S is brand name +details.notification.disable=%1$S will be disabled after you restart %2$S. +#LOCALIZATION NOTE (details.notification.install) %1$S is the add-on name, %2$S is brand name +details.notification.install=%1$S will be installed after you restart %2$S. +#LOCALIZATION NOTE (details.notification.uninstall) %1$S is the add-on name, %2$S is brand name +details.notification.uninstall=%1$S will be uninstalled after you restart %2$S. +#LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name +details.notification.upgrade=%1$S will be updated after you restart %2$S. +#LOCALIZATION NOTE (details.notification.gmpPending) %1$S is the add-on name +details.notification.gmpPending=%1$S will be installed shortly. + +# LOCALIZATION NOTE (details.experiment.time.daysRemaining): +# Semicolon-separated list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 is the number of days from now that the experiment will remain active (detail view). +details.experiment.time.daysRemaining=#1 day remaining;#1 days remaining +#LOCALIZATION NOTE (details.experiment.time.endsToday) The experiment will end in less than a day (detail view). +details.experiment.time.endsToday=Less than a day remaining +# LOCALIZATION NOTE (details.experiment.time.daysPassed): +# Semicolon-separated list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 is the number of days since the experiment ran (detail view). +details.experiment.time.daysPassed=#1 day ago;#1 days ago +#LOCALIZATION NOTE (details.experiment.time.endedToday) The experiment ended less than a day ago (detail view). +details.experiment.time.endedToday=Less than a day ago +#LOCALIZATION NOTE (details.experiment.state.active) This experiment is active (detail view). +details.experiment.state.active=Active +#LOCALIZATION NOTE (details.experiment.state.complete) This experiment is complete (it was previously active) (detail view). +details.experiment.state.complete=Complete + +# LOCALIZATION NOTE (experiment.time.daysRemaining): +# Semicolon-separated list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 is the number of days from now that the experiment will remain active (list view item). +experiment.time.daysRemaining=#1 day remaining;#1 days remaining +#LOCALIZATION NOTE (experiment.time.endsToday) The experiment will end in less than a day (list view item). +experiment.time.endsToday=Less than a day remaining +# LOCALIZATION NOTE (experiment.time.daysPassed): +# Semicolon-separated list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 is the number of days since the experiment ran (list view item). +experiment.time.daysPassed=#1 day ago;#1 days ago +#LOCALIZATION NOTE (experiment.time.endedToday) The experiment ended less than a day ago (list view item). +experiment.time.endedToday=Less than a day ago +#LOCALIZATION NOTE (experiment.state.active) This experiment is active (list view item). +experiment.state.active=Active +#LOCALIZATION NOTE (experiment.state.complete) This experiment is complete (it was previously active) (list view item). +experiment.state.complete=Complete + +installFromFile.dialogTitle=Select add-on to install +installFromFile.filterName=Add-ons + +uninstallAddonTooltip=Uninstall this add-on +uninstallAddonRestartRequiredTooltip=Uninstall this add-on (restart required) +enableAddonTooltip=Enable this add-on +enableAddonRestartRequiredTooltip=Enable this add-on (restart required) +disableAddonTooltip=Disable this add-on +disableAddonRestartRequiredTooltip=Disable this add-on (restart required) + +#LOCALIZATION NOTE (showAllSearchResults): Semicolon-separated list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 is the total number of search results +showAllSearchResults=See one result;See all #1 results + +#LOCALIZATION NOTE (addon.purchase.label) displayed on a button in the list +# view, %S is the price of the add-on including currency symbol +addon.purchase.label=Purchase for %S… +addon.purchase.tooltip=Visit the add-ons gallery to purchase this add-on +#LOCALIZATION NOTE (cmd.purchaseAddon.label) displayed on a button in the detail +# view, %S is the price of the add-on including currency symbol +cmd.purchaseAddon.label=Purchase for %S… +cmd.purchaseAddon.accesskey=u + +#LOCALIZATION NOTE (eulaHeader) %S is name of the add-on asking the user to agree to the EULA +eulaHeader=%S requires that you accept the following End User License Agreement before installation can proceed: + +type.extension.name=Extensions +type.theme.name=Appearance +type.locale.name=Languages +type.plugin.name=Plugins +type.dictionary.name=Dictionaries +type.service.name=Services +type.experiment.name=Experiments diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/newaddon.dtd b/toolkit/locales/en-US/chrome/mozapps/webextensions/newaddon.dtd new file mode 100644 index 000000000..1307cebb9 --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/newaddon.dtd @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/newaddon.properties b/toolkit/locales/en-US/chrome/mozapps/webextensions/newaddon.properties new file mode 100644 index 000000000..bd5997a26 --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/newaddon.properties @@ -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/. + +#LOCALIZATION NOTE (name) %1$S is the add-on name, %2$S is the add-on version +name=%1$S %2$S +#LOCALIZATION NOTE (author) %S is the author of the add-on +author=By %S +#LOCALIZATION NOTE (location) %S is the path the add-on is installed in +location=Location: %S diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/update.dtd b/toolkit/locales/en-US/chrome/mozapps/webextensions/update.dtd new file mode 100644 index 000000000..6dd64a9c6 --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/update.dtd @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/update.properties b/toolkit/locales/en-US/chrome/mozapps/webextensions/update.properties new file mode 100644 index 000000000..c2d4ed90f --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/update.properties @@ -0,0 +1,21 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +mismatchCheckNow=Check Now +mismatchCheckNowAccesskey=C +mismatchDontCheck=Don’t Check +mismatchDontCheckAccesskey=D +installButtonText=Install Now +installButtonTextAccesskey=I +nextButtonText=Next > +nextButtonTextAccesskey=N +cancelButtonText=Cancel +cancelButtonTextAccesskey=C +statusPrefix=Finished checking %S +downloadingPrefix=Downloading: %S +installingPrefix=Installing: %S +closeButton=Close +installErrors=%S was unable to install updates for the following add-ons: +checkingErrors=%S was unable to check for updates for the following add-ons: +installErrorItemFormat=%S (%S) diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/xpinstallConfirm.dtd b/toolkit/locales/en-US/chrome/mozapps/webextensions/xpinstallConfirm.dtd new file mode 100644 index 000000000..6a7d17a16 --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/xpinstallConfirm.dtd @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/toolkit/locales/en-US/chrome/mozapps/webextensions/xpinstallConfirm.properties b/toolkit/locales/en-US/chrome/mozapps/webextensions/xpinstallConfirm.properties new file mode 100644 index 000000000..d0b4e46ad --- /dev/null +++ b/toolkit/locales/en-US/chrome/mozapps/webextensions/xpinstallConfirm.properties @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +unverified=(Author not verified) +signed=(%S) + +itemWarnIntroMultiple=You have asked to install the following %S items: +itemWarnIntroSingle=You have asked to install the following item: +installButtonDisabledLabel=Install (%S) +installButtonLabel=Install Now diff --git a/toolkit/locales/jar.mn b/toolkit/locales/jar.mn index c28cf6f49..e42f0de00 100644 --- a/toolkit/locales/jar.mn +++ b/toolkit/locales/jar.mn @@ -105,15 +105,17 @@ locale/@AB_CD@/mozapps/downloads/settingsChange.dtd (%chrome/mozapps/downloads/settingsChange.dtd) locale/@AB_CD@/mozapps/downloads/downloads.dtd (%chrome/mozapps/downloads/downloads.dtd) locale/@AB_CD@/mozapps/downloads/downloads.properties (%chrome/mozapps/downloads/downloads.properties) - locale/@AB_CD@/mozapps/extensions/extensions.dtd (%chrome/mozapps/extensions/extensions.dtd) +#ifdef MOZ_WEBEXTENSIONS + locale/@AB_CD@/mozapps/extensions/extensions.dtd (%chrome/mozapps/webextensions/extensions.dtd) #ifndef MOZ_FENNEC - locale/@AB_CD@/mozapps/extensions/extensions.properties (%chrome/mozapps/extensions/extensions.properties) - locale/@AB_CD@/mozapps/extensions/blocklist.dtd (%chrome/mozapps/extensions/blocklist.dtd) - locale/@AB_CD@/mozapps/extensions/about.dtd (%chrome/mozapps/extensions/about.dtd) - locale/@AB_CD@/mozapps/extensions/update.dtd (%chrome/mozapps/extensions/update.dtd) - locale/@AB_CD@/mozapps/extensions/update.properties (%chrome/mozapps/extensions/update.properties) - locale/@AB_CD@/mozapps/extensions/newaddon.dtd (%chrome/mozapps/extensions/newaddon.dtd) - locale/@AB_CD@/mozapps/extensions/newaddon.properties (%chrome/mozapps/extensions/newaddon.properties) + locale/@AB_CD@/mozapps/extensions/extensions.properties (%chrome/mozapps/webextensions/extensions.properties) + locale/@AB_CD@/mozapps/extensions/blocklist.dtd (%chrome/mozapps/webextensions/blocklist.dtd) + locale/@AB_CD@/mozapps/extensions/about.dtd (%chrome/mozapps/webextensions/about.dtd) + locale/@AB_CD@/mozapps/extensions/update.dtd (%chrome/mozapps/webextensions/update.dtd) + locale/@AB_CD@/mozapps/extensions/update.properties (%chrome/mozapps/webextensions/update.properties) + locale/@AB_CD@/mozapps/extensions/newaddon.dtd (%chrome/mozapps/webextensions/newaddon.dtd) + locale/@AB_CD@/mozapps/extensions/newaddon.properties (%chrome/mozapps/webextensions/newaddon.properties) +#endif #endif locale/@AB_CD@/mozapps/handling/handling.dtd (%chrome/mozapps/handling/handling.dtd) locale/@AB_CD@/mozapps/handling/handling.properties (%chrome/mozapps/handling/handling.properties) @@ -128,8 +130,10 @@ locale/@AB_CD@/mozapps/update/updates.properties (%chrome/mozapps/update/updates.properties) #endif locale/@AB_CD@/mozapps/update/history.dtd (%chrome/mozapps/update/history.dtd) - locale/@AB_CD@/mozapps/xpinstall/xpinstallConfirm.dtd (%chrome/mozapps/extensions/xpinstallConfirm.dtd) - locale/@AB_CD@/mozapps/xpinstall/xpinstallConfirm.properties (%chrome/mozapps/extensions/xpinstallConfirm.properties) +#ifdef MOZ_WEBEXTENSIONS + locale/@AB_CD@/mozapps/xpinstall/xpinstallConfirm.dtd (%chrome/mozapps/webextensions/xpinstallConfirm.dtd) + locale/@AB_CD@/mozapps/xpinstall/xpinstallConfirm.properties (%chrome/mozapps/webextensions/xpinstallConfirm.properties) +#endif % locale pluginproblem @AB_CD@ %locale/@AB_CD@/pluginproblem/ locale/@AB_CD@/pluginproblem/pluginproblem.dtd (%chrome/pluginproblem/pluginproblem.dtd) % locale alerts @AB_CD@ %locale/@AB_CD@/alerts/ diff --git a/toolkit/moz.build b/toolkit/moz.build index f3159137c..9501938ae 100644 --- a/toolkit/moz.build +++ b/toolkit/moz.build @@ -13,7 +13,6 @@ DIRS += [ 'locales', 'modules', 'mozapps/downloads', - 'mozapps/extensions', 'mozapps/handling', 'mozapps/preferences', 'obsolete', @@ -22,6 +21,9 @@ DIRS += [ 'themes', ] +if CONFIG['MOZ_WEBEXTENSIONS']: + DIRS += ['mozapps/webextensions'] + if CONFIG['MOZ_UPDATER'] and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android': DIRS += ['mozapps/update'] diff --git a/toolkit/mozapps/extensions/.eslintrc.js b/toolkit/mozapps/extensions/.eslintrc.js deleted file mode 100644 index 2b90bd053..000000000 --- a/toolkit/mozapps/extensions/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; - -module.exports = { // eslint-disable-line no-undef - "rules": { - // No using undeclared variables - "no-undef": "error", - } -}; diff --git a/toolkit/mozapps/extensions/AddonContentPolicy.cpp b/toolkit/mozapps/extensions/AddonContentPolicy.cpp deleted file mode 100644 index 90e53b2ea..000000000 --- a/toolkit/mozapps/extensions/AddonContentPolicy.cpp +++ /dev/null @@ -1,478 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "AddonContentPolicy.h" - -#include "mozilla/dom/nsCSPUtils.h" -#include "nsCOMPtr.h" -#include "nsContentPolicyUtils.h" -#include "nsContentTypeParser.h" -#include "nsContentUtils.h" -#include "nsIConsoleService.h" -#include "nsIContentSecurityPolicy.h" -#include "nsIContent.h" -#include "nsIDocument.h" -#include "nsIEffectiveTLDService.h" -#include "nsIScriptError.h" -#include "nsIStringBundle.h" -#include "nsIUUIDGenerator.h" -#include "nsIURI.h" -#include "nsNetCID.h" -#include "nsNetUtil.h" - -using namespace mozilla; - -/* Enforces content policies for WebExtension scopes. Currently: - * - * - Prevents loading scripts with a non-default JavaScript version. - * - Checks custom content security policies for sufficiently stringent - * script-src and object-src directives. - */ - -#define VERSIONED_JS_BLOCKED_MESSAGE \ - u"Versioned JavaScript is a non-standard, deprecated extension, and is " \ - u"not supported in WebExtension code. For alternatives, please see: " \ - u"https://developer.mozilla.org/Add-ons/WebExtensions/Tips" - -AddonContentPolicy::AddonContentPolicy() -{ -} - -AddonContentPolicy::~AddonContentPolicy() -{ -} - -NS_IMPL_ISUPPORTS(AddonContentPolicy, nsIContentPolicy, nsIAddonContentPolicy) - -static nsresult -GetWindowIDFromContext(nsISupports* aContext, uint64_t *aResult) -{ - NS_ENSURE_TRUE(aContext, NS_ERROR_FAILURE); - - nsCOMPtr content = do_QueryInterface(aContext); - NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); - - nsCOMPtr document = content->OwnerDoc(); - NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); - - nsCOMPtr window = document->GetInnerWindow(); - NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); - - *aResult = window->WindowID(); - return NS_OK; -} - -static nsresult -LogMessage(const nsAString &aMessage, nsIURI* aSourceURI, const nsAString &aSourceSample, - nsISupports* aContext) -{ - nsCOMPtr error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); - NS_ENSURE_TRUE(error, NS_ERROR_OUT_OF_MEMORY); - - nsCString sourceName = aSourceURI->GetSpecOrDefault(); - - uint64_t windowID = 0; - GetWindowIDFromContext(aContext, &windowID); - - nsresult rv = - error->InitWithWindowID(aMessage, NS_ConvertUTF8toUTF16(sourceName), - aSourceSample, 0, 0, nsIScriptError::errorFlag, - "JavaScript", windowID); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr console = do_GetService(NS_CONSOLESERVICE_CONTRACTID); - NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY); - - console->LogMessage(error); - return NS_OK; -} - - -// Content policy enforcement: - -NS_IMETHODIMP -AddonContentPolicy::ShouldLoad(uint32_t aContentType, - nsIURI* aContentLocation, - nsIURI* aRequestOrigin, - nsISupports* aContext, - const nsACString& aMimeTypeGuess, - nsISupports* aExtra, - nsIPrincipal* aRequestPrincipal, - int16_t* aShouldLoad) -{ - MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), - "We should only see external content policy types here."); - - *aShouldLoad = nsIContentPolicy::ACCEPT; - - if (!aRequestOrigin) { - return NS_OK; - } - - // Only apply this policy to requests from documents loaded from - // moz-extension URLs, or to resources being loaded from moz-extension URLs. - bool equals; - if (!((NS_SUCCEEDED(aContentLocation->SchemeIs("moz-extension", &equals)) && equals) || - (NS_SUCCEEDED(aRequestOrigin->SchemeIs("moz-extension", &equals)) && equals))) { - return NS_OK; - } - - if (aContentType == nsIContentPolicy::TYPE_SCRIPT) { - NS_ConvertUTF8toUTF16 typeString(aMimeTypeGuess); - nsContentTypeParser mimeParser(typeString); - - // Reject attempts to load JavaScript scripts with a non-default version. - nsAutoString mimeType, version; - if (NS_SUCCEEDED(mimeParser.GetType(mimeType)) && - nsContentUtils::IsJavascriptMIMEType(mimeType) && - NS_SUCCEEDED(mimeParser.GetParameter("version", version))) { - *aShouldLoad = nsIContentPolicy::REJECT_REQUEST; - - LogMessage(NS_MULTILINE_LITERAL_STRING(VERSIONED_JS_BLOCKED_MESSAGE), - aRequestOrigin, typeString, aContext); - return NS_OK; - } - } - - return NS_OK; -} - -NS_IMETHODIMP -AddonContentPolicy::ShouldProcess(uint32_t aContentType, - nsIURI* aContentLocation, - nsIURI* aRequestOrigin, - nsISupports* aRequestingContext, - const nsACString& aMimeTypeGuess, - nsISupports* aExtra, - nsIPrincipal* aRequestPrincipal, - int16_t* aShouldProcess) -{ - MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), - "We should only see external content policy types here."); - - *aShouldProcess = nsIContentPolicy::ACCEPT; - return NS_OK; -} - - -// CSP Validation: - -static const char* allowedSchemes[] = { - "blob", - "filesystem", - nullptr -}; - -static const char* allowedHostSchemes[] = { - "https", - "moz-extension", - nullptr -}; - -/** - * Validates a CSP directive to ensure that it is sufficiently stringent. - * In particular, ensures that: - * - * - No remote sources are allowed other than from https: schemes - * - * - No remote sources specify host wildcards for generic domains - * (*.blogspot.com, *.com, *) - * - * - All remote sources and local extension sources specify a host - * - * - No scheme sources are allowed other than blob:, filesystem:, - * moz-extension:, and https: - * - * - No keyword sources are allowed other than 'none', 'self', 'unsafe-eval', - * and hash sources. - */ -class CSPValidator final : public nsCSPSrcVisitor { - public: - CSPValidator(nsAString& aURL, CSPDirective aDirective, bool aDirectiveRequired = true) : - mURL(aURL), - mDirective(CSP_CSPDirectiveToString(aDirective)), - mFoundSelf(false) - { - // Start with the default error message for a missing directive, since no - // visitors will be called if the directive isn't present. - if (aDirectiveRequired) { - FormatError("csp.error.missing-directive"); - } - } - - // Visitors - - bool visitSchemeSrc(const nsCSPSchemeSrc& src) override - { - nsAutoString scheme; - src.getScheme(scheme); - - if (SchemeInList(scheme, allowedHostSchemes)) { - FormatError("csp.error.missing-host", scheme); - return false; - } - if (!SchemeInList(scheme, allowedSchemes)) { - FormatError("csp.error.illegal-protocol", scheme); - return false; - } - return true; - }; - - bool visitHostSrc(const nsCSPHostSrc& src) override - { - nsAutoString scheme, host; - - src.getScheme(scheme); - src.getHost(host); - - if (scheme.LowerCaseEqualsLiteral("https")) { - if (!HostIsAllowed(host)) { - FormatError("csp.error.illegal-host-wildcard", scheme); - return false; - } - } else if (scheme.LowerCaseEqualsLiteral("moz-extension")) { - // The CSP parser silently converts 'self' keywords to the origin - // URL, so we need to reconstruct the URL to see if it was present. - if (!mFoundSelf) { - nsAutoString url(u"moz-extension://"); - url.Append(host); - - mFoundSelf = url.Equals(mURL); - } - - if (host.IsEmpty() || host.EqualsLiteral("*")) { - FormatError("csp.error.missing-host", scheme); - return false; - } - } else if (!SchemeInList(scheme, allowedSchemes)) { - FormatError("csp.error.illegal-protocol", scheme); - return false; - } - - return true; - }; - - bool visitKeywordSrc(const nsCSPKeywordSrc& src) override - { - switch (src.getKeyword()) { - case CSP_NONE: - case CSP_SELF: - case CSP_UNSAFE_EVAL: - return true; - - default: - NS_ConvertASCIItoUTF16 keyword(CSP_EnumToKeyword(src.getKeyword())); - - FormatError("csp.error.illegal-keyword", keyword); - return false; - } - }; - - bool visitNonceSrc(const nsCSPNonceSrc& src) override - { - FormatError("csp.error.illegal-keyword", NS_LITERAL_STRING("'nonce-*'")); - return false; - }; - - bool visitHashSrc(const nsCSPHashSrc& src) override - { - return true; - }; - - // Accessors - - inline nsAString& GetError() - { - return mError; - }; - - inline bool FoundSelf() - { - return mFoundSelf; - }; - - - // Formatters - - template - inline void FormatError(const char* aName, const T ...aParams) - { - const char16_t* params[] = { mDirective.get(), aParams.get()... }; - FormatErrorParams(aName, params, MOZ_ARRAY_LENGTH(params)); - }; - - private: - // Validators - - bool HostIsAllowed(nsAString& host) - { - if (host.First() == '*') { - if (host.EqualsLiteral("*") || host[1] != '.') { - return false; - } - - host.Cut(0, 2); - - nsCOMPtr tldService = - do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); - - if (!tldService) { - return false; - } - - NS_ConvertUTF16toUTF8 cHost(host); - nsAutoCString publicSuffix; - - nsresult rv = tldService->GetPublicSuffixFromHost(cHost, publicSuffix); - - return NS_SUCCEEDED(rv) && !cHost.Equals(publicSuffix); - } - - return true; - }; - - bool SchemeInList(nsAString& scheme, const char** schemes) - { - for (; *schemes; schemes++) { - if (scheme.LowerCaseEqualsASCII(*schemes)) { - return true; - } - } - return false; - }; - - - // Formatters - - already_AddRefed - GetStringBundle() - { - nsCOMPtr sbs = - mozilla::services::GetStringBundleService(); - NS_ENSURE_TRUE(sbs, nullptr); - - nsCOMPtr stringBundle; - sbs->CreateBundle("chrome://global/locale/extensions.properties", - getter_AddRefs(stringBundle)); - - return stringBundle.forget(); - }; - - void FormatErrorParams(const char* aName, const char16_t** aParams, int32_t aLength) - { - nsresult rv = NS_ERROR_FAILURE; - - nsCOMPtr stringBundle = GetStringBundle(); - - if (stringBundle) { - NS_ConvertASCIItoUTF16 name(aName); - - rv = stringBundle->FormatStringFromName(name.get(), aParams, aLength, - getter_Copies(mError)); - } - - if (NS_WARN_IF(NS_FAILED(rv))) { - mError.AssignLiteral("An unexpected error occurred"); - } - }; - - - // Data members - - nsAutoString mURL; - NS_ConvertASCIItoUTF16 mDirective; - nsXPIDLString mError; - - bool mFoundSelf; -}; - -/** - * Validates a custom content security policy string for use by an add-on. - * In particular, ensures that: - * - * - Both object-src and script-src directives are present, and meet - * the policies required by the CSPValidator class - * - * - The script-src directive includes the source 'self' - */ -NS_IMETHODIMP -AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString, - nsAString& aResult) -{ - nsresult rv; - - // Validate against a randomly-generated extension origin. - // There is no add-on-specific behavior in the CSP code, beyond the ability - // for add-ons to specify a custom policy, but the parser requires a valid - // origin in order to operate correctly. - nsAutoString url(u"moz-extension://"); - { - nsCOMPtr uuidgen = services::GetUUIDGenerator(); - NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE); - - nsID id; - rv = uuidgen->GenerateUUIDInPlace(&id); - NS_ENSURE_SUCCESS(rv, rv); - - char idString[NSID_LENGTH]; - id.ToProvidedString(idString); - - MOZ_RELEASE_ASSERT(idString[0] == '{' && idString[NSID_LENGTH - 2] == '}', - "UUID generator did not return a valid UUID"); - - url.AppendASCII(idString + 1, NSID_LENGTH - 3); - } - - - RefPtr principal = - BasePrincipal::CreateCodebasePrincipal(NS_ConvertUTF16toUTF8(url)); - - nsCOMPtr csp; - rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp)); - NS_ENSURE_SUCCESS(rv, rv); - - - csp->AppendPolicy(aPolicyString, false, false); - - const nsCSPPolicy* policy = csp->GetPolicy(0); - if (!policy) { - CSPValidator validator(url, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE); - aResult.Assign(validator.GetError()); - return NS_OK; - } - - bool haveValidDefaultSrc = false; - { - CSPDirective directive = nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; - CSPValidator validator(url, directive); - - haveValidDefaultSrc = policy->visitDirectiveSrcs(directive, &validator); - } - - aResult.SetIsVoid(true); - { - CSPDirective directive = nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE; - CSPValidator validator(url, directive, !haveValidDefaultSrc); - - if (!policy->visitDirectiveSrcs(directive, &validator)) { - aResult.Assign(validator.GetError()); - } else if (!validator.FoundSelf()) { - validator.FormatError("csp.error.missing-source", NS_LITERAL_STRING("'self'")); - aResult.Assign(validator.GetError()); - } - } - - if (aResult.IsVoid()) { - CSPDirective directive = nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE; - CSPValidator validator(url, directive, !haveValidDefaultSrc); - - if (!policy->visitDirectiveSrcs(directive, &validator)) { - aResult.Assign(validator.GetError()); - } - } - - return NS_OK; -} diff --git a/toolkit/mozapps/extensions/AddonContentPolicy.h b/toolkit/mozapps/extensions/AddonContentPolicy.h deleted file mode 100644 index 4c8af4828..000000000 --- a/toolkit/mozapps/extensions/AddonContentPolicy.h +++ /dev/null @@ -1,22 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsIContentPolicy.h" -#include "nsIAddonPolicyService.h" - -class AddonContentPolicy : public nsIContentPolicy, - public nsIAddonContentPolicy -{ -protected: - virtual ~AddonContentPolicy(); - -public: - AddonContentPolicy(); - - NS_DECL_ISUPPORTS - NS_DECL_NSICONTENTPOLICY - NS_DECL_NSIADDONCONTENTPOLICY -}; diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm deleted file mode 100644 index c5cb80091..000000000 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ /dev/null @@ -1,3674 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as -// most tests later register different nsIAppInfo implementations, which -// wouldn't be reflected in Services.appinfo anymore, as the lazy getter -// underlying it would have been initialized if we used it here. -if ("@mozilla.org/xre/app-info;1" in Cc) { - let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); - if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { - // Refuse to run in child processes. - throw new Error("You cannot use the AddonManager in child processes!"); - } -} - -Cu.import("resource://gre/modules/AppConstants.jsm"); - -const MOZ_COMPATIBILITY_NIGHTLY = !['aurora', 'beta', 'release', 'esr'].includes(AppConstants.MOZ_UPDATE_CHANNEL); - -const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; -const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled"; -const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled"; -const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; -const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion"; -const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; -const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; -const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; -const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url"; -const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; -const PREF_APP_UPDATE_AUTO = "app.update.auto"; -const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; -const PREF_EM_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion"; -const PREF_EM_HOTFIX_URL = "extensions.hotfix.url"; -const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; -const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; -const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; -const PREF_SELECTED_LOCALE = "general.useragent.locale"; -const UNKNOWN_XPCOM_ABI = "unknownABI"; - -const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; -const PREF_WEBAPI_TESTING = "extensions.webapi.testing"; - -const UPDATE_REQUEST_VERSION = 2; -const CATEGORY_UPDATE_PARAMS = "extension-update-params"; - -const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; - -const KEY_PROFILEDIR = "ProfD"; -const KEY_APPDIR = "XCurProcD"; -const FILE_BLOCKLIST = "blocklist.xml"; - -const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi; -const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility"; -var PREF_EM_CHECK_COMPATIBILITY = MOZ_COMPATIBILITY_NIGHTLY ? - PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly" : - undefined; - -const TOOLKIT_ID = "toolkit@mozilla.org"; - -const VALID_TYPES_REGEXP = /^[\w\-]+$/; - -const WEBAPI_INSTALL_HOSTS = ["addons.mozilla.org", "testpilot.firefox.com"]; -const WEBAPI_TEST_INSTALL_HOSTS = [ - "addons.allizom.org", "addons-dev.allizom.org", - "testpilot.stage.mozaws.net", "testpilot.dev.mozaws.net", - "example.com", -]; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/AsyncShutdown.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/Promise.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", - "resource://gre/modules/addons/AddonRepository.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Extension", - "resource://gre/modules/Extension.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); - -XPCOMUtils.defineLazyGetter(this, "CertUtils", function() { - let certUtils = {}; - Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); - return certUtils; -}); - -const INTEGER = /^[1-9]\d*$/; - -this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ]; - -const CATEGORY_PROVIDER_MODULE = "addon-provider-module"; - -// A list of providers to load by default -const DEFAULT_PROVIDERS = [ - "resource://gre/modules/addons/XPIProvider.jsm", - "resource://gre/modules/LightweightThemeManager.jsm" -]; - -Cu.import("resource://gre/modules/Log.jsm"); -// Configure a logger at the parent 'addons' level to format -// messages for all the modules under addons.* -const PARENT_LOGGER_ID = "addons"; -var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID); -parentLogger.level = Log.Level.Warn; -var formatter = new Log.BasicFormatter(); -// Set parent logger (and its children) to append to -// the Javascript section of the Browser Console -parentLogger.addAppender(new Log.ConsoleAppender(formatter)); -// Set parent logger (and its children) to -// also append to standard out -parentLogger.addAppender(new Log.DumpAppender(formatter)); - -// Create a new logger (child of 'addons' logger) -// for use by the Addons Manager -const LOGGER_ID = "addons.manager"; -var logger = Log.repository.getLogger(LOGGER_ID); - -// Provide the ability to enable/disable logging -// messages at runtime. -// If the "extensions.logging.enabled" preference is -// missing or 'false', messages at the WARNING and higher -// severity should be logged to the JS console and standard error. -// If "extensions.logging.enabled" is set to 'true', messages -// at DEBUG and higher should go to JS console and standard error. -const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; -const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; - -const UNNAMED_PROVIDER = ""; -function providerName(aProvider) { - return aProvider.name || UNNAMED_PROVIDER; -} - -/** - * Preference listener which listens for a change in the - * "extensions.logging.enabled" preference and changes the logging level of the - * parent 'addons' level logger accordingly. - */ -var PrefObserver = { - init: function() { - Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); - Services.obs.addObserver(this, "xpcom-shutdown", false); - this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); - }, - - observe: function(aSubject, aTopic, aData) { - if (aTopic == "xpcom-shutdown") { - Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); - Services.obs.removeObserver(this, "xpcom-shutdown"); - } - else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { - let debugLogEnabled = false; - try { - debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); - } - catch (e) { - } - if (debugLogEnabled) { - parentLogger.level = Log.Level.Debug; - } - else { - parentLogger.level = Log.Level.Warn; - } - } - } -}; - -PrefObserver.init(); - -/** - * Calls a callback method consuming any thrown exception. Any parameters after - * the callback parameter will be passed to the callback. - * - * @param aCallback - * The callback method to call - */ -function safeCall(aCallback, ...aArgs) { - try { - aCallback.apply(null, aArgs); - } - catch (e) { - logger.warn("Exception calling callback", e); - } -} - -/** - * Creates a function that will call the passed callback catching and logging - * any exceptions. - * - * @param aCallback - * The callback method to call - */ -function makeSafe(aCallback) { - return function(...aArgs) { - safeCall(aCallback, ...aArgs); - } -} - -/** - * Report an exception thrown by a provider API method. - */ -function reportProviderError(aProvider, aMethod, aError) { - let method = `provider ${providerName(aProvider)}.${aMethod}`; - AddonManagerPrivate.recordException("AMI", method, aError); - logger.error("Exception calling " + method, aError); -} - -/** - * Calls a method on a provider if it exists and consumes any thrown exception. - * Any parameters after the aDefault parameter are passed to the provider's method. - * - * @param aProvider - * The provider to call - * @param aMethod - * The method name to call - * @param aDefault - * A default return value if the provider does not implement the named - * method or throws an error. - * @return the return value from the provider, or aDefault if the provider does not - * implement method or throws an error - */ -function callProvider(aProvider, aMethod, aDefault, ...aArgs) { - if (!(aMethod in aProvider)) - return aDefault; - - try { - return aProvider[aMethod].apply(aProvider, aArgs); - } - catch (e) { - reportProviderError(aProvider, aMethod, e); - return aDefault; - } -} - -/** - * Calls a method on a provider if it exists and consumes any thrown exception. - * Parameters after aMethod are passed to aProvider.aMethod(). - * The last parameter must be a callback function. - * If the provider does not implement the method, or the method throws, calls - * the callback with 'undefined'. - * - * @param aProvider - * The provider to call - * @param aMethod - * The method name to call - */ -function callProviderAsync(aProvider, aMethod, ...aArgs) { - let callback = aArgs[aArgs.length - 1]; - if (!(aMethod in aProvider)) { - callback(undefined); - return undefined; - } - try { - return aProvider[aMethod].apply(aProvider, aArgs); - } - catch (e) { - reportProviderError(aProvider, aMethod, e); - callback(undefined); - return undefined; - } -} - -/** - * Calls a method on a provider if it exists and consumes any thrown exception. - * Parameters after aMethod are passed to aProvider.aMethod() and an additional - * callback is added for the provider to return a result to. - * - * @param aProvider - * The provider to call - * @param aMethod - * The method name to call - * @return {Promise} - * @resolves The result the provider returns, or |undefined| if the provider - * does not implement the method or the method throws. - * @rejects Never - */ -function promiseCallProvider(aProvider, aMethod, ...aArgs) { - return new Promise(resolve => { - callProviderAsync(aProvider, aMethod, ...aArgs, resolve); - }); -} - -/** - * Gets the currently selected locale for display. - * @return the selected locale or "en-US" if none is selected - */ -function getLocale() { - try { - if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE)) - return Services.locale.getLocaleComponentForUserAgent(); - } - catch (e) { } - - try { - let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, - Ci.nsIPrefLocalizedString); - if (locale) - return locale; - } - catch (e) { } - - try { - return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); - } - catch (e) { } - - return "en-US"; -} - -function webAPIForAddon(addon) { - if (!addon) { - return null; - } - - let result = {}; - - // By default just pass through any plain property, the webidl will - // control access. Also filter out private properties, regular Addon - // objects are okay but MockAddon used in tests has non-serializable - // private properties. - for (let prop in addon) { - if (prop[0] != "_" && typeof(addon[prop]) != "function") { - result[prop] = addon[prop]; - } - } - - // A few properties are computed for a nicer API - result.isEnabled = !addon.userDisabled; - result.canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL); - - return result; -} - -/** - * A helper class to repeatedly call a listener with each object in an array - * optionally checking whether the object has a method in it. - * - * @param aObjects - * The array of objects to iterate through - * @param aMethod - * An optional method name, if not null any objects without this method - * will not be passed to the listener - * @param aListener - * A listener implementing nextObject and noMoreObjects methods. The - * former will be called with the AsyncObjectCaller as the first - * parameter and the object as the second. noMoreObjects will be passed - * just the AsyncObjectCaller - */ -function AsyncObjectCaller(aObjects, aMethod, aListener) { - this.objects = [...aObjects]; - this.method = aMethod; - this.listener = aListener; - - this.callNext(); -} - -AsyncObjectCaller.prototype = { - objects: null, - method: null, - listener: null, - - /** - * Passes the next object to the listener or calls noMoreObjects if there - * are none left. - */ - callNext: function() { - if (this.objects.length == 0) { - this.listener.noMoreObjects(this); - return; - } - - let object = this.objects.shift(); - if (!this.method || this.method in object) - this.listener.nextObject(this, object); - else - this.callNext(); - } -}; - -/** - * Listens for a browser changing origin and cancels the installs that were - * started by it. - */ -function BrowserListener(aBrowser, aInstallingPrincipal, aInstalls) { - this.browser = aBrowser; - this.principal = aInstallingPrincipal; - this.installs = aInstalls; - this.installCount = aInstalls.length; - - aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); - Services.obs.addObserver(this, "message-manager-close", true); - - for (let install of this.installs) - install.addListener(this); - - this.registered = true; -} - -BrowserListener.prototype = { - browser: null, - installs: null, - installCount: null, - registered: false, - - unregister: function() { - if (!this.registered) - return; - this.registered = false; - - Services.obs.removeObserver(this, "message-manager-close"); - // The browser may have already been detached - if (this.browser.removeProgressListener) - this.browser.removeProgressListener(this); - - for (let install of this.installs) - install.removeListener(this); - this.installs = null; - }, - - cancelInstalls: function() { - for (let install of this.installs) { - try { - install.cancel(); - } - catch (e) { - // Some installs may have already failed or been cancelled, ignore these - } - } - }, - - observe: function(subject, topic, data) { - if (subject != this.browser.messageManager) - return; - - // The browser's message manager has closed and so the browser is - // going away, cancel all installs - this.cancelInstalls(); - }, - - onLocationChange: function(webProgress, request, location) { - if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal)) - return; - - // The browser has navigated to a new origin so cancel all installs - this.cancelInstalls(); - }, - - onDownloadCancelled: function(install) { - // Don't need to hear more events from this install - install.removeListener(this); - - // Once all installs have ended unregister everything - if (--this.installCount == 0) - this.unregister(); - }, - - onDownloadFailed: function(install) { - this.onDownloadCancelled(install); - }, - - onInstallFailed: function(install) { - this.onDownloadCancelled(install); - }, - - onInstallEnded: function(install) { - this.onDownloadCancelled(install); - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, - Ci.nsIWebProgressListener, - Ci.nsIObserver]) -}; - -/** - * This represents an author of an add-on (e.g. creator or developer) - * - * @param aName - * The name of the author - * @param aURL - * The URL of the author's profile page - */ -function AddonAuthor(aName, aURL) { - this.name = aName; - this.url = aURL; -} - -AddonAuthor.prototype = { - name: null, - url: null, - - // Returns the author's name, defaulting to the empty string - toString: function() { - return this.name || ""; - } -} - -/** - * This represents an screenshot for an add-on - * - * @param aURL - * The URL to the full version of the screenshot - * @param aWidth - * The width in pixels of the screenshot - * @param aHeight - * The height in pixels of the screenshot - * @param aThumbnailURL - * The URL to the thumbnail version of the screenshot - * @param aThumbnailWidth - * The width in pixels of the thumbnail version of the screenshot - * @param aThumbnailHeight - * The height in pixels of the thumbnail version of the screenshot - * @param aCaption - * The caption of the screenshot - */ -function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL, - aThumbnailWidth, aThumbnailHeight, aCaption) { - this.url = aURL; - if (aWidth) this.width = aWidth; - if (aHeight) this.height = aHeight; - if (aThumbnailURL) this.thumbnailURL = aThumbnailURL; - if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth; - if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight; - if (aCaption) this.caption = aCaption; -} - -AddonScreenshot.prototype = { - url: null, - width: null, - height: null, - thumbnailURL: null, - thumbnailWidth: null, - thumbnailHeight: null, - caption: null, - - // Returns the screenshot URL, defaulting to the empty string - toString: function() { - return this.url || ""; - } -} - - -/** - * This represents a compatibility override for an addon. - * - * @param aType - * Overrride type - "compatible" or "incompatible" - * @param aMinVersion - * Minimum version of the addon to match - * @param aMaxVersion - * Maximum version of the addon to match - * @param aAppID - * Application ID used to match appMinVersion and appMaxVersion - * @param aAppMinVersion - * Minimum version of the application to match - * @param aAppMaxVersion - * Maximum version of the application to match - */ -function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID, - aAppMinVersion, aAppMaxVersion) { - this.type = aType; - this.minVersion = aMinVersion; - this.maxVersion = aMaxVersion; - this.appID = aAppID; - this.appMinVersion = aAppMinVersion; - this.appMaxVersion = aAppMaxVersion; -} - -AddonCompatibilityOverride.prototype = { - /** - * Type of override - "incompatible" or "compatible". - * Only "incompatible" is supported for now. - */ - type: null, - - /** - * Min version of the addon to match. - */ - minVersion: null, - - /** - * Max version of the addon to match. - */ - maxVersion: null, - - /** - * Application ID to match. - */ - appID: null, - - /** - * Min version of the application to match. - */ - appMinVersion: null, - - /** - * Max version of the application to match. - */ - appMaxVersion: null -}; - - -/** - * A type of add-on, used by the UI to determine how to display different types - * of add-ons. - * - * @param aID - * The add-on type ID - * @param aLocaleURI - * The URI of a localized properties file to get the displayable name - * for the type from - * @param aLocaleKey - * The key for the string in the properties file or the actual display - * name if aLocaleURI is null. Include %ID% to include the type ID in - * the key - * @param aViewType - * The optional type of view to use in the UI - * @param aUIPriority - * The priority is used by the UI to list the types in order. Lower - * values push the type higher in the list. - * @param aFlags - * An option set of flags that customize the display of the add-on in - * the UI. - */ -function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) { - if (!aID) - throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG); - - if (aViewType && aUIPriority === undefined) - throw Components.Exception("An AddonType with a defined view must have a set UI priority", - Cr.NS_ERROR_INVALID_ARG); - - if (!aLocaleKey) - throw Components.Exception("An AddonType must have a displayable name", - Cr.NS_ERROR_INVALID_ARG); - - this.id = aID; - this.uiPriority = aUIPriority; - this.viewType = aViewType; - this.flags = aFlags; - - if (aLocaleURI) { - XPCOMUtils.defineLazyGetter(this, "name", () => { - let bundle = Services.strings.createBundle(aLocaleURI); - return bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID)); - }); - } - else { - this.name = aLocaleKey; - } -} - -var gStarted = false; -var gStartupComplete = false; -var gCheckCompatibility = true; -var gStrictCompatibility = true; -var gCheckUpdateSecurityDefault = true; -var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; -var gUpdateEnabled = true; -var gAutoUpdateDefault = true; -var gHotfixID = null; -var gWebExtensionsMinPlatformVersion = null; -var gShutdownBarrier = null; -var gRepoShutdownState = ""; -var gShutdownInProgress = false; -var gPluginPageListener = null; - -/** - * This is the real manager, kept here rather than in AddonManager to keep its - * contents hidden from API users. - */ -var AddonManagerInternal = { - managerListeners: [], - installListeners: [], - addonListeners: [], - typeListeners: [], - pendingProviders: new Set(), - providers: new Set(), - providerShutdowns: new Map(), - types: {}, - startupChanges: {}, - // Store telemetry details per addon provider - telemetryDetails: {}, - upgradeListeners: new Map(), - - recordTimestamp: function(name, value) { - this.TelemetryTimestamps.add(name, value); - }, - - validateBlocklist: function() { - let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); - - // If there is no application shipped blocklist then there is nothing to do - if (!appBlocklist.exists()) - return; - - let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); - - // If there is no blocklist in the profile then copy the application shipped - // one there - if (!profileBlocklist.exists()) { - try { - appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); - } - catch (e) { - logger.warn("Failed to copy the application shipped blocklist to the profile", e); - } - return; - } - - let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - try { - let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Ci.nsIConverterInputStream); - fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); - cstream.init(fileStream, "UTF-8", 0, 0); - - let data = ""; - let str = {}; - let read = 0; - do { - read = cstream.readString(0xffffffff, str); - data += str.value; - } while (read != 0); - - let parser = Cc["@mozilla.org/xmlextras/domparser;1"]. - createInstance(Ci.nsIDOMParser); - var doc = parser.parseFromString(data, "text/xml"); - } - catch (e) { - logger.warn("Application shipped blocklist could not be loaded", e); - return; - } - finally { - try { - fileStream.close(); - } - catch (e) { - logger.warn("Unable to close blocklist file stream", e); - } - } - - // If the namespace is incorrect then ignore the application shipped - // blocklist - if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { - logger.warn("Application shipped blocklist has an unexpected namespace (" + - doc.documentElement.namespaceURI + ")"); - return; - } - - // If there is no lastupdate information then ignore the application shipped - // blocklist - if (!doc.documentElement.hasAttribute("lastupdate")) - return; - - // If the application shipped blocklist is older than the profile blocklist - // then do nothing - if (doc.documentElement.getAttribute("lastupdate") <= - profileBlocklist.lastModifiedTime) - return; - - // Otherwise copy the application shipped blocklist to the profile - try { - appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); - } - catch (e) { - logger.warn("Failed to copy the application shipped blocklist to the profile", e); - } - }, - - /** - * Start up a provider, and register its shutdown hook if it has one - */ - _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - logger.debug(`Starting provider: ${providerName(aProvider)}`); - callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion); - if ('shutdown' in aProvider) { - let name = providerName(aProvider); - let AMProviderShutdown = () => { - // If the provider has been unregistered, it will have been removed from - // this.providers. If it hasn't been unregistered, then this is a normal - // shutdown - and we move it to this.pendingProviders incase we're - // running in a test that will start AddonManager again. - if (this.providers.has(aProvider)) { - this.providers.delete(aProvider); - this.pendingProviders.add(aProvider); - } - - return new Promise((resolve, reject) => { - logger.debug("Calling shutdown blocker for " + name); - resolve(aProvider.shutdown()); - }) - .catch(err => { - logger.warn("Failure during shutdown of " + name, err); - AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err); - }); - }; - logger.debug("Registering shutdown blocker for " + name); - this.providerShutdowns.set(aProvider, AMProviderShutdown); - AddonManager.shutdown.addBlocker(name, AMProviderShutdown); - } - - this.pendingProviders.delete(aProvider); - this.providers.add(aProvider); - logger.debug(`Provider finished startup: ${providerName(aProvider)}`); - }, - - _getProviderByName(aName) { - for (let provider of this.providers) { - if (providerName(provider) == aName) - return provider; - } - return undefined; - }, - - /** - * Initializes the AddonManager, loading any known providers and initializing - * them. - */ - startup: function() { - try { - if (gStarted) - return; - - this.recordTimestamp("AMI_startup_begin"); - - // clear this for xpcshell test restarts - for (let provider in this.telemetryDetails) - delete this.telemetryDetails[provider]; - - let appChanged = undefined; - - let oldAppVersion = null; - try { - oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION); - appChanged = Services.appinfo.version != oldAppVersion; - } - catch (e) { } - - Extension.browserUpdated = appChanged; - - let oldPlatformVersion = null; - try { - oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION); - } - catch (e) { } - - if (appChanged !== false) { - logger.debug("Application has been upgraded"); - Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION, - Services.appinfo.version); - Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION, - Services.appinfo.platformVersion); - Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, - (appChanged === undefined ? 0 : -1)); - this.validateBlocklist(); - } - - if (!MOZ_COMPATIBILITY_NIGHTLY) { - PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." + - Services.appinfo.version.replace(BRANCH_REGEXP, "$1"); - } - - try { - gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false); - - try { - gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false); - - try { - let defaultBranch = Services.prefs.getDefaultBranch(""); - gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); - } catch (e) {} - - try { - gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false); - - try { - gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false); - - try { - gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false); - - try { - gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); - } catch (e) {} - Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); - - try { - gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); - } catch (e) {} - Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); - - let defaultProvidersEnabled = true; - try { - defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); - } catch (e) {} - AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled); - - // Ensure all default providers have had a chance to register themselves - if (defaultProvidersEnabled) { - for (let url of DEFAULT_PROVIDERS) { - try { - let scope = {}; - Components.utils.import(url, scope); - // Sanity check - make sure the provider exports a symbol that - // has a 'startup' method - let syms = Object.keys(scope); - if ((syms.length < 1) || - (typeof scope[syms[0]].startup != "function")) { - logger.warn("Provider " + url + " has no startup()"); - AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()"); - } - logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource()); - } - catch (e) { - AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); - logger.error("Exception loading default provider \"" + url + "\"", e); - } - } - } - - // Load any providers registered in the category manager - let catman = Cc["@mozilla.org/categorymanager;1"]. - getService(Ci.nsICategoryManager); - let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE); - while (entries.hasMoreElements()) { - let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data; - let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry); - - try { - Components.utils.import(url, {}); - logger.debug(`Loaded provider scope for ${url}`); - } - catch (e) { - AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); - logger.error("Exception loading provider " + entry + " from category \"" + - url + "\"", e); - } - } - - // Register our shutdown handler with the AsyncShutdown manager - gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down."); - AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.", - this.shutdownManager.bind(this), - {fetchState: this.shutdownState.bind(this)}); - - // Once we start calling providers we must allow all normal methods to work. - gStarted = true; - - for (let provider of this.pendingProviders) { - this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion); - } - - // If this is a new profile just pretend that there were no changes - if (appChanged === undefined) { - for (let type in this.startupChanges) - delete this.startupChanges[type]; - } - - // Support for remote about:plugins. Note that this module isn't loaded - // at the top because Services.appinfo is defined late in tests. - let { RemotePages } = Cu.import("resource://gre/modules/RemotePageManager.jsm", {}); - - gPluginPageListener = new RemotePages("about:plugins"); - gPluginPageListener.addMessageListener("RequestPlugins", this.requestPlugins); - - gStartupComplete = true; - this.recordTimestamp("AMI_startup_end"); - } - catch (e) { - logger.error("startup failed", e); - AddonManagerPrivate.recordException("AMI", "startup failed", e); - } - - logger.debug("Completed startup sequence"); - this.callManagerListeners("onStartup"); - }, - - /** - * Registers a new AddonProvider. - * - * @param aProvider - * The provider to register - * @param aTypes - * An optional array of add-on types - */ - registerProvider: function(aProvider, aTypes) { - if (!aProvider || typeof aProvider != "object") - throw Components.Exception("aProvider must be specified", - Cr.NS_ERROR_INVALID_ARG); - - if (aTypes && !Array.isArray(aTypes)) - throw Components.Exception("aTypes must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - this.pendingProviders.add(aProvider); - - if (aTypes) { - for (let type of aTypes) { - if (!(type.id in this.types)) { - if (!VALID_TYPES_REGEXP.test(type.id)) { - logger.warn("Ignoring invalid type " + type.id); - return; - } - - this.types[type.id] = { - type: type, - providers: [aProvider] - }; - - let typeListeners = this.typeListeners.slice(0); - for (let listener of typeListeners) - safeCall(() => listener.onTypeAdded(type)); - } - else { - this.types[type.id].providers.push(aProvider); - } - } - } - - // If we're registering after startup call this provider's startup. - if (gStarted) { - this._startProvider(aProvider); - } - }, - - /** - * Unregisters an AddonProvider. - * - * @param aProvider - * The provider to unregister - * @return Whatever the provider's 'shutdown' method returns (if anything). - * For providers that have async shutdown methods returning Promises, - * the caller should wait for that Promise to resolve. - */ - unregisterProvider: function(aProvider) { - if (!aProvider || typeof aProvider != "object") - throw Components.Exception("aProvider must be specified", - Cr.NS_ERROR_INVALID_ARG); - - this.providers.delete(aProvider); - // The test harness will unregister XPIProvider *after* shutdown, which is - // after the provider will have been moved from providers to - // pendingProviders. - this.pendingProviders.delete(aProvider); - - for (let type in this.types) { - this.types[type].providers = this.types[type].providers.filter(p => p != aProvider); - if (this.types[type].providers.length == 0) { - let oldType = this.types[type].type; - delete this.types[type]; - - let typeListeners = this.typeListeners.slice(0); - for (let listener of typeListeners) - safeCall(() => listener.onTypeRemoved(oldType)); - } - } - - // If we're unregistering after startup but before shutting down, - // remove the blocker for this provider's shutdown and call it. - // If we're already shutting down, just let gShutdownBarrier call it to avoid races. - if (gStarted && !gShutdownInProgress) { - logger.debug("Unregistering shutdown blocker for " + providerName(aProvider)); - let shutter = this.providerShutdowns.get(aProvider); - if (shutter) { - this.providerShutdowns.delete(aProvider); - gShutdownBarrier.client.removeBlocker(shutter); - return shutter(); - } - } - return undefined; - }, - - /** - * Mark a provider as safe to access via AddonManager APIs, before its - * startup has completed. - * - * Normally a provider isn't marked as safe until after its (synchronous) - * startup() method has returned. Until a provider has been marked safe, - * it won't be used by any of the AddonManager APIs. markProviderSafe() - * allows a provider to mark itself as safe during its startup; this can be - * useful if the provider wants to perform tasks that block startup, which - * happen after its required initialization tasks and therefore when the - * provider is in a safe state. - * - * @param aProvider Provider object to mark safe - */ - markProviderSafe: function(aProvider) { - if (!gStarted) { - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - } - - if (!aProvider || typeof aProvider != "object") { - throw Components.Exception("aProvider must be specified", - Cr.NS_ERROR_INVALID_ARG); - } - - if (!this.pendingProviders.has(aProvider)) { - return; - } - - this.pendingProviders.delete(aProvider); - this.providers.add(aProvider); - }, - - /** - * Calls a method on all registered providers if it exists and consumes any - * thrown exception. Return values are ignored. Any parameters after the - * method parameter are passed to the provider's method. - * WARNING: Do not use for asynchronous calls; callProviders() does not - * invoke callbacks if provider methods throw synchronous exceptions. - * - * @param aMethod - * The method name to call - * @see callProvider - */ - callProviders: function(aMethod, ...aArgs) { - if (!aMethod || typeof aMethod != "string") - throw Components.Exception("aMethod must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let providers = [...this.providers]; - for (let provider of providers) { - try { - if (aMethod in provider) - provider[aMethod].apply(provider, aArgs); - } - catch (e) { - reportProviderError(provider, aMethod, e); - } - } - }, - - /** - * Report the current state of asynchronous shutdown - */ - shutdownState() { - let state = []; - if (gShutdownBarrier) { - state.push({ - name: gShutdownBarrier.client.name, - state: gShutdownBarrier.state - }); - } - state.push({ - name: "AddonRepository: async shutdown", - state: gRepoShutdownState - }); - return state; - }, - - /** - * Shuts down the addon manager and all registered providers, this must clean - * up everything in order for automated tests to fake restarts. - * @return Promise{null} that resolves when all providers and dependent modules - * have finished shutting down - */ - shutdownManager: Task.async(function*() { - logger.debug("shutdown"); - this.callManagerListeners("onShutdown"); - - gRepoShutdownState = "pending"; - gShutdownInProgress = true; - // Clean up listeners - Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this); - Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this); - Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this); - Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this); - Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this); - Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this); - gPluginPageListener.destroy(); - gPluginPageListener = null; - - let savedError = null; - // Only shut down providers if they've been started. - if (gStarted) { - try { - yield gShutdownBarrier.wait(); - } - catch (err) { - savedError = err; - logger.error("Failure during wait for shutdown barrier", err); - AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonManager providers", err); - } - } - - // Shut down AddonRepository after providers (if any). - try { - gRepoShutdownState = "in progress"; - yield AddonRepository.shutdown(); - gRepoShutdownState = "done"; - } - catch (err) { - savedError = err; - logger.error("Failure during AddonRepository shutdown", err); - AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err); - } - - logger.debug("Async provider shutdown done"); - this.managerListeners.splice(0, this.managerListeners.length); - this.installListeners.splice(0, this.installListeners.length); - this.addonListeners.splice(0, this.addonListeners.length); - this.typeListeners.splice(0, this.typeListeners.length); - this.providerShutdowns.clear(); - for (let type in this.startupChanges) - delete this.startupChanges[type]; - gStarted = false; - gStartupComplete = false; - gShutdownBarrier = null; - gShutdownInProgress = false; - if (savedError) { - throw savedError; - } - }), - - requestPlugins: function({ target: port }) { - // Lists all the properties that plugins.html needs - const NEEDED_PROPS = ["name", "pluginLibraries", "pluginFullpath", "version", - "isActive", "blocklistState", "description", - "pluginMimeTypes"]; - function filterProperties(plugin) { - let filtered = {}; - for (let prop of NEEDED_PROPS) { - filtered[prop] = plugin[prop]; - } - return filtered; - } - - AddonManager.getAddonsByTypes(["plugin"], function(aPlugins) { - port.sendAsyncMessage("PluginList", aPlugins.map(filterProperties)); - }); - }, - - /** - * Notified when a preference we're interested in has changed. - * - * @see nsIObserver - */ - observe: function(aSubject, aTopic, aData) { - switch (aData) { - case PREF_EM_CHECK_COMPATIBILITY: { - let oldValue = gCheckCompatibility; - try { - gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); - } catch (e) { - gCheckCompatibility = true; - } - - this.callManagerListeners("onCompatibilityModeChanged"); - - if (gCheckCompatibility != oldValue) - this.updateAddonAppDisabledStates(); - - break; - } - case PREF_EM_STRICT_COMPATIBILITY: { - let oldValue = gStrictCompatibility; - try { - gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); - } catch (e) { - gStrictCompatibility = true; - } - - this.callManagerListeners("onCompatibilityModeChanged"); - - if (gStrictCompatibility != oldValue) - this.updateAddonAppDisabledStates(); - - break; - } - case PREF_EM_CHECK_UPDATE_SECURITY: { - let oldValue = gCheckUpdateSecurity; - try { - gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); - } catch (e) { - gCheckUpdateSecurity = true; - } - - this.callManagerListeners("onCheckUpdateSecurityChanged"); - - if (gCheckUpdateSecurity != oldValue) - this.updateAddonAppDisabledStates(); - - break; - } - case PREF_EM_UPDATE_ENABLED: { - let oldValue = gUpdateEnabled; - try { - gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); - } catch (e) { - gUpdateEnabled = true; - } - - this.callManagerListeners("onUpdateModeChanged"); - break; - } - case PREF_EM_AUTOUPDATE_DEFAULT: { - let oldValue = gAutoUpdateDefault; - try { - gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); - } catch (e) { - gAutoUpdateDefault = true; - } - - this.callManagerListeners("onUpdateModeChanged"); - break; - } - case PREF_EM_HOTFIX_ID: { - try { - gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); - } catch (e) { - gHotfixID = null; - } - break; - } - case PREF_MIN_WEBEXT_PLATFORM_VERSION: { - gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); - break; - } - } - }, - - /** - * Replaces %...% strings in an addon url (update and updateInfo) with - * appropriate values. - * - * @param aAddon - * The Addon representing the add-on - * @param aUri - * The string representation of the URI to escape - * @param aAppVersion - * The optional application version to use for %APP_VERSION% - * @return The appropriately escaped URI. - */ - escapeAddonURI: function(aAddon, aUri, aAppVersion) - { - if (!aAddon || typeof aAddon != "object") - throw Components.Exception("aAddon must be an Addon object", - Cr.NS_ERROR_INVALID_ARG); - - if (!aUri || typeof aUri != "string") - throw Components.Exception("aUri must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (aAppVersion && typeof aAppVersion != "string") - throw Components.Exception("aAppVersion must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled" - : "userEnabled"; - - if (!aAddon.isCompatible) - addonStatus += ",incompatible"; - if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) - addonStatus += ",blocklisted"; - if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - addonStatus += ",softblocked"; - - try { - var xpcomABI = Services.appinfo.XPCOMABI; - } catch (ex) { - xpcomABI = UNKNOWN_XPCOM_ABI; - } - - let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id); - uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version); - uri = uri.replace(/%ITEM_STATUS%/g, addonStatus); - uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID); - uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion : - Services.appinfo.version); - uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION); - uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS); - uri = uri.replace(/%APP_ABI%/g, xpcomABI); - uri = uri.replace(/%APP_LOCALE%/g, getLocale()); - uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version); - - // Replace custom parameters (names of custom parameters must have at - // least 3 characters to prevent lookups for something like %D0%C8) - var catMan = null; - uri = uri.replace(/%(\w{3,})%/g, function(aMatch, aParam) { - if (!catMan) { - catMan = Cc["@mozilla.org/categorymanager;1"]. - getService(Ci.nsICategoryManager); - } - - try { - var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam); - var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2); - return paramHandler.getPropertyAsAString(aParam); - } - catch (e) { - return aMatch; - } - }); - - // escape() does not properly encode + symbols in any embedded FVF strings. - return uri.replace(/\+/g, "%2B"); - }, - - /** - * Performs a background update check by starting an update for all add-ons - * that can be updated. - * @return Promise{null} Resolves when the background update check is complete - * (the resulting addon installations may still be in progress). - */ - backgroundUpdateCheck: function() { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - let buPromise = Task.spawn(function*() { - let hotfixID = this.hotfixID; - - let appUpdateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && - Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); - let checkHotfix = hotfixID && appUpdateEnabled; - - logger.debug("Background update check beginning"); - - Services.obs.notifyObservers(null, "addons-background-update-start", null); - - if (this.updateEnabled) { - let scope = {}; - Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope); - scope.LightweightThemeManager.updateCurrentTheme(); - - let allAddons = yield new Promise((resolve, reject) => this.getAllAddons(resolve)); - - // Repopulate repository cache first, to ensure compatibility overrides - // are up to date before checking for addon updates. - yield AddonRepository.backgroundUpdateCheck(); - - // Keep track of all the async add-on updates happening in parallel - let updates = []; - - for (let addon of allAddons) { - if (addon.id == hotfixID) { - continue; - } - - // Check all add-ons for updates so that any compatibility updates will - // be applied - updates.push(new Promise((resolve, reject) => { - addon.findUpdates({ - onUpdateAvailable: function(aAddon, aInstall) { - // Start installing updates when the add-on can be updated and - // background updates should be applied. - logger.debug("Found update for add-on ${id}", aAddon); - if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && - AddonManager.shouldAutoUpdate(aAddon)) { - // XXX we really should resolve when this install is done, - // not when update-available check completes, no? - logger.debug(`Starting upgrade install of ${aAddon.id}`); - aInstall.install(); - } - }, - - onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - })); - } - yield Promise.all(updates); - } - - if (checkHotfix) { - var hotfixVersion = ""; - try { - hotfixVersion = Services.prefs.getCharPref(PREF_EM_HOTFIX_LASTVERSION); - } - catch (e) { } - - let url = null; - if (Services.prefs.getPrefType(PREF_EM_HOTFIX_URL) == Ci.nsIPrefBranch.PREF_STRING) - url = Services.prefs.getCharPref(PREF_EM_HOTFIX_URL); - else - url = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL); - - // Build the URI from a fake add-on data. - url = AddonManager.escapeAddonURI({ - id: hotfixID, - version: hotfixVersion, - userDisabled: false, - appDisabled: false - }, url); - - Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); - let update = null; - try { - let foundUpdates = yield new Promise((resolve, reject) => { - AddonUpdateChecker.checkForUpdates(hotfixID, null, url, { - onUpdateCheckComplete: resolve, - onUpdateCheckError: reject - }); - }); - update = AddonUpdateChecker.getNewestCompatibleUpdate(foundUpdates); - } catch (e) { - // AUC.checkForUpdates already logged the error - } - - // Check that we have a hotfix update, and it's newer than the one we already - // have installed (if any) - if (update) { - if (Services.vc.compare(hotfixVersion, update.version) < 0) { - logger.debug("Downloading hotfix version " + update.version); - let aInstall = yield new Promise((resolve, reject) => - AddonManager.getInstallForURL(update.updateURL, resolve, - "application/x-xpinstall", update.updateHash, null, - null, update.version)); - - aInstall.addListener({ - onDownloadEnded: function(aInstall) { - if (aInstall.addon.id != hotfixID) { - logger.warn("The downloaded hotfix add-on did not have the " + - "expected ID and so will not be installed."); - aInstall.cancel(); - return; - } - - // If XPIProvider has reported the hotfix as properly signed then - // there is nothing more to do here - if (aInstall.addon.signedState == AddonManager.SIGNEDSTATE_SIGNED) - return; - - try { - if (!Services.prefs.getBoolPref(PREF_EM_CERT_CHECKATTRIBUTES)) - return; - } - catch (e) { - // By default don't do certificate checks. - return; - } - - try { - CertUtils.validateCert(aInstall.certificate, - CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS)); - } - catch (e) { - logger.warn("The hotfix add-on was not signed by the expected " + - "certificate and so will not be installed.", e); - aInstall.cancel(); - } - }, - - onInstallEnded: function(aInstall) { - // Remember the last successfully installed version. - Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, - aInstall.version); - }, - - onInstallCancelled: function(aInstall) { - // Revert to the previous version if the installation was - // cancelled. - Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, - hotfixVersion); - } - }); - - aInstall.install(); - } - } - } - - if (appUpdateEnabled) { - try { - yield AddonManagerInternal._getProviderByName("XPIProvider").updateSystemAddons(); - } - catch (e) { - logger.warn("Failed to update system addons", e); - } - } - - logger.debug("Background update check complete"); - Services.obs.notifyObservers(null, - "addons-background-update-complete", - null); - }.bind(this)); - // Fork the promise chain so we can log the error and let our caller see it too. - buPromise.then(null, e => logger.warn("Error in background update", e)); - return buPromise; - }, - - /** - * Adds a add-on to the list of detected changes for this startup. If - * addStartupChange is called multiple times for the same add-on in the same - * startup then only the most recent change will be remembered. - * - * @param aType - * The type of change as a string. Providers can define their own - * types of changes or use the existing defined STARTUP_CHANGE_* - * constants - * @param aID - * The ID of the add-on - */ - addStartupChange: function(aType, aID) { - if (!aType || typeof aType != "string") - throw Components.Exception("aType must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (!aID || typeof aID != "string") - throw Components.Exception("aID must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (gStartupComplete) - return; - logger.debug("Registering startup change '" + aType + "' for " + aID); - - // Ensure that an ID is only listed in one type of change - for (let type in this.startupChanges) - this.removeStartupChange(type, aID); - - if (!(aType in this.startupChanges)) - this.startupChanges[aType] = []; - this.startupChanges[aType].push(aID); - }, - - /** - * Removes a startup change for an add-on. - * - * @param aType - * The type of change - * @param aID - * The ID of the add-on - */ - removeStartupChange: function(aType, aID) { - if (!aType || typeof aType != "string") - throw Components.Exception("aType must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (!aID || typeof aID != "string") - throw Components.Exception("aID must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (gStartupComplete) - return; - - if (!(aType in this.startupChanges)) - return; - - this.startupChanges[aType] = this.startupChanges[aType].filter(aItem => aItem != aID); - }, - - /** - * Calls all registered AddonManagerListeners with an event. Any parameters - * after the method parameter are passed to the listener. - * - * @param aMethod - * The method on the listeners to call - */ - callManagerListeners: function(aMethod, ...aArgs) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMethod || typeof aMethod != "string") - throw Components.Exception("aMethod must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let managerListeners = this.managerListeners.slice(0); - for (let listener of managerListeners) { - try { - if (aMethod in listener) - listener[aMethod].apply(listener, aArgs); - } - catch (e) { - logger.warn("AddonManagerListener threw exception when calling " + aMethod, e); - } - } - }, - - /** - * Calls all registered InstallListeners with an event. Any parameters after - * the extraListeners parameter are passed to the listener. - * - * @param aMethod - * The method on the listeners to call - * @param aExtraListeners - * An optional array of extra InstallListeners to also call - * @return false if any of the listeners returned false, true otherwise - */ - callInstallListeners: function(aMethod, - aExtraListeners, ...aArgs) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMethod || typeof aMethod != "string") - throw Components.Exception("aMethod must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (aExtraListeners && !Array.isArray(aExtraListeners)) - throw Components.Exception("aExtraListeners must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - let result = true; - let listeners; - if (aExtraListeners) - listeners = aExtraListeners.concat(this.installListeners); - else - listeners = this.installListeners.slice(0); - - for (let listener of listeners) { - try { - if (aMethod in listener) { - if (listener[aMethod].apply(listener, aArgs) === false) - result = false; - } - } - catch (e) { - logger.warn("InstallListener threw exception when calling " + aMethod, e); - } - } - return result; - }, - - /** - * Calls all registered AddonListeners with an event. Any parameters after - * the method parameter are passed to the listener. - * - * @param aMethod - * The method on the listeners to call - */ - callAddonListeners: function(aMethod, ...aArgs) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMethod || typeof aMethod != "string") - throw Components.Exception("aMethod must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let addonListeners = this.addonListeners.slice(0); - for (let listener of addonListeners) { - try { - if (aMethod in listener) - listener[aMethod].apply(listener, aArgs); - } - catch (e) { - logger.warn("AddonListener threw exception when calling " + aMethod, e); - } - } - }, - - /** - * Notifies all providers that an add-on has been enabled when that type of - * add-on only supports a single add-on being enabled at a time. This allows - * the providers to disable theirs if necessary. - * - * @param aID - * The ID of the enabled add-on - * @param aType - * The type of the enabled add-on - * @param aPendingRestart - * A boolean indicating if the change will only take place the next - * time the application is restarted - */ - notifyAddonChanged: function(aID, aType, aPendingRestart) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (aID && typeof aID != "string") - throw Components.Exception("aID must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - if (!aType || typeof aType != "string") - throw Components.Exception("aType must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - // Temporary hack until bug 520124 lands. - // We can get here during synchronous startup, at which point it's - // considered unsafe (and therefore disallowed by AddonManager.jsm) to - // access providers that haven't been initialized yet. Since this is when - // XPIProvider is starting up, XPIProvider can't access itself via APIs - // going through AddonManager.jsm. Furthermore, LightweightThemeManager may - // not be initialized until after XPIProvider is, and therefore would also - // be unaccessible during XPIProvider startup. Thankfully, these are the - // only two uses of this API, and we know it's safe to use this API with - // both providers; so we have this hack to allow bypassing the normal - // safetey guard. - // The notifyAddonChanged/addonChanged API will be unneeded and therefore - // removed by bug 520124, so this is a temporary quick'n'dirty hack. - let providers = [...this.providers, ...this.pendingProviders]; - for (let provider of providers) { - callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart); - } - }, - - /** - * Notifies all providers they need to update the appDisabled property for - * their add-ons in response to an application change such as a blocklist - * update. - */ - updateAddonAppDisabledStates: function() { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - this.callProviders("updateAddonAppDisabledStates"); - }, - - /** - * Notifies all providers that the repository has updated its data for - * installed add-ons. - * - * @param aCallback - * Function to call when operation is complete. - */ - updateAddonRepositoryData: function(aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "updateAddonRepositoryData", - aCaller.callNext.bind(aCaller)); - }, - noMoreObjects: function(aCaller) { - safeCall(aCallback); - // only tests should care about this - Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null); - } - }); - }, - - /** - * Asynchronously gets an AddonInstall for a URL. - * - * @param aUrl - * The string represenation of the URL the add-on is located at - * @param aCallback - * A callback to pass the AddonInstall to - * @param aMimetype - * The mimetype of the add-on - * @param aHash - * An optional hash of the add-on - * @param aName - * An optional placeholder name while the add-on is being downloaded - * @param aIcons - * Optional placeholder icons while the add-on is being downloaded - * @param aVersion - * An optional placeholder version while the add-on is being downloaded - * @param aLoadGroup - * An optional nsILoadGroup to associate any network requests with - * @throws if the aUrl, aCallback or aMimetype arguments are not specified - */ - getInstallForURL: function(aUrl, aCallback, aMimetype, - aHash, aName, aIcons, - aVersion, aBrowser) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aUrl || typeof aUrl != "string") - throw Components.Exception("aURL must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - if (!aMimetype || typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (aHash && typeof aHash != "string") - throw Components.Exception("aHash must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - if (aName && typeof aName != "string") - throw Components.Exception("aName must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - if (aIcons) { - if (typeof aIcons == "string") - aIcons = { "32": aIcons }; - else if (typeof aIcons != "object") - throw Components.Exception("aIcons must be a string, an object or null", - Cr.NS_ERROR_INVALID_ARG); - } else { - aIcons = {}; - } - - if (aVersion && typeof aVersion != "string") - throw Components.Exception("aVersion must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - if (aBrowser && (!(aBrowser instanceof Ci.nsIDOMElement))) - throw Components.Exception("aBrowser must be a nsIDOMElement or null", - Cr.NS_ERROR_INVALID_ARG); - - let providers = [...this.providers]; - for (let provider of providers) { - if (callProvider(provider, "supportsMimetype", false, aMimetype)) { - callProviderAsync(provider, "getInstallForURL", - aUrl, aHash, aName, aIcons, aVersion, aBrowser, - function getInstallForURL_safeCall(aInstall) { - safeCall(aCallback, aInstall); - }); - return; - } - } - safeCall(aCallback, null); - }, - - /** - * Asynchronously gets an AddonInstall for an nsIFile. - * - * @param aFile - * The nsIFile where the add-on is located - * @param aCallback - * A callback to pass the AddonInstall to - * @param aMimetype - * An optional mimetype hint for the add-on - * @throws if the aFile or aCallback arguments are not specified - */ - getInstallForFile: function(aFile, aCallback, aMimetype) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!(aFile instanceof Ci.nsIFile)) - throw Components.Exception("aFile must be a nsIFile", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - if (aMimetype && typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a string or null", - Cr.NS_ERROR_INVALID_ARG); - - new AsyncObjectCaller(this.providers, "getInstallForFile", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "getInstallForFile", aFile, - function(aInstall) { - if (aInstall) - safeCall(aCallback, aInstall); - else - aCaller.callNext(); - }); - }, - - noMoreObjects: function(aCaller) { - safeCall(aCallback, null); - } - }); - }, - - /** - * Asynchronously gets all current AddonInstalls optionally limiting to a list - * of types. - * - * @param aTypes - * An optional array of types to retrieve. Each type is a string name - * @param aCallback - * A callback which will be passed an array of AddonInstalls - * @throws If the aCallback argument is not specified - */ - getInstallsByTypes: function(aTypes, aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (aTypes && !Array.isArray(aTypes)) - throw Components.Exception("aTypes must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - let installs = []; - - new AsyncObjectCaller(this.providers, "getInstallsByTypes", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "getInstallsByTypes", aTypes, - function(aProviderInstalls) { - if (aProviderInstalls) { - installs = installs.concat(aProviderInstalls); - } - aCaller.callNext(); - }); - }, - - noMoreObjects: function(aCaller) { - safeCall(aCallback, installs); - } - }); - }, - - /** - * Asynchronously gets all current AddonInstalls. - * - * @param aCallback - * A callback which will be passed an array of AddonInstalls - */ - getAllInstalls: function(aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - this.getInstallsByTypes(null, aCallback); - }, - - /** - * Synchronously map a URI to the corresponding Addon ID. - * - * Mappable URIs are limited to in-application resources belonging to the - * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. - * but do not include URIs from meta data, such as the add-on homepage. - * - * @param aURI - * nsIURI to map to an addon id - * @return string containing the Addon ID or null - * @see amIAddonManager.mapURIToAddonID - */ - mapURIToAddonID: function(aURI) { - if (!(aURI instanceof Ci.nsIURI)) { - throw Components.Exception("aURI is not a nsIURI", - Cr.NS_ERROR_INVALID_ARG); - } - - // Try all providers - let providers = [...this.providers]; - for (let provider of providers) { - var id = callProvider(provider, "mapURIToAddonID", null, aURI); - if (id !== null) { - return id; - } - } - - return null; - }, - - /** - * Checks whether installation is enabled for a particular mimetype. - * - * @param aMimetype - * The mimetype to check - * @return true if installation is enabled for the mimetype - */ - isInstallEnabled: function(aMimetype) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMimetype || typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let providers = [...this.providers]; - for (let provider of providers) { - if (callProvider(provider, "supportsMimetype", false, aMimetype) && - callProvider(provider, "isInstallEnabled")) - return true; - } - return false; - }, - - /** - * Checks whether a particular source is allowed to install add-ons of a - * given mimetype. - * - * @param aMimetype - * The mimetype of the add-on - * @param aInstallingPrincipal - * The nsIPrincipal that initiated the install - * @return true if the source is allowed to install this mimetype - */ - isInstallAllowed: function(aMimetype, aInstallingPrincipal) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMimetype || typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal)) - throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal", - Cr.NS_ERROR_INVALID_ARG); - - let providers = [...this.providers]; - for (let provider of providers) { - if (callProvider(provider, "supportsMimetype", false, aMimetype) && - callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal)) - return true; - } - return false; - }, - - /** - * Starts installation of an array of AddonInstalls notifying the registered - * web install listener of blocked or started installs. - * - * @param aMimetype - * The mimetype of add-ons being installed - * @param aBrowser - * The optional browser element that started the installs - * @param aInstallingPrincipal - * The nsIPrincipal that initiated the install - * @param aInstalls - * The array of AddonInstalls to be installed - */ - installAddonsFromWebpage: function(aMimetype, aBrowser, - aInstallingPrincipal, aInstalls) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aMimetype || typeof aMimetype != "string") - throw Components.Exception("aMimetype must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement)) - throw Components.Exception("aSource must be a nsIDOMElement, or null", - Cr.NS_ERROR_INVALID_ARG); - - if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal)) - throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal", - Cr.NS_ERROR_INVALID_ARG); - - if (!Array.isArray(aInstalls)) - throw Components.Exception("aInstalls must be an array", - Cr.NS_ERROR_INVALID_ARG); - - if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) { - logger.warn("No web installer available, cancelling all installs"); - for (let install of aInstalls) - install.cancel(); - return; - } - - // When a chrome in-content UI has loaded a inside to host a - // website we want to do our security checks on the inner-browser but - // notify front-end that install events came from the outer-browser (the - // main tab's browser). Check this by seeing if the browser we've been - // passed is in a content type docshell and if so get the outer-browser. - let topBrowser = aBrowser; - let docShell = aBrowser.ownerDocument.defaultView - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIDocShellTreeItem); - if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent) - topBrowser = docShell.chromeEventHandler; - - try { - let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]. - getService(Ci.amIWebInstallListener); - - if (!this.isInstallEnabled(aMimetype)) { - for (let install of aInstalls) - install.cancel(); - - weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI, - aInstalls, aInstalls.length); - return; - } - else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) { - for (let install of aInstalls) - install.cancel(); - - if (weblistener instanceof Ci.amIWebInstallListener2) { - weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI, - aInstalls, aInstalls.length); - } - return; - } - - // The installs may start now depending on the web install listener, - // listen for the browser navigating to a new origin and cancel the - // installs in that case. - new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls); - - if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) { - if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI, - aInstalls, aInstalls.length)) { - for (let install of aInstalls) - install.install(); - } - } - else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI, - aInstalls, aInstalls.length)) { - for (let install of aInstalls) - install.install(); - } - } - catch (e) { - // In the event that the weblistener throws during instantiation or when - // calling onWebInstallBlocked or onWebInstallRequested all of the - // installs should get cancelled. - logger.warn("Failure calling web installer", e); - for (let install of aInstalls) - install.cancel(); - } - }, - - /** - * Adds a new InstallListener if the listener is not already registered. - * - * @param aListener - * The InstallListener to add - */ - addInstallListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be a InstallListener object", - Cr.NS_ERROR_INVALID_ARG); - - if (!this.installListeners.some(function(i) { - return i == aListener; })) - this.installListeners.push(aListener); - }, - - /** - * Removes an InstallListener if the listener is registered. - * - * @param aListener - * The InstallListener to remove - */ - removeInstallListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be a InstallListener object", - Cr.NS_ERROR_INVALID_ARG); - - let pos = 0; - while (pos < this.installListeners.length) { - if (this.installListeners[pos] == aListener) - this.installListeners.splice(pos, 1); - else - pos++; - } - }, - /* - * Adds new or overrides existing UpgradeListener. - * - * @param aInstanceID - * The instance ID of an addon to register a listener for. - * @param aCallback - * The callback to invoke when updates are available for this addon. - * @throws if there is no addon matching the instanceID - */ - addUpgradeListener: function(aInstanceID, aCallback) { - if (!aInstanceID || typeof aInstanceID != "symbol") - throw Components.Exception("aInstanceID must be a symbol", - Cr.NS_ERROR_INVALID_ARG); - - if (!aCallback || typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - this.getAddonByInstanceID(aInstanceID).then(wrapper => { - if (!wrapper) { - throw Error("No addon matching instanceID:", aInstanceID.toString()); - } - let addonId = wrapper.addonId(); - logger.debug(`Registering upgrade listener for ${addonId}`); - this.upgradeListeners.set(addonId, aCallback); - }); - }, - - /** - * Removes an UpgradeListener if the listener is registered. - * - * @param aInstanceID - * The instance ID of the addon to remove - */ - removeUpgradeListener: function(aInstanceID) { - if (!aInstanceID || typeof aInstanceID != "symbol") - throw Components.Exception("aInstanceID must be a symbol", - Cr.NS_ERROR_INVALID_ARG); - - this.getAddonByInstanceID(aInstanceID).then(addon => { - if (!addon) { - throw Error("No addon for instanceID:", aInstanceID.toString()); - } - if (this.upgradeListeners.has(addon.id)) { - this.upgradeListeners.delete(addon.id); - } else { - throw Error("No upgrade listener registered for addon ID:", addon.id); - } - }); - }, - - /** - * Installs a temporary add-on from a local file or directory. - * @param aFile - * An nsIFile for the file or directory of the add-on to be - * temporarily installed. - * @return a Promise that rejects if the add-on is not a valid restartless - * add-on or if the same ID is already temporarily installed. - */ - installTemporaryAddon: function(aFile) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!(aFile instanceof Ci.nsIFile)) - throw Components.Exception("aFile must be a nsIFile", - Cr.NS_ERROR_INVALID_ARG); - - return AddonManagerInternal._getProviderByName("XPIProvider") - .installTemporaryAddon(aFile); - }, - - installAddonFromSources: function(aFile) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!(aFile instanceof Ci.nsIFile)) - throw Components.Exception("aFile must be a nsIFile", - Cr.NS_ERROR_INVALID_ARG); - - return AddonManagerInternal._getProviderByName("XPIProvider") - .installAddonFromSources(aFile); - }, - - /** - * Returns an Addon corresponding to an instance ID. - * @param aInstanceID - * An Addon Instance ID symbol - * @return {Promise} - * @resolves The found Addon or null if no such add-on exists. - * @rejects Never - * @throws if the aInstanceID argument is not specified - * or the AddonManager is not initialized - */ - getAddonByInstanceID: function(aInstanceID) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aInstanceID || typeof aInstanceID != "symbol") - throw Components.Exception("aInstanceID must be a Symbol()", - Cr.NS_ERROR_INVALID_ARG); - - return AddonManagerInternal._getProviderByName("XPIProvider") - .getAddonByInstanceID(aInstanceID); - }, - - /** - * Gets an icon from the icon set provided by the add-on - * that is closest to the specified size. - * - * The optional window parameter will be used to determine - * the screen resolution and select a more appropriate icon. - * Calling this method with 48px on retina screens will try to - * match an icon of size 96px. - * - * @param aAddon - * An addon object, meaning: - * An object with either an icons property that is a key-value - * list of icon size and icon URL, or an object having an iconURL - * and icon64URL property. - * @param aSize - * Ideal icon size in pixels - * @param aWindow - * Optional window object for determining the correct scale. - * @return {String} The absolute URL of the icon or null if the addon doesn't have icons - */ - getPreferredIconURL: function(aAddon, aSize, aWindow = undefined) { - if (aWindow && aWindow.devicePixelRatio) { - aSize *= aWindow.devicePixelRatio; - } - - let icons = aAddon.icons; - - // certain addon-types only have iconURLs - if (!icons) { - icons = {}; - if (aAddon.iconURL) { - icons[32] = aAddon.iconURL; - icons[48] = aAddon.iconURL; - } - if (aAddon.icon64URL) { - icons[64] = aAddon.icon64URL; - } - } - - // quick return if the exact size was found - if (icons[aSize]) { - return icons[aSize]; - } - - let bestSize = null; - - for (let size of Object.keys(icons)) { - if (!INTEGER.test(size)) { - throw Components.Exception("Invalid icon size, must be an integer", - Cr.NS_ERROR_ILLEGAL_VALUE); - } - - size = parseInt(size, 10); - - if (!bestSize) { - bestSize = size; - continue; - } - - if (size > aSize && bestSize > aSize) { - // If both best size and current size are larger than the wanted size then choose - // the one closest to the wanted size - bestSize = Math.min(bestSize, size); - } - else { - // Otherwise choose the largest of the two so we'll prefer sizes as close to below aSize - // or above aSize - bestSize = Math.max(bestSize, size); - } - } - - return icons[bestSize] || null; - }, - - /** - * Asynchronously gets an add-on with a specific ID. - * - * @param aID - * The ID of the add-on to retrieve - * @return {Promise} - * @resolves The found Addon or null if no such add-on exists. - * @rejects Never - * @throws if the aID argument is not specified - */ - getAddonByID: function(aID) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aID || typeof aID != "string") - throw Components.Exception("aID must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - let promises = Array.from(this.providers, - p => promiseCallProvider(p, "getAddonByID", aID)); - return Promise.all(promises).then(aAddons => { - return aAddons.find(a => !!a) || null; - }); - }, - - /** - * Asynchronously get an add-on with a specific Sync GUID. - * - * @param aGUID - * String GUID of add-on to retrieve - * @param aCallback - * The callback to pass the retrieved add-on to. - * @throws if the aGUID or aCallback arguments are not specified - */ - getAddonBySyncGUID: function(aGUID, aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!aGUID || typeof aGUID != "string") - throw Components.Exception("aGUID must be a non-empty string", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "getAddonBySyncGUID", aGUID, - function(aAddon) { - if (aAddon) { - safeCall(aCallback, aAddon); - } else { - aCaller.callNext(); - } - }); - }, - - noMoreObjects: function(aCaller) { - safeCall(aCallback, null); - } - }); - }, - - /** - * Asynchronously gets an array of add-ons. - * - * @param aIDs - * The array of IDs to retrieve - * @return {Promise} - * @resolves The array of found add-ons. - * @rejects Never - * @throws if the aIDs argument is not specified - */ - getAddonsByIDs: function(aIDs) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (!Array.isArray(aIDs)) - throw Components.Exception("aIDs must be an array", - Cr.NS_ERROR_INVALID_ARG); - - let promises = aIDs.map(a => AddonManagerInternal.getAddonByID(a)); - return Promise.all(promises); - }, - - /** - * Asynchronously gets add-ons of specific types. - * - * @param aTypes - * An optional array of types to retrieve. Each type is a string name - * @param aCallback - * The callback to pass an array of Addons to. - * @throws if the aCallback argument is not specified - */ - getAddonsByTypes: function(aTypes, aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (aTypes && !Array.isArray(aTypes)) - throw Components.Exception("aTypes must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - let addons = []; - - new AsyncObjectCaller(this.providers, "getAddonsByTypes", { - nextObject: function(aCaller, aProvider) { - callProviderAsync(aProvider, "getAddonsByTypes", aTypes, - function(aProviderAddons) { - if (aProviderAddons) { - addons = addons.concat(aProviderAddons); - } - aCaller.callNext(); - }); - }, - - noMoreObjects: function(aCaller) { - safeCall(aCallback, addons); - } - }); - }, - - /** - * Asynchronously gets all installed add-ons. - * - * @param aCallback - * A callback which will be passed an array of Addons - */ - getAllAddons: function(aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - this.getAddonsByTypes(null, aCallback); - }, - - /** - * Asynchronously gets add-ons that have operations waiting for an application - * restart to complete. - * - * @param aTypes - * An optional array of types to retrieve. Each type is a string name - * @param aCallback - * The callback to pass the array of Addons to - * @throws if the aCallback argument is not specified - */ - getAddonsWithOperationsByTypes: function(aTypes, aCallback) { - if (!gStarted) - throw Components.Exception("AddonManager is not initialized", - Cr.NS_ERROR_NOT_INITIALIZED); - - if (aTypes && !Array.isArray(aTypes)) - throw Components.Exception("aTypes must be an array or null", - Cr.NS_ERROR_INVALID_ARG); - - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - let addons = []; - - new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", { - nextObject: function getAddonsWithOperationsByTypes_nextObject - (aCaller, aProvider) { - callProviderAsync(aProvider, "getAddonsWithOperationsByTypes", aTypes, - function getAddonsWithOperationsByTypes_concatAddons - (aProviderAddons) { - if (aProviderAddons) { - addons = addons.concat(aProviderAddons); - } - aCaller.callNext(); - }); - }, - - noMoreObjects: function(caller) { - safeCall(aCallback, addons); - } - }); - }, - - /** - * Adds a new AddonManagerListener if the listener is not already registered. - * - * @param aListener - * The listener to add - */ - addManagerListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be an AddonManagerListener object", - Cr.NS_ERROR_INVALID_ARG); - - if (!this.managerListeners.some(i => i == aListener)) - this.managerListeners.push(aListener); - }, - - /** - * Removes an AddonManagerListener if the listener is registered. - * - * @param aListener - * The listener to remove - */ - removeManagerListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be an AddonManagerListener object", - Cr.NS_ERROR_INVALID_ARG); - - let pos = 0; - while (pos < this.managerListeners.length) { - if (this.managerListeners[pos] == aListener) - this.managerListeners.splice(pos, 1); - else - pos++; - } - }, - - /** - * Adds a new AddonListener if the listener is not already registered. - * - * @param aListener - * The AddonListener to add - */ - addAddonListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be an AddonListener object", - Cr.NS_ERROR_INVALID_ARG); - - if (!this.addonListeners.some(i => i == aListener)) - this.addonListeners.push(aListener); - }, - - /** - * Removes an AddonListener if the listener is registered. - * - * @param aListener - * The AddonListener to remove - */ - removeAddonListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be an AddonListener object", - Cr.NS_ERROR_INVALID_ARG); - - let pos = 0; - while (pos < this.addonListeners.length) { - if (this.addonListeners[pos] == aListener) - this.addonListeners.splice(pos, 1); - else - pos++; - } - }, - - /** - * Adds a new TypeListener if the listener is not already registered. - * - * @param aListener - * The TypeListener to add - */ - addTypeListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be a TypeListener object", - Cr.NS_ERROR_INVALID_ARG); - - if (!this.typeListeners.some(i => i == aListener)) - this.typeListeners.push(aListener); - }, - - /** - * Removes an TypeListener if the listener is registered. - * - * @param aListener - * The TypeListener to remove - */ - removeTypeListener: function(aListener) { - if (!aListener || typeof aListener != "object") - throw Components.Exception("aListener must be a TypeListener object", - Cr.NS_ERROR_INVALID_ARG); - - let pos = 0; - while (pos < this.typeListeners.length) { - if (this.typeListeners[pos] == aListener) - this.typeListeners.splice(pos, 1); - else - pos++; - } - }, - - get addonTypes() { - // A read-only wrapper around the types dictionary - return new Proxy(this.types, { - defineProperty(target, property, descriptor) { - // Not allowed to define properties - return false; - }, - - deleteProperty(target, property) { - // Not allowed to delete properties - return false; - }, - - get(target, property, receiver) { - if (!target.hasOwnProperty(property)) - return undefined; - - return target[property].type; - }, - - getOwnPropertyDescriptor(target, property) { - if (!target.hasOwnProperty(property)) - return undefined; - - return { - value: target[property].type, - writable: false, - // Claim configurability to maintain the proxy invariants. - configurable: true, - enumerable: true - } - }, - - preventExtensions(target) { - // Not allowed to prevent adding new properties - return false; - }, - - set(target, property, value, receiver) { - // Not allowed to set properties - return false; - }, - - setPrototypeOf(target, prototype) { - // Not allowed to change prototype - return false; - } - }); - }, - - get autoUpdateDefault() { - return gAutoUpdateDefault; - }, - - set autoUpdateDefault(aValue) { - aValue = !!aValue; - if (aValue != gAutoUpdateDefault) - Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue); - return aValue; - }, - - get checkCompatibility() { - return gCheckCompatibility; - }, - - set checkCompatibility(aValue) { - aValue = !!aValue; - if (aValue != gCheckCompatibility) { - if (!aValue) - Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false); - else - Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY); - } - return aValue; - }, - - get strictCompatibility() { - return gStrictCompatibility; - }, - - set strictCompatibility(aValue) { - aValue = !!aValue; - if (aValue != gStrictCompatibility) - Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue); - return aValue; - }, - - get checkUpdateSecurityDefault() { - return gCheckUpdateSecurityDefault; - }, - - get checkUpdateSecurity() { - return gCheckUpdateSecurity; - }, - - set checkUpdateSecurity(aValue) { - aValue = !!aValue; - if (aValue != gCheckUpdateSecurity) { - if (aValue != gCheckUpdateSecurityDefault) - Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue); - else - Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY); - } - return aValue; - }, - - get updateEnabled() { - return gUpdateEnabled; - }, - - set updateEnabled(aValue) { - aValue = !!aValue; - if (aValue != gUpdateEnabled) - Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue); - return aValue; - }, - - get hotfixID() { - return gHotfixID; - }, - - webAPI: { - // installs maps integer ids to AddonInstall instances. - installs: new Map(), - nextInstall: 0, - - sendEvent: null, - setEventHandler(fn) { - this.sendEvent = fn; - }, - - getAddonByID(target, id) { - return new Promise(resolve => { - AddonManager.getAddonByID(id, (addon) => { - resolve(webAPIForAddon(addon)); - }); - }); - }, - - // helper to copy (and convert) the properties we care about - copyProps(install, obj) { - obj.state = AddonManager.stateToString(install.state); - obj.error = AddonManager.errorToString(install.error); - obj.progress = install.progress; - obj.maxProgress = install.maxProgress; - }, - - makeListener(id, mm) { - const events = [ - "onDownloadStarted", - "onDownloadProgress", - "onDownloadEnded", - "onDownloadCancelled", - "onDownloadFailed", - "onInstallStarted", - "onInstallEnded", - "onInstallCancelled", - "onInstallFailed", - ]; - - let listener = {}; - events.forEach(event => { - listener[event] = (install) => { - let data = {event, id}; - AddonManager.webAPI.copyProps(install, data); - this.sendEvent(mm, data); - } - }); - return listener; - }, - - forgetInstall(id) { - let info = this.installs.get(id); - if (!info) { - throw new Error(`forgetInstall cannot find ${id}`); - } - info.install.removeListener(info.listener); - this.installs.delete(id); - }, - - createInstall(target, options) { - // Throw an appropriate error if the given URL is not valid - // as an installation source. Return silently if it is okay. - function checkInstallUrl(url) { - let host = Services.io.newURI(options.url, null, null).host; - if (WEBAPI_INSTALL_HOSTS.includes(host)) { - return; - } - if (Services.prefs.getBoolPref(PREF_WEBAPI_TESTING) - && WEBAPI_TEST_INSTALL_HOSTS.includes(host)) { - return; - } - - throw new Error(`Install from ${host} not permitted`); - } - - return new Promise((resolve, reject) => { - try { - checkInstallUrl(options.url); - } catch (err) { - reject({message: err.message}); - return; - } - - let newInstall = install => { - let id = this.nextInstall++; - let listener = this.makeListener(id, target.messageManager); - install.addListener(listener); - - this.installs.set(id, {install, target, listener}); - - let result = {id}; - this.copyProps(install, result); - resolve(result); - }; - AddonManager.getInstallForURL(options.url, newInstall, "application/x-xpinstall", options.hash); - }); - }, - - addonUninstall(target, id) { - return new Promise(resolve => { - AddonManager.getAddonByID(id, addon => { - if (!addon) { - resolve(false); - } - - try { - addon.uninstall(); - resolve(true); - } catch (err) { - Cu.reportError(err); - resolve(false); - } - }); - }); - }, - - addonSetEnabled(target, id, value) { - return new Promise((resolve, reject) => { - AddonManager.getAddonByID(id, addon => { - if (!addon) { - reject({message: `No such addon ${id}`}); - } - addon.userDisabled = !value; - resolve(); - }); - }); - }, - - addonInstallDoInstall(target, id) { - let state = this.installs.get(id); - if (!state) { - return Promise.reject(`invalid id ${id}`); - } - return Promise.resolve(state.install.install()); - }, - - addonInstallCancel(target, id) { - let state = this.installs.get(id); - if (!state) { - return Promise.reject(`invalid id ${id}`); - } - return Promise.resolve(state.install.cancel()); - }, - - clearInstalls(ids) { - for (let id of ids) { - this.forgetInstall(id); - } - }, - - clearInstallsFrom(mm) { - for (let [id, info] of this.installs) { - if (info.target.messageManager == mm) { - this.forgetInstall(id); - } - } - }, - }, -}; - -/** - * Should not be used outside of core Mozilla code. This is a private API for - * the startup and platform integration code to use. Refer to the methods on - * AddonManagerInternal for documentation however note that these methods are - * subject to change at any time. - */ -this.AddonManagerPrivate = { - startup: function() { - AddonManagerInternal.startup(); - }, - - registerProvider: function(aProvider, aTypes) { - AddonManagerInternal.registerProvider(aProvider, aTypes); - }, - - unregisterProvider: function(aProvider) { - AddonManagerInternal.unregisterProvider(aProvider); - }, - - markProviderSafe: function(aProvider) { - AddonManagerInternal.markProviderSafe(aProvider); - }, - - backgroundUpdateCheck: function() { - return AddonManagerInternal.backgroundUpdateCheck(); - }, - - backgroundUpdateTimerHandler() { - // Don't call through to the real update check if no checks are enabled. - let checkHotfix = AddonManagerInternal.hotfixID && - Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && - Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); - - if (!AddonManagerInternal.updateEnabled && !checkHotfix) { - logger.info("Skipping background update check"); - return; - } - // Don't return the promise here, since the caller doesn't care. - AddonManagerInternal.backgroundUpdateCheck(); - }, - - addStartupChange: function(aType, aID) { - AddonManagerInternal.addStartupChange(aType, aID); - }, - - removeStartupChange: function(aType, aID) { - AddonManagerInternal.removeStartupChange(aType, aID); - }, - - notifyAddonChanged: function(aID, aType, aPendingRestart) { - AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart); - }, - - updateAddonAppDisabledStates: function() { - AddonManagerInternal.updateAddonAppDisabledStates(); - }, - - updateAddonRepositoryData: function(aCallback) { - AddonManagerInternal.updateAddonRepositoryData(aCallback); - }, - - callInstallListeners: function(...aArgs) { - return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal, - aArgs); - }, - - callAddonListeners: function(...aArgs) { - AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs); - }, - - AddonAuthor: AddonAuthor, - - AddonScreenshot: AddonScreenshot, - - AddonCompatibilityOverride: AddonCompatibilityOverride, - - AddonType: AddonType, - - recordTimestamp: function(name, value) { - AddonManagerInternal.recordTimestamp(name, value); - }, - - _simpleMeasures: {}, - recordSimpleMeasure: function(name, value) { - this._simpleMeasures[name] = value; - }, - - recordException: function(aModule, aContext, aException) { - let report = { - module: aModule, - context: aContext - }; - - if (typeof aException == "number") { - report.message = Components.Exception("", aException).name; - } - else { - report.message = aException.toString(); - if (aException.fileName) { - report.file = aException.fileName; - report.line = aException.lineNumber; - } - } - - this._simpleMeasures.exception = report; - }, - - getSimpleMeasures: function() { - return this._simpleMeasures; - }, - - getTelemetryDetails: function() { - return AddonManagerInternal.telemetryDetails; - }, - - setTelemetryDetails: function(aProvider, aDetails) { - AddonManagerInternal.telemetryDetails[aProvider] = aDetails; - }, - - // Start a timer, record a simple measure of the time interval when - // timer.done() is called - simpleTimer: function(aName) { - let startTime = Cu.now(); - return { - done: () => this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime)) - }; - }, - - /** - * Helper to call update listeners when no update is available. - * - * This can be used as an implementation for Addon.findUpdates() when - * no update mechanism is available. - */ - callNoUpdateListeners: function(addon, listener, reason, appVersion, platformVersion) { - if ("onNoCompatibilityUpdateAvailable" in listener) { - safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon); - } - if ("onNoUpdateAvailable" in listener) { - safeCall(listener.onNoUpdateAvailable.bind(listener), addon); - } - if ("onUpdateFinished" in listener) { - safeCall(listener.onUpdateFinished.bind(listener), addon); - } - }, - - get webExtensionsMinPlatformVersion() { - return gWebExtensionsMinPlatformVersion; - }, - - hasUpgradeListener: function(aId) { - return AddonManagerInternal.upgradeListeners.has(aId); - }, - - getUpgradeListener: function(aId) { - return AddonManagerInternal.upgradeListeners.get(aId); - }, -}; - -/** - * This is the public API that UI and developers should be calling. All methods - * just forward to AddonManagerInternal. - */ -this.AddonManager = { - // Constants for the AddonInstall.state property - // These will show up as AddonManager.STATE_* (eg, STATE_AVAILABLE) - _states: new Map([ - // The install is available for download. - ["STATE_AVAILABLE", 0], - // The install is being downloaded. - ["STATE_DOWNLOADING", 1], - // The install is checking for compatibility information. - ["STATE_CHECKING", 2], - // The install is downloaded and ready to install. - ["STATE_DOWNLOADED", 3], - // The download failed. - ["STATE_DOWNLOAD_FAILED", 4], - // The install has been postponed. - ["STATE_POSTPONED", 5], - // The add-on is being installed. - ["STATE_INSTALLING", 6], - // The add-on has been installed. - ["STATE_INSTALLED", 7], - // The install failed. - ["STATE_INSTALL_FAILED", 8], - // The install has been cancelled. - ["STATE_CANCELLED", 9], - ]), - - // Constants representing different types of errors while downloading an - // add-on. - // These will show up as AddonManager.ERROR_* (eg, ERROR_NETWORK_FAILURE) - _errors: new Map([ - // The download failed due to network problems. - ["ERROR_NETWORK_FAILURE", -1], - // The downloaded file did not match the provided hash. - ["ERROR_INCORRECT_HASH", -2], - // The downloaded file seems to be corrupted in some way. - ["ERROR_CORRUPT_FILE", -3], - // An error occured trying to write to the filesystem. - ["ERROR_FILE_ACCESS", -4], - // The add-on must be signed and isn't. - ["ERROR_SIGNEDSTATE_REQUIRED", -5], - // The downloaded add-on had a different type than expected. - ["ERROR_UNEXPECTED_ADDON_TYPE", -6], - // The addon did not have the expected ID - ["ERROR_INCORRECT_ID", -7], - ]), - - // These must be kept in sync with AddonUpdateChecker. - // No error was encountered. - UPDATE_STATUS_NO_ERROR: 0, - // The update check timed out - UPDATE_STATUS_TIMEOUT: -1, - // There was an error while downloading the update information. - UPDATE_STATUS_DOWNLOAD_ERROR: -2, - // The update information was malformed in some way. - UPDATE_STATUS_PARSE_ERROR: -3, - // The update information was not in any known format. - UPDATE_STATUS_UNKNOWN_FORMAT: -4, - // The update information was not correctly signed or there was an SSL error. - UPDATE_STATUS_SECURITY_ERROR: -5, - // The update was cancelled. - UPDATE_STATUS_CANCELLED: -6, - - // Constants to indicate why an update check is being performed - // Update check has been requested by the user. - UPDATE_WHEN_USER_REQUESTED: 1, - // Update check is necessary to see if the Addon is compatibile with a new - // version of the application. - UPDATE_WHEN_NEW_APP_DETECTED: 2, - // Update check is necessary because a new application has been installed. - UPDATE_WHEN_NEW_APP_INSTALLED: 3, - // Update check is a regular background update check. - UPDATE_WHEN_PERIODIC_UPDATE: 16, - // Update check is needed to check an Addon that is being installed. - UPDATE_WHEN_ADDON_INSTALLED: 17, - - // Constants for operations in Addon.pendingOperations - // Indicates that the Addon has no pending operations. - PENDING_NONE: 0, - // Indicates that the Addon will be enabled after the application restarts. - PENDING_ENABLE: 1, - // Indicates that the Addon will be disabled after the application restarts. - PENDING_DISABLE: 2, - // Indicates that the Addon will be uninstalled after the application restarts. - PENDING_UNINSTALL: 4, - // Indicates that the Addon will be installed after the application restarts. - PENDING_INSTALL: 8, - PENDING_UPGRADE: 16, - - // Constants for operations in Addon.operationsRequiringRestart - // Indicates that restart isn't required for any operation. - OP_NEEDS_RESTART_NONE: 0, - // Indicates that restart is required for enabling the addon. - OP_NEEDS_RESTART_ENABLE: 1, - // Indicates that restart is required for disabling the addon. - OP_NEEDS_RESTART_DISABLE: 2, - // Indicates that restart is required for uninstalling the addon. - OP_NEEDS_RESTART_UNINSTALL: 4, - // Indicates that restart is required for installing the addon. - OP_NEEDS_RESTART_INSTALL: 8, - - // Constants for permissions in Addon.permissions. - // Indicates that the Addon can be uninstalled. - PERM_CAN_UNINSTALL: 1, - // Indicates that the Addon can be enabled by the user. - PERM_CAN_ENABLE: 2, - // Indicates that the Addon can be disabled by the user. - PERM_CAN_DISABLE: 4, - // Indicates that the Addon can be upgraded. - PERM_CAN_UPGRADE: 8, - // Indicates that the Addon can be set to be optionally enabled - // on a case-by-case basis. - PERM_CAN_ASK_TO_ACTIVATE: 16, - - // General descriptions of where items are installed. - // Installed in this profile. - SCOPE_PROFILE: 1, - // Installed for all of this user's profiles. - SCOPE_USER: 2, - // Installed and owned by the application. - SCOPE_APPLICATION: 4, - // Installed for all users of the computer. - SCOPE_SYSTEM: 8, - // Installed temporarily - SCOPE_TEMPORARY: 16, - // The combination of all scopes. - SCOPE_ALL: 31, - - // Add-on type is expected to be displayed in the UI in a list. - VIEW_TYPE_LIST: "list", - - // Constants describing how add-on types behave. - - // If no add-ons of a type are installed, then the category for that add-on - // type should be hidden in the UI. - TYPE_UI_HIDE_EMPTY: 16, - // Indicates that this add-on type supports the ask-to-activate state. - // That is, add-ons of this type can be set to be optionally enabled - // on a case-by-case basis. - TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32, - // The add-on type natively supports undo for restartless uninstalls. - // If this flag is not specified, the UI is expected to handle this via - // disabling the add-on, and performing the actual uninstall at a later time. - TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL: 64, - - // Constants for Addon.applyBackgroundUpdates. - // Indicates that the Addon should not update automatically. - AUTOUPDATE_DISABLE: 0, - // Indicates that the Addon should update automatically only if - // that's the global default. - AUTOUPDATE_DEFAULT: 1, - // Indicates that the Addon should update automatically. - AUTOUPDATE_ENABLE: 2, - - // Constants for how Addon options should be shown. - // Options will be opened in a new window - OPTIONS_TYPE_DIALOG: 1, - // Options will be displayed within the AM detail view - OPTIONS_TYPE_INLINE: 2, - // Options will be displayed in a new tab, if possible - OPTIONS_TYPE_TAB: 3, - // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown. - // Used to indicate that only non-interactive information will be shown. - OPTIONS_TYPE_INLINE_INFO: 4, - // Similar to OPTIONS_TYPE_INLINE, but rather than generating inline - // options from a specially-formatted XUL file, the contents of the - // file are simply displayed in an inline element. - OPTIONS_TYPE_INLINE_BROWSER: 5, - - // Constants for displayed or hidden options notifications - // Options notification will be displayed - OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed", - // Options notification will be hidden - OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden", - - // Constants for getStartupChanges, addStartupChange and removeStartupChange - // Add-ons that were detected as installed during startup. Doesn't include - // add-ons that were pending installation the last time the application ran. - STARTUP_CHANGE_INSTALLED: "installed", - // Add-ons that were detected as changed during startup. This includes an - // add-on moving to a different location, changing version or just having - // been detected as possibly changed. - STARTUP_CHANGE_CHANGED: "changed", - // Add-ons that were detected as uninstalled during startup. Doesn't include - // add-ons that were pending uninstallation the last time the application ran. - STARTUP_CHANGE_UNINSTALLED: "uninstalled", - // Add-ons that were detected as disabled during startup, normally because of - // an application change making an add-on incompatible. Doesn't include - // add-ons that were pending being disabled the last time the application ran. - STARTUP_CHANGE_DISABLED: "disabled", - // Add-ons that were detected as enabled during startup, normally because of - // an application change making an add-on compatible. Doesn't include - // add-ons that were pending being enabled the last time the application ran. - STARTUP_CHANGE_ENABLED: "enabled", - - // Constants for Addon.signedState. Any states that should cause an add-on - // to be unusable in builds that require signing should have negative values. - // Add-on signing is not required, e.g. because the pref is disabled. - SIGNEDSTATE_NOT_REQUIRED: undefined, - // Add-on is signed but signature verification has failed. - SIGNEDSTATE_BROKEN: -2, - // Add-on may be signed but by an certificate that doesn't chain to our - // our trusted certificate. - SIGNEDSTATE_UNKNOWN: -1, - // Add-on is unsigned. - SIGNEDSTATE_MISSING: 0, - // Add-on is preliminarily reviewed. - SIGNEDSTATE_PRELIMINARY: 1, - // Add-on is fully reviewed. - SIGNEDSTATE_SIGNED: 2, - // Add-on is system add-on. - SIGNEDSTATE_SYSTEM: 3, - - // Constants for the Addon.userDisabled property - // Indicates that the userDisabled state of this add-on is currently - // ask-to-activate. That is, it can be conditionally enabled on a - // case-by-case basis. - STATE_ASK_TO_ACTIVATE: "askToActivate", - - get __AddonManagerInternal__() { - return AppConstants.DEBUG ? AddonManagerInternal : undefined; - }, - - get isReady() { - return gStartupComplete && !gShutdownInProgress; - }, - - init() { - this._stateToString = new Map(); - for (let [name, value] of this._states) { - this[name] = value; - this._stateToString.set(value, name); - } - this._errorToString = new Map(); - for (let [name, value] of this._errors) { - this[name] = value; - this._errorToString.set(value, name); - } - }, - - stateToString(state) { - return this._stateToString.get(state); - }, - - errorToString(err) { - return err ? this._errorToString.get(err) : null; - }, - - getInstallForURL: function(aUrl, aCallback, aMimetype, - aHash, aName, aIcons, - aVersion, aBrowser) { - AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash, - aName, aIcons, aVersion, aBrowser); - }, - - getInstallForFile: function(aFile, aCallback, aMimetype) { - AddonManagerInternal.getInstallForFile(aFile, aCallback, aMimetype); - }, - - /** - * Gets an array of add-on IDs that changed during the most recent startup. - * - * @param aType - * The type of startup change to get - * @return An array of add-on IDs - */ - getStartupChanges: function(aType) { - if (!(aType in AddonManagerInternal.startupChanges)) - return []; - return AddonManagerInternal.startupChanges[aType].slice(0); - }, - - getAddonByID: function(aID, aCallback) { - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - AddonManagerInternal.getAddonByID(aID) - .then(makeSafe(aCallback)) - .catch(logger.error); - }, - - getAddonBySyncGUID: function(aGUID, aCallback) { - AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback); - }, - - getAddonsByIDs: function(aIDs, aCallback) { - if (typeof aCallback != "function") - throw Components.Exception("aCallback must be a function", - Cr.NS_ERROR_INVALID_ARG); - - AddonManagerInternal.getAddonsByIDs(aIDs) - .then(makeSafe(aCallback)) - .catch(logger.error); - }, - - getAddonsWithOperationsByTypes: function(aTypes, aCallback) { - AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes, aCallback); - }, - - getAddonsByTypes: function(aTypes, aCallback) { - AddonManagerInternal.getAddonsByTypes(aTypes, aCallback); - }, - - getAllAddons: function(aCallback) { - AddonManagerInternal.getAllAddons(aCallback); - }, - - getInstallsByTypes: function(aTypes, aCallback) { - AddonManagerInternal.getInstallsByTypes(aTypes, aCallback); - }, - - getAllInstalls: function(aCallback) { - AddonManagerInternal.getAllInstalls(aCallback); - }, - - mapURIToAddonID: function(aURI) { - return AddonManagerInternal.mapURIToAddonID(aURI); - }, - - isInstallEnabled: function(aType) { - return AddonManagerInternal.isInstallEnabled(aType); - }, - - isInstallAllowed: function(aType, aInstallingPrincipal) { - return AddonManagerInternal.isInstallAllowed(aType, aInstallingPrincipal); - }, - - installAddonsFromWebpage: function(aType, aBrowser, aInstallingPrincipal, - aInstalls) { - AddonManagerInternal.installAddonsFromWebpage(aType, aBrowser, - aInstallingPrincipal, - aInstalls); - }, - - installTemporaryAddon: function(aDirectory) { - return AddonManagerInternal.installTemporaryAddon(aDirectory); - }, - - installAddonFromSources: function(aDirectory) { - return AddonManagerInternal.installAddonFromSources(aDirectory); - }, - - getAddonByInstanceID: function(aInstanceID) { - return AddonManagerInternal.getAddonByInstanceID(aInstanceID); - }, - - addManagerListener: function(aListener) { - AddonManagerInternal.addManagerListener(aListener); - }, - - removeManagerListener: function(aListener) { - AddonManagerInternal.removeManagerListener(aListener); - }, - - addInstallListener: function(aListener) { - AddonManagerInternal.addInstallListener(aListener); - }, - - removeInstallListener: function(aListener) { - AddonManagerInternal.removeInstallListener(aListener); - }, - - getUpgradeListener: function(aId) { - return AddonManagerInternal.upgradeListeners.get(aId); - }, - - addUpgradeListener: function(aInstanceID, aCallback) { - AddonManagerInternal.addUpgradeListener(aInstanceID, aCallback); - }, - - removeUpgradeListener: function(aInstanceID) { - AddonManagerInternal.removeUpgradeListener(aInstanceID); - }, - - addAddonListener: function(aListener) { - AddonManagerInternal.addAddonListener(aListener); - }, - - removeAddonListener: function(aListener) { - AddonManagerInternal.removeAddonListener(aListener); - }, - - addTypeListener: function(aListener) { - AddonManagerInternal.addTypeListener(aListener); - }, - - removeTypeListener: function(aListener) { - AddonManagerInternal.removeTypeListener(aListener); - }, - - get addonTypes() { - return AddonManagerInternal.addonTypes; - }, - - /** - * Determines whether an Addon should auto-update or not. - * - * @param aAddon - * The Addon representing the add-on - * @return true if the addon should auto-update, false otherwise. - */ - shouldAutoUpdate: function(aAddon) { - if (!aAddon || typeof aAddon != "object") - throw Components.Exception("aAddon must be specified", - Cr.NS_ERROR_INVALID_ARG); - - if (!("applyBackgroundUpdates" in aAddon)) - return false; - if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE) - return true; - if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE) - return false; - return this.autoUpdateDefault; - }, - - get checkCompatibility() { - return AddonManagerInternal.checkCompatibility; - }, - - set checkCompatibility(aValue) { - AddonManagerInternal.checkCompatibility = aValue; - }, - - get strictCompatibility() { - return AddonManagerInternal.strictCompatibility; - }, - - set strictCompatibility(aValue) { - AddonManagerInternal.strictCompatibility = aValue; - }, - - get checkUpdateSecurityDefault() { - return AddonManagerInternal.checkUpdateSecurityDefault; - }, - - get checkUpdateSecurity() { - return AddonManagerInternal.checkUpdateSecurity; - }, - - set checkUpdateSecurity(aValue) { - AddonManagerInternal.checkUpdateSecurity = aValue; - }, - - get updateEnabled() { - return AddonManagerInternal.updateEnabled; - }, - - set updateEnabled(aValue) { - AddonManagerInternal.updateEnabled = aValue; - }, - - get autoUpdateDefault() { - return AddonManagerInternal.autoUpdateDefault; - }, - - set autoUpdateDefault(aValue) { - AddonManagerInternal.autoUpdateDefault = aValue; - }, - - get hotfixID() { - return AddonManagerInternal.hotfixID; - }, - - escapeAddonURI: function(aAddon, aUri, aAppVersion) { - return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion); - }, - - getPreferredIconURL: function(aAddon, aSize, aWindow = undefined) { - return AddonManagerInternal.getPreferredIconURL(aAddon, aSize, aWindow); - }, - - get webAPI() { - return AddonManagerInternal.webAPI; - }, - - get shutdown() { - return gShutdownBarrier.client; - }, -}; - -this.AddonManager.init(); - -// load the timestamps module into AddonManagerInternal -Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal); -Object.freeze(AddonManagerInternal); -Object.freeze(AddonManagerPrivate); -Object.freeze(AddonManager); diff --git a/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp b/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp deleted file mode 100644 index 3f2a7a529..000000000 --- a/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "AddonManagerWebAPI.h" - -#include "mozilla/dom/Navigator.h" -#include "mozilla/dom/NavigatorBinding.h" - -#include "mozilla/Preferences.h" -#include "nsGlobalWindow.h" - -#include "nsIDocShell.h" -#include "nsIScriptObjectPrincipal.h" - -namespace mozilla { -using namespace mozilla::dom; - -static bool -IsValidHost(const nsACString& host) { - // This is ugly, but Preferences.h doesn't have support - // for default prefs or locked prefs - nsCOMPtr prefService (do_GetService(NS_PREFSERVICE_CONTRACTID)); - nsCOMPtr prefs; - if (prefService) { - prefService->GetDefaultBranch(nullptr, getter_AddRefs(prefs)); - bool isEnabled; - if (NS_SUCCEEDED(prefs->GetBoolPref("xpinstall.enabled", &isEnabled)) && !isEnabled) { - bool isLocked; - prefs->PrefIsLocked("xpinstall.enabled", &isLocked); - if (isLocked) { - return false; - } - } - } - - if (host.Equals("addons.mozilla.org") || - host.Equals("discovery.addons.mozilla.org") || - host.Equals("testpilot.firefox.com")) { - return true; - } - - // When testing allow access to the developer sites. - if (Preferences::GetBool("extensions.webapi.testing", false)) { - if (host.LowerCaseEqualsLiteral("addons.allizom.org") || - host.LowerCaseEqualsLiteral("discovery.addons.allizom.org") || - host.LowerCaseEqualsLiteral("addons-dev.allizom.org") || - host.LowerCaseEqualsLiteral("discovery.addons-dev.allizom.org") || - host.LowerCaseEqualsLiteral("testpilot.stage.mozaws.net") || - host.LowerCaseEqualsLiteral("testpilot.dev.mozaws.net") || - host.LowerCaseEqualsLiteral("example.com")) { - return true; - } - } - - return false; -} - -// Checks if the given uri is secure and matches one of the hosts allowed to -// access the API. -bool -AddonManagerWebAPI::IsValidSite(nsIURI* uri) -{ - if (!uri) { - return false; - } - - bool isSecure; - nsresult rv = uri->SchemeIs("https", &isSecure); - if (NS_FAILED(rv) || !isSecure) { - return false; - } - - nsAutoCString host; - rv = uri->GetHost(host); - if (NS_FAILED(rv)) { - return false; - } - - return IsValidHost(host); -} - -bool -AddonManagerWebAPI::IsAPIEnabled(JSContext* cx, JSObject* obj) -{ - nsGlobalWindow* global = xpc::WindowGlobalOrNull(obj); - if (!global) { - return false; - } - - nsCOMPtr win = global->AsInner(); - if (!win) { - return false; - } - - // Check that the current window and all parent frames are allowed access to - // the API. - while (win) { - nsCOMPtr sop = do_QueryInterface(win); - if (!sop) { - return false; - } - - nsCOMPtr principal = sop->GetPrincipal(); - if (!principal) { - return false; - } - - // Reaching a window with a system principal means we have reached - // privileged UI of some kind so stop at this point and allow access. - if (principal->GetIsSystemPrincipal()) { - return true; - } - - nsCOMPtr docShell = win->GetDocShell(); - if (!docShell) { - // This window has been torn down so don't allow access to the API. - return false; - } - - if (!IsValidSite(win->GetDocumentURI())) { - return false; - } - - // Checks whether there is a parent frame of the same type. This won't cross - // mozbrowser or chrome boundaries. - nsCOMPtr parent; - nsresult rv = docShell->GetSameTypeParent(getter_AddRefs(parent)); - if (NS_FAILED(rv)) { - return false; - } - - if (!parent) { - // No parent means we've hit a mozbrowser or chrome boundary so allow - // access to the API. - return true; - } - - nsIDocument* doc = win->GetDoc(); - if (!doc) { - return false; - } - - doc = doc->GetParentDocument(); - if (!doc) { - // Getting here means something has been torn down so fail safe. - return false; - } - - - win = doc->GetInnerWindow(); - } - - // Found a document with no inner window, don't grant access to the API. - return false; -} - -namespace dom { - -bool -AddonManagerPermissions::IsHostPermitted(const GlobalObject& /*unused*/, const nsAString& host) -{ - return IsValidHost(NS_ConvertUTF16toUTF8(host)); -} - -} // namespace mozilla::dom - - -} // namespace mozilla diff --git a/toolkit/mozapps/extensions/AddonManagerWebAPI.h b/toolkit/mozapps/extensions/AddonManagerWebAPI.h deleted file mode 100644 index 6830bc91f..000000000 --- a/toolkit/mozapps/extensions/AddonManagerWebAPI.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef addonmanagerwebapi_h_ -#define addonmanagerwebapi_h_ - -#include "nsPIDOMWindow.h" - -namespace mozilla { - -class AddonManagerWebAPI { -public: - static bool IsAPIEnabled(JSContext* cx, JSObject* obj); - -private: - static bool IsValidSite(nsIURI* uri); -}; - -namespace dom { - -class AddonManagerPermissions { -public: - static bool IsHostPermitted(const GlobalObject&, const nsAString& host); -}; - -} // namespace mozilla::dom - -} // namespace mozilla - -#endif // addonmanagerwebapi_h_ diff --git a/toolkit/mozapps/extensions/AddonPathService.cpp b/toolkit/mozapps/extensions/AddonPathService.cpp deleted file mode 100644 index 8a405c0ea..000000000 --- a/toolkit/mozapps/extensions/AddonPathService.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "AddonPathService.h" - -#include "amIAddonManager.h" -#include "nsIURI.h" -#include "nsXULAppAPI.h" -#include "jsapi.h" -#include "nsServiceManagerUtils.h" -#include "nsLiteralString.h" -#include "nsThreadUtils.h" -#include "nsIIOService.h" -#include "nsNetUtil.h" -#include "nsIAddonPolicyService.h" -#include "nsIFileURL.h" -#include "nsIResProtocolHandler.h" -#include "nsIChromeRegistry.h" -#include "nsIJARURI.h" -#include "nsJSUtils.h" -#include "mozilla/dom/ScriptSettings.h" -#include "mozilla/dom/ToJSValue.h" -#include "mozilla/AddonPathService.h" -#include "mozilla/Omnijar.h" - -#include - -namespace mozilla { - -struct PathEntryComparator -{ - typedef AddonPathService::PathEntry PathEntry; - - bool Equals(const PathEntry& entry1, const PathEntry& entry2) const - { - return entry1.mPath == entry2.mPath; - } - - bool LessThan(const PathEntry& entry1, const PathEntry& entry2) const - { - return entry1.mPath < entry2.mPath; - } -}; - -AddonPathService::AddonPathService() -{ -} - -AddonPathService::~AddonPathService() -{ - sInstance = nullptr; -} - -NS_IMPL_ISUPPORTS(AddonPathService, amIAddonPathService) - -AddonPathService *AddonPathService::sInstance; - -/* static */ AddonPathService* -AddonPathService::GetInstance() -{ - if (!sInstance) { - sInstance = new AddonPathService(); - } - NS_ADDREF(sInstance); - return sInstance; -} - -static JSAddonId* -ConvertAddonId(const nsAString& addonIdString) -{ - AutoSafeJSContext cx; - JS::RootedValue strv(cx); - if (!mozilla::dom::ToJSValue(cx, addonIdString, &strv)) { - return nullptr; - } - JS::RootedString str(cx, strv.toString()); - return JS::NewAddonId(cx, str); -} - -JSAddonId* -AddonPathService::Find(const nsAString& path) -{ - // Use binary search to find the nearest entry that is <= |path|. - PathEntryComparator comparator; - unsigned index = mPaths.IndexOfFirstElementGt(PathEntry(path, nullptr), comparator); - if (index == 0) { - return nullptr; - } - const PathEntry& entry = mPaths[index - 1]; - - // Return the entry's addon if its path is a prefix of |path|. - if (StringBeginsWith(path, entry.mPath)) { - return entry.mAddonId; - } - return nullptr; -} - -NS_IMETHODIMP -AddonPathService::FindAddonId(const nsAString& path, nsAString& addonIdString) -{ - if (JSAddonId* id = Find(path)) { - JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); - AssignJSFlatString(addonIdString, flat); - } - return NS_OK; -} - -/* static */ JSAddonId* -AddonPathService::FindAddonId(const nsAString& path) -{ - // If no service has been created, then we're not going to find anything. - if (!sInstance) { - return nullptr; - } - - return sInstance->Find(path); -} - -NS_IMETHODIMP -AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdString) -{ - JSAddonId* addonId = ConvertAddonId(addonIdString); - - // Add the new path in sorted order. - PathEntryComparator comparator; - mPaths.InsertElementSorted(PathEntry(path, addonId), comparator); - return NS_OK; -} - -NS_IMETHODIMP -AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString) -{ - if (JSAddonId* id = MapURIToAddonID(aURI)) { - JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); - AssignJSFlatString(addonIdString, flat); - } - return NS_OK; -} - -static nsresult -ResolveURI(nsIURI* aURI, nsAString& out) -{ - bool equals; - nsresult rv; - nsCOMPtr uri; - nsAutoCString spec; - - // Resolve resource:// URIs. At the end of this if/else block, we - // have both spec and uri variables identifying the same URI. - if (NS_SUCCEEDED(aURI->SchemeIs("resource", &equals)) && equals) { - nsCOMPtr ioService = do_GetIOService(&rv); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - nsCOMPtr ph; - rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - nsCOMPtr irph(do_QueryInterface(ph, &rv)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - rv = irph->ResolveURI(aURI, spec); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - } else if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &equals)) && equals) { - nsCOMPtr chromeReg = - mozilla::services::GetChromeRegistryService(); - if (NS_WARN_IF(!chromeReg)) - return NS_ERROR_UNEXPECTED; - - rv = chromeReg->ConvertChromeURL(aURI, getter_AddRefs(uri)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - } else { - uri = aURI; - } - - if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) { - nsCOMPtr jarURI = do_QueryInterface(uri, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - nsCOMPtr jarFileURI; - rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - return ResolveURI(jarFileURI, out); - } - - if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) { - nsCOMPtr baseFileURL = do_QueryInterface(uri, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - nsCOMPtr file; - rv = baseFileURL->GetFile(getter_AddRefs(file)); - if (NS_WARN_IF(NS_FAILED(rv))) - return rv; - - return file->GetPath(out); - } - return NS_ERROR_FAILURE; -} - -JSAddonId* -MapURIToAddonID(nsIURI* aURI) -{ - if (!NS_IsMainThread() || !XRE_IsParentProcess()) { - return nullptr; - } - - bool equals; - nsresult rv; - if (NS_SUCCEEDED(aURI->SchemeIs("moz-extension", &equals)) && equals) { - nsCOMPtr service = do_GetService("@mozilla.org/addons/policy-service;1"); - if (service) { - nsString addonId; - rv = service->ExtensionURIToAddonId(aURI, addonId); - if (NS_FAILED(rv)) - return nullptr; - - return ConvertAddonId(addonId); - } - } - - nsAutoString filePath; - rv = ResolveURI(aURI, filePath); - if (NS_FAILED(rv)) - return nullptr; - - nsCOMPtr greJar = Omnijar::GetPath(Omnijar::GRE); - nsCOMPtr appJar = Omnijar::GetPath(Omnijar::APP); - if (greJar && appJar) { - nsAutoString greJarString, appJarString; - if (NS_FAILED(greJar->GetPath(greJarString)) || NS_FAILED(appJar->GetPath(appJarString))) - return nullptr; - - // If |aURI| is part of either Omnijar, then it can't be part of an - // add-on. This catches pretty much all URLs for Firefox content. - if (filePath.Equals(greJarString) || filePath.Equals(appJarString)) - return nullptr; - } - - // If it's not part of Firefox, we resort to binary searching through the - // add-on paths. - return AddonPathService::FindAddonId(filePath); -} - -} // namespace mozilla diff --git a/toolkit/mozapps/extensions/AddonPathService.h b/toolkit/mozapps/extensions/AddonPathService.h deleted file mode 100644 index f739b018f..000000000 --- a/toolkit/mozapps/extensions/AddonPathService.h +++ /dev/null @@ -1,55 +0,0 @@ -//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef AddonPathService_h -#define AddonPathService_h - -#include "amIAddonPathService.h" -#include "nsString.h" -#include "nsTArray.h" - -class nsIURI; -class JSAddonId; - -namespace mozilla { - -JSAddonId* -MapURIToAddonID(nsIURI* aURI); - -class AddonPathService final : public amIAddonPathService -{ -public: - AddonPathService(); - - static AddonPathService* GetInstance(); - - JSAddonId* Find(const nsAString& path); - static JSAddonId* FindAddonId(const nsAString& path); - - NS_DECL_ISUPPORTS - NS_DECL_AMIADDONPATHSERVICE - - struct PathEntry - { - nsString mPath; - JSAddonId* mAddonId; - - PathEntry(const nsAString& aPath, JSAddonId* aAddonId) - : mPath(aPath), mAddonId(aAddonId) - {} - }; - -private: - virtual ~AddonPathService(); - - // Paths are stored sorted in order of their mPath. - nsTArray mPaths; - - static AddonPathService* sInstance; -}; - -} // namespace mozilla - -#endif diff --git a/toolkit/mozapps/extensions/ChromeManifestParser.jsm b/toolkit/mozapps/extensions/ChromeManifestParser.jsm deleted file mode 100644 index 63f1db785..000000000 --- a/toolkit/mozapps/extensions/ChromeManifestParser.jsm +++ /dev/null @@ -1,157 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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.EXPORTED_SYMBOLS = ["ChromeManifestParser"]; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); - -const MSG_JAR_FLUSH = "AddonJarFlush"; - - -/** - * Sends local and remote notifications to flush a JAR file cache entry - * - * @param aJarFile - * The ZIP/XPI/JAR file as a nsIFile - */ -function flushJarCache(aJarFile) { - Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null); - Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster) - .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path); -} - - -/** - * Parses chrome manifest files. - */ -this.ChromeManifestParser = { - - /** - * Reads and parses a chrome manifest file located at a specified URI, and all - * secondary manifests it references. - * - * @param aURI - * A nsIURI pointing to a chrome manifest. - * Typically a file: or jar: URI. - * @return Array of objects describing each manifest instruction, in the form: - * { type: instruction-type, baseURI: string-uri, args: [arguments] } - **/ - parseSync: function(aURI) { - function parseLine(aLine) { - let line = aLine.trim(); - if (line.length == 0 || line.charAt(0) == '#') - return; - let tokens = line.split(/\s+/); - let type = tokens.shift(); - if (type == "manifest") { - let uri = NetUtil.newURI(tokens.shift(), null, aURI); - data = data.concat(this.parseSync(uri)); - } else { - data.push({type: type, baseURI: baseURI, args: tokens}); - } - } - - let contents = ""; - try { - if (aURI.scheme == "jar") - contents = this._readFromJar(aURI); - else - contents = this._readFromFile(aURI); - } catch (e) { - // Silently fail. - } - - if (!contents) - return []; - - let baseURI = NetUtil.newURI(".", null, aURI).spec; - - let data = []; - let lines = contents.split("\n"); - lines.forEach(parseLine.bind(this)); - return data; - }, - - _readFromJar: function(aURI) { - let data = ""; - let entries = []; - let readers = []; - - try { - // Deconstrict URI, which can be nested jar: URIs. - let uri = aURI.clone(); - while (uri instanceof Ci.nsIJARURI) { - entries.push(uri.JAREntry); - uri = uri.JARFile; - } - - // Open the base jar. - let reader = Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(Ci.nsIZipReader); - reader.open(uri.QueryInterface(Ci.nsIFileURL).file); - readers.push(reader); - - // Open the nested jars. - for (let i = entries.length - 1; i > 0; i--) { - let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(Ci.nsIZipReader); - innerReader.openInner(reader, entries[i]); - readers.push(innerReader); - reader = innerReader; - } - - // First entry is the actual file we want to read. - let zis = reader.getInputStream(entries[0]); - data = NetUtil.readInputStreamToString(zis, zis.available()); - } - finally { - // Close readers in reverse order. - for (let i = readers.length - 1; i >= 0; i--) { - readers[i].close(); - flushJarCache(readers[i].file); - } - } - - return data; - }, - - _readFromFile: function(aURI) { - let file = aURI.QueryInterface(Ci.nsIFileURL).file; - if (!file.exists() || !file.isFile()) - return ""; - - let data = ""; - let fis = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - try { - fis.init(file, -1, -1, false); - data = NetUtil.readInputStreamToString(fis, fis.available()); - } finally { - fis.close(); - } - return data; - }, - - /** - * Detects if there were any instructions of a specified type in a given - * chrome manifest. - * - * @param aManifest - * Manifest data, as returned by ChromeManifestParser.parseSync(). - * @param aType - * Instruction type to filter by. - * @return True if any matching instructions were found in the manifest. - */ - hasType: function(aManifest, aType) { - return aManifest.some(entry => entry.type == aType); - } -}; diff --git a/toolkit/mozapps/extensions/DeferredSave.jsm b/toolkit/mozapps/extensions/DeferredSave.jsm deleted file mode 100644 index 89f82b265..000000000 --- a/toolkit/mozapps/extensions/DeferredSave.jsm +++ /dev/null @@ -1,275 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 = Components.utils; -const Cc = Components.classes; -const Ci = Components.interfaces; - -Cu.import("resource://gre/modules/osfile.jsm"); -/* globals OS*/ -Cu.import("resource://gre/modules/Promise.jsm"); - -// Make it possible to mock out timers for testing -var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - -this.EXPORTED_SYMBOLS = ["DeferredSave"]; - -// If delay parameter is not provided, default is 50 milliseconds. -const DEFAULT_SAVE_DELAY_MS = 50; - -Cu.import("resource://gre/modules/Log.jsm"); -// Configure a logger at the parent 'DeferredSave' level to format -// messages for all the modules under DeferredSave.* -const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave"; -var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID); -parentLogger.level = Log.Level.Warn; -var formatter = new Log.BasicFormatter(); -// Set parent logger (and its children) to append to -// the Javascript section of the Browser Console -parentLogger.addAppender(new Log.ConsoleAppender(formatter)); -// Set parent logger (and its children) to -// also append to standard out -parentLogger.addAppender(new Log.DumpAppender(formatter)); - -// Provide the ability to enable/disable logging -// messages at runtime. -// If the "extensions.logging.enabled" preference is -// missing or 'false', messages at the WARNING and higher -// severity should be logged to the JS console and standard error. -// If "extensions.logging.enabled" is set to 'true', messages -// at DEBUG and higher should go to JS console and standard error. -Cu.import("resource://gre/modules/Services.jsm"); - -const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; -const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; - -/** -* Preference listener which listens for a change in the -* "extensions.logging.enabled" preference and changes the logging level of the -* parent 'addons' level logger accordingly. -*/ -var PrefObserver = { - init: function() { - Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); - Services.obs.addObserver(this, "xpcom-shutdown", false); - this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); - }, - - observe: function(aSubject, aTopic, aData) { - if (aTopic == "xpcom-shutdown") { - Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); - Services.obs.removeObserver(this, "xpcom-shutdown"); - } - else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { - let debugLogEnabled = false; - try { - debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); - } - catch (e) { - } - if (debugLogEnabled) { - parentLogger.level = Log.Level.Debug; - } - else { - parentLogger.level = Log.Level.Warn; - } - } - } -}; - -PrefObserver.init(); - -/** - * A module to manage deferred, asynchronous writing of data files - * to disk. Writing is deferred by waiting for a specified delay after - * a request to save the data, before beginning to write. If more than - * one save request is received during the delay, all requests are - * fulfilled by a single write. - * - * @constructor - * @param aPath - * String representing the full path of the file where the data - * is to be written. - * @param aDataProvider - * Callback function that takes no argument and returns the data to - * be written. If aDataProvider returns an ArrayBufferView, the - * bytes it contains are written to the file as is. - * If aDataProvider returns a String the data are UTF-8 encoded - * and then written to the file. - * @param [optional] aDelay - * The delay in milliseconds between the first saveChanges() call - * that marks the data as needing to be saved, and when the DeferredSave - * begins writing the data to disk. Default 50 milliseconds. - */ -this.DeferredSave = function(aPath, aDataProvider, aDelay) { - // Create a new logger (child of 'DeferredSave' logger) - // for use by this particular instance of DeferredSave object - let leafName = OS.Path.basename(aPath); - let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName; - this.logger = Log.repository.getLogger(logger_id); - - // @type {Deferred|null}, null when no data needs to be written - // @resolves with the result of OS.File.writeAtomic when all writes complete - // @rejects with the error from OS.File.writeAtomic if the write fails, - // or with the error from aDataProvider() if that throws. - this._pending = null; - - // @type {Promise}, completes when the in-progress write (if any) completes, - // kept as a resolved promise at other times to simplify logic. - // Because _deferredSave() always uses _writing.then() to execute - // its next action, we don't need a special case for whether a write - // is in progress - if the previous write is complete (and the _writing - // promise is already resolved/rejected), _writing.then() starts - // the next action immediately. - // - // @resolves with the result of OS.File.writeAtomic - // @rejects with the error from OS.File.writeAtomic - this._writing = Promise.resolve(0); - - // Are we currently waiting for a write to complete - this.writeInProgress = false; - - this._path = aPath; - this._dataProvider = aDataProvider; - - this._timer = null; - - // Some counters for telemetry - // The total number of times the file was written - this.totalSaves = 0; - - // The number of times the data became dirty while - // another save was in progress - this.overlappedSaves = 0; - - // Error returned by the most recent write (if any) - this._lastError = null; - - if (aDelay && (aDelay > 0)) - this._delay = aDelay; - else - this._delay = DEFAULT_SAVE_DELAY_MS; -} - -this.DeferredSave.prototype = { - get dirty() { - return this._pending || this.writeInProgress; - }, - - get lastError() { - return this._lastError; - }, - - // Start the pending timer if data is dirty - _startTimer: function() { - if (!this._pending) { - return; - } - - this.logger.debug("Starting timer"); - if (!this._timer) - this._timer = MakeTimer(); - this._timer.initWithCallback(() => this._deferredSave(), - this._delay, Ci.nsITimer.TYPE_ONE_SHOT); - }, - - /** - * Mark the current stored data dirty, and schedule a flush to disk - * @return A Promise that will be resolved after the data is written to disk; - * the promise is resolved with the number of bytes written. - */ - saveChanges: function() { - this.logger.debug("Save changes"); - if (!this._pending) { - if (this.writeInProgress) { - this.logger.debug("Data changed while write in progress"); - this.overlappedSaves++; - } - this._pending = Promise.defer(); - // Wait until the most recent write completes or fails (if it hasn't already) - // and then restart our timer - this._writing.then(count => this._startTimer(), error => this._startTimer()); - } - return this._pending.promise; - }, - - _deferredSave: function() { - let pending = this._pending; - this._pending = null; - let writing = this._writing; - this._writing = pending.promise; - - // In either the success or the exception handling case, we don't need to handle - // the error from _writing here; it's already being handled in another then() - let toSave = null; - try { - toSave = this._dataProvider(); - } - catch (e) { - this.logger.error("Deferred save dataProvider failed", e); - writing.then(null, error => {}) - .then(count => { - pending.reject(e); - }); - return; - } - - writing.then(null, error => { return 0; }) - .then(count => { - this.logger.debug("Starting write"); - this.totalSaves++; - this.writeInProgress = true; - - OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"}) - .then( - result => { - this._lastError = null; - this.writeInProgress = false; - this.logger.debug("Write succeeded"); - pending.resolve(result); - }, - error => { - this._lastError = error; - this.writeInProgress = false; - this.logger.warn("Write failed", error); - pending.reject(error); - }); - }); - }, - - /** - * Immediately save the dirty data to disk, skipping - * the delay of normal operation. Note that the write - * still happens asynchronously in the worker - * thread from OS.File. - * - * There are four possible situations: - * 1) Nothing to flush - * 2) Data is not currently being written, in-memory copy is dirty - * 3) Data is currently being written, in-memory copy is clean - * 4) Data is being written and in-memory copy is dirty - * - * @return Promise that will resolve when all in-memory data - * has finished being flushed, returning the number of bytes - * written. If all in-memory data is clean, completes with the - * result of the most recent write. - */ - flush: function() { - // If we have pending changes, cancel our timer and set up the write - // immediately (_deferredSave queues the write for after the most - // recent write completes, if it hasn't already) - if (this._pending) { - this.logger.debug("Flush called while data is dirty"); - if (this._timer) { - this._timer.cancel(); - this._timer = null; - } - this._deferredSave(); - } - - return this._writing; - } -}; diff --git a/toolkit/mozapps/extensions/LightweightThemeManager.jsm b/toolkit/mozapps/extensions/LightweightThemeManager.jsm deleted file mode 100644 index 5dd41831d..000000000 --- a/toolkit/mozapps/extensions/LightweightThemeManager.jsm +++ /dev/null @@ -1,909 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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.EXPORTED_SYMBOLS = ["LightweightThemeManager"]; - -const Cc = Components.classes; -const Ci = Components.interfaces; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/AddonManager.jsm"); -/* globals AddonManagerPrivate*/ -Components.utils.import("resource://gre/modules/Services.jsm"); - -const ID_SUFFIX = "@personas.mozilla.org"; -const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect"; -const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; -const PREF_EM_DSS_ENABLED = "extensions.dss.enabled"; -const ADDON_TYPE = "theme"; - -const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; - -const STRING_TYPE_NAME = "type.%ID%.name"; - -const DEFAULT_MAX_USED_THEMES_COUNT = 30; - -const MAX_PREVIEW_SECONDS = 30; - -const MANDATORY = ["id", "name", "headerURL"]; -const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL", - "previewURL", "author", "description", "homepageURL", - "updateURL", "version"]; - -const PERSIST_ENABLED = true; -const PERSIST_BYPASS_CACHE = false; -const PERSIST_FILES = { - headerURL: "lightweighttheme-header", - footerURL: "lightweighttheme-footer" -}; - -XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer", - "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest", - "resource://gre/modules/ServiceRequest.jsm"); - - -XPCOMUtils.defineLazyGetter(this, "_prefs", () => { - return Services.prefs.getBranch("lightweightThemes."); -}); - -Object.defineProperty(this, "_maxUsedThemes", { - get: function() { - delete this._maxUsedThemes; - try { - this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes"); - } - catch (e) { - this._maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; - } - return this._maxUsedThemes; - }, - - set: function(val) { - delete this._maxUsedThemes; - return this._maxUsedThemes = val; - }, - configurable: true, -}); - -// Holds the ID of the theme being enabled or disabled while sending out the -// events so cached AddonWrapper instances can return correct values for -// permissions and pendingOperations -var _themeIDBeingEnabled = null; -var _themeIDBeingDisabled = null; - -// Convert from the old storage format (in which the order of usedThemes -// was combined with isThemeSelected to determine which theme was selected) -// to the new one (where a selectedThemeID determines which theme is selected). -(function() { - let wasThemeSelected = false; - try { - wasThemeSelected = _prefs.getBoolPref("isThemeSelected"); - } catch (e) { } - - if (wasThemeSelected) { - _prefs.clearUserPref("isThemeSelected"); - let themes = []; - try { - themes = JSON.parse(_prefs.getComplexValue("usedThemes", - Ci.nsISupportsString).data); - } catch (e) { } - - if (Array.isArray(themes) && themes[0]) { - _prefs.setCharPref("selectedThemeID", themes[0].id); - } - } -})(); - -this.LightweightThemeManager = { - get name() { - return "LightweightThemeManager"; - }, - - // Themes that can be added for an application. They can't be removed, and - // will always show up at the top of the list. - _builtInThemes: new Map(), - - get usedThemes () { - let themes = []; - try { - themes = JSON.parse(_prefs.getComplexValue("usedThemes", - Ci.nsISupportsString).data); - } catch (e) { } - - themes.push(...this._builtInThemes.values()); - return themes; - }, - - get currentTheme () { - let selectedThemeID = null; - try { - selectedThemeID = _prefs.getCharPref("selectedThemeID"); - } catch (e) {} - - let data = null; - if (selectedThemeID) { - data = this.getUsedTheme(selectedThemeID); - } - return data; - }, - - get currentThemeForDisplay () { - var data = this.currentTheme; - - if (data && PERSIST_ENABLED) { - for (let key in PERSIST_FILES) { - try { - if (data[key] && _prefs.getBoolPref("persisted." + key)) - data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec - + "?" + data.id + ";" + _version(data); - } catch (e) {} - } - } - - return data; - }, - - set currentTheme (aData) { - return _setCurrentTheme(aData, false); - }, - - setLocalTheme: function(aData) { - _setCurrentTheme(aData, true); - }, - - getUsedTheme: function(aId) { - var usedThemes = this.usedThemes; - for (let usedTheme of usedThemes) { - if (usedTheme.id == aId) - return usedTheme; - } - return null; - }, - - forgetUsedTheme: function(aId) { - let theme = this.getUsedTheme(aId); - if (!theme || LightweightThemeManager._builtInThemes.has(theme.id)) - return; - - let wrapper = new AddonWrapper(theme); - AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); - - var currentTheme = this.currentTheme; - if (currentTheme && currentTheme.id == aId) { - this.themeChanged(null); - AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false); - } - - _updateUsedThemes(_usedThemesExceptId(aId)); - AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); - }, - - addBuiltInTheme: function(theme) { - if (!theme || !theme.id || this.usedThemes.some(t => t.id == theme.id)) { - throw new Error("Trying to add invalid builtIn theme"); - } - - this._builtInThemes.set(theme.id, theme); - - if (_prefs.getCharPref("selectedThemeID") == theme.id) { - this.currentTheme = theme; - } - }, - - forgetBuiltInTheme: function(id) { - if (!this._builtInThemes.has(id)) { - let currentTheme = this.currentTheme; - if (currentTheme && currentTheme.id == id) { - this.currentTheme = null; - } - } - return this._builtInThemes.delete(id); - }, - - clearBuiltInThemes: function() { - for (let id of this._builtInThemes.keys()) { - this.forgetBuiltInTheme(id); - } - }, - - previewTheme: function(aData) { - let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); - cancel.data = false; - Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested", - JSON.stringify(aData)); - if (cancel.data) - return; - - if (_previewTimer) - _previewTimer.cancel(); - else - _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - _previewTimer.initWithCallback(_previewTimerCallback, - MAX_PREVIEW_SECONDS * 1000, - _previewTimer.TYPE_ONE_SHOT); - - _notifyWindows(aData); - }, - - resetPreview: function() { - if (_previewTimer) { - _previewTimer.cancel(); - _previewTimer = null; - _notifyWindows(this.currentThemeForDisplay); - } - }, - - parseTheme: function(aString, aBaseURI) { - try { - return _sanitizeTheme(JSON.parse(aString), aBaseURI, false); - } catch (e) { - return null; - } - }, - - updateCurrentTheme: function() { - try { - if (!_prefs.getBoolPref("update.enabled")) - return; - } catch (e) { - return; - } - - var theme = this.currentTheme; - if (!theme || !theme.updateURL) - return; - - var req = new ServiceRequest(); - - req.mozBackgroundRequest = true; - req.overrideMimeType("text/plain"); - req.open("GET", theme.updateURL, true); - // Prevent the request from reading from the cache. - req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; - // Prevent the request from writing to the cache. - req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - - req.addEventListener("load", () => { - if (req.status != 200) - return; - - let newData = this.parseTheme(req.responseText, theme.updateURL); - if (!newData || - newData.id != theme.id || - _version(newData) == _version(theme)) - return; - - var currentTheme = this.currentTheme; - if (currentTheme && currentTheme.id == theme.id) - this.currentTheme = newData; - }, false); - - req.send(null); - }, - - /** - * Switches to a new lightweight theme. - * - * @param aData - * The lightweight theme to switch to - */ - themeChanged: function(aData) { - if (_previewTimer) { - _previewTimer.cancel(); - _previewTimer = null; - } - - if (aData) { - let usedThemes = _usedThemesExceptId(aData.id); - usedThemes.unshift(aData); - _updateUsedThemes(usedThemes); - if (PERSIST_ENABLED) { - LightweightThemeImageOptimizer.purge(); - _persistImages(aData, function() { - _notifyWindows(this.currentThemeForDisplay); - }.bind(this)); - } - } - - if (aData) - _prefs.setCharPref("selectedThemeID", aData.id); - else - _prefs.setCharPref("selectedThemeID", ""); - - _notifyWindows(aData); - Services.obs.notifyObservers(null, "lightweight-theme-changed", null); - }, - - /** - * Starts the Addons provider and enables the new lightweight theme if - * necessary. - */ - startup: function() { - if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) { - let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - if (id) - this.themeChanged(this.getUsedTheme(id)); - else - this.themeChanged(null); - Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); - } - - _prefs.addObserver("", _prefObserver, false); - }, - - /** - * Shuts down the provider. - */ - shutdown: function() { - _prefs.removeObserver("", _prefObserver); - }, - - /** - * Called when a new add-on has been enabled when only one add-on of that type - * can be enabled. - * - * @param aId - * The ID of the newly enabled add-on - * @param aType - * The type of the newly enabled add-on - * @param aPendingRestart - * true if the newly enabled add-on will only become enabled after a - * restart - */ - addonChanged: function(aId, aType, aPendingRestart) { - if (aType != ADDON_TYPE) - return; - - let id = _getInternalID(aId); - let current = this.currentTheme; - - try { - let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - if (id == next && aPendingRestart) - return; - - Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); - if (next) { - AddonManagerPrivate.callAddonListeners("onOperationCancelled", - new AddonWrapper(this.getUsedTheme(next))); - } - else if (id == current.id) { - AddonManagerPrivate.callAddonListeners("onOperationCancelled", - new AddonWrapper(current)); - return; - } - } - catch (e) { - } - - if (current) { - if (current.id == id) - return; - _themeIDBeingDisabled = current.id; - let wrapper = new AddonWrapper(current); - if (aPendingRestart) { - Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, ""); - AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true); - } - else { - AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false); - this.themeChanged(null); - AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); - } - _themeIDBeingDisabled = null; - } - - if (id) { - let theme = this.getUsedTheme(id); - _themeIDBeingEnabled = id; - let wrapper = new AddonWrapper(theme); - if (aPendingRestart) { - AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true); - Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id); - - // Flush the preferences to disk so they survive any crash - Services.prefs.savePrefFile(null); - } - else { - AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false); - this.themeChanged(theme); - AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); - } - _themeIDBeingEnabled = null; - } - }, - - /** - * Called to get an Addon with a particular ID. - * - * @param aId - * The ID of the add-on to retrieve - * @param aCallback - * A callback to pass the Addon to - */ - getAddonByID: function(aId, aCallback) { - let id = _getInternalID(aId); - if (!id) { - aCallback(null); - return; - } - - let theme = this.getUsedTheme(id); - if (!theme) { - aCallback(null); - return; - } - - aCallback(new AddonWrapper(theme)); - }, - - /** - * Called to get Addons of a particular type. - * - * @param aTypes - * An array of types to fetch. Can be null to get all types. - * @param aCallback - * A callback to pass an array of Addons to - */ - getAddonsByTypes: function(aTypes, aCallback) { - if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) { - aCallback([]); - return; - } - - aCallback(this.usedThemes.map(a => new AddonWrapper(a))); - }, -}; - -const wrapperMap = new WeakMap(); -let themeFor = wrapper => wrapperMap.get(wrapper); - -/** - * The AddonWrapper wraps lightweight theme to provide the data visible to - * consumers of the AddonManager API. - */ -function AddonWrapper(aTheme) { - wrapperMap.set(this, aTheme); -} - -AddonWrapper.prototype = { - get id() { - return themeFor(this).id + ID_SUFFIX; - }, - - get type() { - return ADDON_TYPE; - }, - - get isActive() { - let current = LightweightThemeManager.currentTheme; - if (current) - return themeFor(this).id == current.id; - return false; - }, - - get name() { - return themeFor(this).name; - }, - - get version() { - let theme = themeFor(this); - return "version" in theme ? theme.version : ""; - }, - - get creator() { - let theme = themeFor(this); - return "author" in theme ? new AddonManagerPrivate.AddonAuthor(theme.author) : null; - }, - - get screenshots() { - let url = themeFor(this).previewURL; - return [new AddonManagerPrivate.AddonScreenshot(url)]; - }, - - get pendingOperations() { - let pending = AddonManager.PENDING_NONE; - if (this.isActive == this.userDisabled) - pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE; - return pending; - }, - - get operationsRequiringRestart() { - // If a non-default theme is in use then a restart will be required to - // enable lightweight themes unless dynamic theme switching is enabled - if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) { - try { - if (Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED)) - return AddonManager.OP_NEEDS_RESTART_NONE; - } - catch (e) { - } - return AddonManager.OP_NEEDS_RESTART_ENABLE; - } - - return AddonManager.OP_NEEDS_RESTART_NONE; - }, - - get size() { - // The size changes depending on whether the theme is in use or not, this is - // probably not worth exposing. - return null; - }, - - get permissions() { - let permissions = 0; - - // Do not allow uninstall of builtIn themes. - if (!LightweightThemeManager._builtInThemes.has(themeFor(this).id)) - permissions = AddonManager.PERM_CAN_UNINSTALL; - if (this.userDisabled) - permissions |= AddonManager.PERM_CAN_ENABLE; - else - permissions |= AddonManager.PERM_CAN_DISABLE; - return permissions; - }, - - get userDisabled() { - let id = themeFor(this).id; - if (_themeIDBeingEnabled == id) - return false; - if (_themeIDBeingDisabled == id) - return true; - - try { - let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); - return id != toSelect; - } - catch (e) { - let current = LightweightThemeManager.currentTheme; - return !current || current.id != id; - } - }, - - set userDisabled(val) { - if (val == this.userDisabled) - return val; - - if (val) - LightweightThemeManager.currentTheme = null; - else - LightweightThemeManager.currentTheme = themeFor(this); - - return val; - }, - - // Lightweight themes are never disabled by the application - get appDisabled() { - return false; - }, - - // Lightweight themes are always compatible - get isCompatible() { - return true; - }, - - get isPlatformCompatible() { - return true; - }, - - get scope() { - return AddonManager.SCOPE_PROFILE; - }, - - get foreignInstall() { - return false; - }, - - uninstall: function() { - LightweightThemeManager.forgetUsedTheme(themeFor(this).id); - }, - - cancelUninstall: function() { - throw new Error("Theme is not marked to be uninstalled"); - }, - - findUpdates: function(listener, reason, appVersion, platformVersion) { - AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion); - }, - - // Lightweight themes are always compatible - isCompatibleWith: function(appVersion, platformVersion) { - return true; - }, - - // Lightweight themes are always securely updated - get providesUpdatesSecurely() { - return true; - }, - - // Lightweight themes are never blocklisted - get blocklistState() { - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - } -}; - -["description", "homepageURL", "iconURL"].forEach(function(prop) { - Object.defineProperty(AddonWrapper.prototype, prop, { - get: function() { - let theme = themeFor(this); - return prop in theme ? theme[prop] : null; - }, - enumarable: true, - }); -}); - -["installDate", "updateDate"].forEach(function(prop) { - Object.defineProperty(AddonWrapper.prototype, prop, { - get: function() { - let theme = themeFor(this); - return prop in theme ? new Date(theme[prop]) : null; - }, - enumarable: true, - }); -}); - -/** - * Converts the ID used by the public AddonManager API to an lightweight theme - * ID. - * - * @param id - * The ID to be converted - * - * @return the lightweight theme ID or null if the ID was not for a lightweight - * theme. - */ -function _getInternalID(id) { - if (!id) - return null; - let len = id.length - ID_SUFFIX.length; - if (len > 0 && id.substring(len) == ID_SUFFIX) - return id.substring(0, len); - return null; -} - -function _setCurrentTheme(aData, aLocal) { - aData = _sanitizeTheme(aData, null, aLocal); - - let needsRestart = (ADDON_TYPE == "theme") && - Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN); - - let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); - cancel.data = false; - Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested", - JSON.stringify(aData)); - - if (aData) { - let theme = LightweightThemeManager.getUsedTheme(aData.id); - let isInstall = !theme || theme.version != aData.version; - if (isInstall) { - aData.updateDate = Date.now(); - if (theme && "installDate" in theme) - aData.installDate = theme.installDate; - else - aData.installDate = aData.updateDate; - - var oldWrapper = theme ? new AddonWrapper(theme) : null; - var wrapper = new AddonWrapper(aData); - AddonManagerPrivate.callInstallListeners("onExternalInstall", null, - wrapper, oldWrapper, false); - AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false); - } - - let current = LightweightThemeManager.currentTheme; - let usedThemes = _usedThemesExceptId(aData.id); - if (current && current.id != aData.id) - usedThemes.splice(1, 0, aData); - else - usedThemes.unshift(aData); - _updateUsedThemes(usedThemes); - - if (isInstall) - AddonManagerPrivate.callAddonListeners("onInstalled", wrapper); - } - - if (cancel.data) - return null; - - AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null, - ADDON_TYPE, needsRestart); - - return LightweightThemeManager.currentTheme; -} - -function _sanitizeTheme(aData, aBaseURI, aLocal) { - if (!aData || typeof aData != "object") - return null; - - var resourceProtocols = ["http", "https", "resource"]; - if (aLocal) - resourceProtocols.push("file"); - var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):"); - - function sanitizeProperty(prop) { - if (!(prop in aData)) - return null; - if (typeof aData[prop] != "string") - return null; - let val = aData[prop].trim(); - if (!val) - return null; - - if (!/URL$/.test(prop)) - return val; - - try { - val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec; - if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val)) - return val; - return null; - } - catch (e) { - return null; - } - } - - let result = {}; - for (let mandatoryProperty of MANDATORY) { - let val = sanitizeProperty(mandatoryProperty); - if (!val) - throw Components.results.NS_ERROR_INVALID_ARG; - result[mandatoryProperty] = val; - } - - for (let optionalProperty of OPTIONAL) { - let val = sanitizeProperty(optionalProperty); - if (!val) - continue; - result[optionalProperty] = val; - } - - return result; -} - -function _usedThemesExceptId(aId) { - return LightweightThemeManager.usedThemes.filter(function(t) { - return "id" in t && t.id != aId; - }); -} - -function _version(aThemeData) { - return aThemeData.version || ""; -} - -function _makeURI(aURL, aBaseURI) { - return Services.io.newURI(aURL, null, aBaseURI); -} - -function _updateUsedThemes(aList) { - // Remove app-specific themes before saving them to the usedThemes pref. - aList = aList.filter(theme => !LightweightThemeManager._builtInThemes.has(theme.id)); - - // Send uninstall events for all themes that need to be removed. - while (aList.length > _maxUsedThemes) { - let wrapper = new AddonWrapper(aList[aList.length - 1]); - AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); - aList.pop(); - AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); - } - - var str = Cc["@mozilla.org/supports-string;1"] - .createInstance(Ci.nsISupportsString); - str.data = JSON.stringify(aList); - _prefs.setComplexValue("usedThemes", Ci.nsISupportsString, str); - - Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null); -} - -function _notifyWindows(aThemeData) { - Services.obs.notifyObservers(null, "lightweight-theme-styling-update", - JSON.stringify(aThemeData)); -} - -var _previewTimer; -var _previewTimerCallback = { - notify: function() { - LightweightThemeManager.resetPreview(); - } -}; - -/** - * Called when any of the lightweightThemes preferences are changed. - */ -function _prefObserver(aSubject, aTopic, aData) { - switch (aData) { - case "maxUsedThemes": - try { - _maxUsedThemes = _prefs.getIntPref(aData); - } - catch (e) { - _maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; - } - // Update the theme list to remove any themes over the number we keep - _updateUsedThemes(LightweightThemeManager.usedThemes); - break; - } -} - -function _persistImages(aData, aCallback) { - function onSuccess(key) { - return function () { - let current = LightweightThemeManager.currentTheme; - if (current && current.id == aData.id) { - _prefs.setBoolPref("persisted." + key, true); - } - if (--numFilesToPersist == 0 && aCallback) { - aCallback(); - } - }; - } - - let numFilesToPersist = 0; - for (let key in PERSIST_FILES) { - _prefs.setBoolPref("persisted." + key, false); - if (aData[key]) { - numFilesToPersist++; - _persistImage(aData[key], PERSIST_FILES[key], onSuccess(key)); - } - } -} - -function _getLocalImageURI(localFileName) { - var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile); - localFile.append(localFileName); - return Services.io.newFileURI(localFile); -} - -function _persistImage(sourceURL, localFileName, successCallback) { - if (/^(file|resource):/.test(sourceURL)) - return; - - var targetURI = _getLocalImageURI(localFileName); - var sourceURI = _makeURI(sourceURL); - - var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(Ci.nsIWebBrowserPersist); - - persist.persistFlags = - Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | - Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION | - (PERSIST_BYPASS_CACHE ? - Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE : - Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE); - - persist.progressListener = new _persistProgressListener(successCallback); - - persist.saveURI(sourceURI, null, - null, Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE, - null, null, targetURI, null); -} - -function _persistProgressListener(successCallback) { - this.onLocationChange = function() {}; - this.onProgressChange = function() {}; - this.onStatusChange = function() {}; - this.onSecurityChange = function() {}; - this.onStateChange = function(aWebProgress, aRequest, aStateFlags, aStatus) { - if (aRequest && - aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && - aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { - try { - if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) { - // success - successCallback(); - return; - } - } catch (e) { } - // failure - } - }; -} - -AddonManagerPrivate.registerProvider(LightweightThemeManager, [ - new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS, - STRING_TYPE_NAME, - AddonManager.VIEW_TYPE_LIST, 5000) -]); diff --git a/toolkit/mozapps/extensions/addonManager.js b/toolkit/mozapps/extensions/addonManager.js deleted file mode 100644 index d34cbaf62..000000000 --- a/toolkit/mozapps/extensions/addonManager.js +++ /dev/null @@ -1,296 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 component serves as integration between the platform and AddonManager. - * It is responsible for initializing and shutting down the AddonManager as well - * as passing new installs from webpages to the AddonManager. - */ - -"use strict"; - -const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -// The old XPInstall error codes -const EXECUTION_ERROR = -203; -const CANT_READ_ARCHIVE = -207; -const USER_CANCELLED = -210; -const DOWNLOAD_ERROR = -228; -const UNSUPPORTED_TYPE = -244; -const SUCCESS = 0; - -const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; -const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; -const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; - -const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; -const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; -const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; -const MSG_INSTALL_CLEANUP = "WebAPICleanup"; -const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; -const MSG_ADDON_EVENT = "WebAPIAddonEvent"; - -const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js"; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -var gSingleton = null; - -function amManager() { - Cu.import("resource://gre/modules/AddonManager.jsm"); - /* globals AddonManagerPrivate*/ - - Services.mm.loadFrameScript(CHILD_SCRIPT, true); - Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this); - Services.mm.addMessageListener(MSG_INSTALL_ADDONS, this); - Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this); - Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this); - Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this); - - Services.obs.addObserver(this, "message-manager-close", false); - Services.obs.addObserver(this, "message-manager-disconnect", false); - - AddonManager.webAPI.setEventHandler(this.sendEvent); - - // Needed so receiveMessage can be called directly by JS callers - this.wrappedJSObject = this; -} - -amManager.prototype = { - observe: function(aSubject, aTopic, aData) { - switch (aTopic) { - case "addons-startup": - AddonManagerPrivate.startup(); - break; - - case "message-manager-close": - case "message-manager-disconnect": - this.childClosed(aSubject); - break; - } - }, - - /** - * @see amIAddonManager.idl - */ - mapURIToAddonID: function(uri, id) { - id.value = AddonManager.mapURIToAddonID(uri); - return !!id.value; - }, - - /** - * @see amIWebInstaller.idl - */ - isInstallEnabled: function(aMimetype, aReferer) { - return AddonManager.isInstallEnabled(aMimetype); - }, - - /** - * @see amIWebInstaller.idl - */ - installAddonsFromWebpage: function(aMimetype, aBrowser, aInstallingPrincipal, - aUris, aHashes, aNames, aIcons, aCallback) { - if (aUris.length == 0) - return false; - - let retval = true; - if (!AddonManager.isInstallAllowed(aMimetype, aInstallingPrincipal)) { - aCallback = null; - retval = false; - } - - let installs = []; - function buildNextInstall() { - if (aUris.length == 0) { - AddonManager.installAddonsFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, installs); - return; - } - let uri = aUris.shift(); - AddonManager.getInstallForURL(uri, function(aInstall) { - function callCallback(aUri, aStatus) { - try { - aCallback.onInstallEnded(aUri, aStatus); - } - catch (e) { - Components.utils.reportError(e); - } - } - - if (aInstall) { - installs.push(aInstall); - if (aCallback) { - aInstall.addListener({ - onDownloadCancelled: function(aInstall) { - callCallback(uri, USER_CANCELLED); - }, - - onDownloadFailed: function(aInstall) { - if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE) - callCallback(uri, CANT_READ_ARCHIVE); - else - callCallback(uri, DOWNLOAD_ERROR); - }, - - onInstallFailed: function(aInstall) { - callCallback(uri, EXECUTION_ERROR); - }, - - onInstallEnded: function(aInstall, aStatus) { - callCallback(uri, SUCCESS); - } - }); - } - } - else if (aCallback) { - aCallback.onInstallEnded(uri, UNSUPPORTED_TYPE); - } - buildNextInstall(); - }, aMimetype, aHashes.shift(), aNames.shift(), aIcons.shift(), null, aBrowser); - } - buildNextInstall(); - - return retval; - }, - - notify: function(aTimer) { - AddonManagerPrivate.backgroundUpdateTimerHandler(); - }, - - // Maps message manager instances for content processes to the associated - // AddonListener instances. - addonListeners: new Map(), - - _addAddonListener(target) { - if (!this.addonListeners.has(target)) { - let handler = (event, id, needsRestart) => { - target.sendAsyncMessage(MSG_ADDON_EVENT, {event, id, needsRestart}); - }; - let listener = { - onEnabling: (addon, needsRestart) => handler("onEnabling", addon.id, needsRestart), - onEnabled: (addon) => handler("onEnabled", addon.id, false), - onDisabling: (addon, needsRestart) => handler("onDisabling", addon.id, needsRestart), - onDisabled: (addon) => handler("onDisabled", addon.id, false), - onInstalling: (addon, needsRestart) => handler("onInstalling", addon.id, needsRestart), - onInstalled: (addon) => handler("onInstalled", addon.id, false), - onUninstalling: (addon, needsRestart) => handler("onUninstalling", addon.id, needsRestart), - onUninstalled: (addon) => handler("onUninstalled", addon.id, false), - onOperationCancelled: (addon) => handler("onOperationCancelled", addon.id, false), - }; - this.addonListeners.set(target, listener); - AddonManager.addAddonListener(listener); - } - }, - - _removeAddonListener(target) { - if (this.addonListeners.has(target)) { - AddonManager.removeAddonListener(this.addonListeners.get(target)); - this.addonListeners.delete(target); - } - }, - - /** - * messageManager callback function. - * - * Listens to requests from child processes for InstallTrigger - * activity, and sends back callbacks. - */ - receiveMessage: function(aMessage) { - let payload = aMessage.data; - - switch (aMessage.name) { - case MSG_INSTALL_ENABLED: - return AddonManager.isInstallEnabled(payload.mimetype); - - case MSG_INSTALL_ADDONS: { - let callback = null; - if (payload.callbackID != -1) { - let mm = aMessage.target.messageManager; - callback = { - onInstallEnded: function(url, status) { - mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, { - callbackID: payload.callbackID, - url: url, - status: status - }); - }, - }; - } - - return this.installAddonsFromWebpage(payload.mimetype, - aMessage.target, payload.triggeringPrincipal, payload.uris, - payload.hashes, payload.names, payload.icons, callback); - } - - case MSG_PROMISE_REQUEST: { - let mm = aMessage.target.messageManager; - let resolve = (value) => { - mm.sendAsyncMessage(MSG_PROMISE_RESULT, { - callbackID: payload.callbackID, - resolve: value - }); - } - let reject = (value) => { - mm.sendAsyncMessage(MSG_PROMISE_RESULT, { - callbackID: payload.callbackID, - reject: value - }); - } - - let API = AddonManager.webAPI; - if (payload.type in API) { - API[payload.type](aMessage.target, ...payload.args).then(resolve, reject); - } - else { - reject("Unknown Add-on API request."); - } - break; - } - - case MSG_INSTALL_CLEANUP: { - AddonManager.webAPI.clearInstalls(payload.ids); - break; - } - - case MSG_ADDON_EVENT_REQ: { - let target = aMessage.target.messageManager; - if (payload.enabled) { - this._addAddonListener(target); - } else { - this._removeAddonListener(target); - } - } - } - return undefined; - }, - - childClosed(target) { - AddonManager.webAPI.clearInstallsFrom(target); - this._removeAddonListener(target); - }, - - sendEvent(mm, data) { - mm.sendAsyncMessage(MSG_INSTALL_EVENT, data); - }, - - classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"), - _xpcom_factory: { - createInstance: function(aOuter, aIid) { - if (aOuter != null) - throw Components.Exception("Component does not support aggregation", - Cr.NS_ERROR_NO_AGGREGATION); - - if (!gSingleton) - gSingleton = new amManager(); - return gSingleton.QueryInterface(aIid); - } - }, - QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager, - Ci.amIWebInstaller, - Ci.nsITimerCallback, - Ci.nsIObserver, - Ci.nsIMessageListener]) -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]); diff --git a/toolkit/mozapps/extensions/amContentHandler.js b/toolkit/mozapps/extensions/amContentHandler.js deleted file mode 100644 index 8dc4dfecd..000000000 --- a/toolkit/mozapps/extensions/amContentHandler.js +++ /dev/null @@ -1,100 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; - -const XPI_CONTENT_TYPE = "application/x-xpinstall"; -const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); - -function amContentHandler() { -} - -amContentHandler.prototype = { - /** - * Handles a new request for an application/x-xpinstall file. - * - * @param aMimetype - * The mimetype of the file - * @param aContext - * The context passed to nsIChannel.asyncOpen - * @param aRequest - * The nsIRequest dealing with the content - */ - handleContent: function(aMimetype, aContext, aRequest) { - if (aMimetype != XPI_CONTENT_TYPE) - throw Cr.NS_ERROR_WONT_HANDLE_CONTENT; - - if (!(aRequest instanceof Ci.nsIChannel)) - throw Cr.NS_ERROR_WONT_HANDLE_CONTENT; - - let uri = aRequest.URI; - - let window = null; - let callbacks = aRequest.notificationCallbacks ? - aRequest.notificationCallbacks : - aRequest.loadGroup.notificationCallbacks; - if (callbacks) - window = callbacks.getInterface(Ci.nsIDOMWindow); - - aRequest.cancel(Cr.NS_BINDING_ABORTED); - - let installs = { - uris: [uri.spec], - hashes: [null], - names: [null], - icons: [null], - mimetype: XPI_CONTENT_TYPE, - triggeringPrincipal: aRequest.loadInfo.triggeringPrincipal, - callbackID: -1 - }; - - if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { - // When running in the main process this might be a frame inside an - // in-content UI page, walk up to find the first frame element in a chrome - // privileged document - let element = window.frameElement; - let ssm = Services.scriptSecurityManager; - while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal)) - element = element.ownerDocument.defaultView.frameElement; - - if (element) { - let listener = Cc["@mozilla.org/addons/integration;1"]. - getService(Ci.nsIMessageListener); - listener.wrappedJSObject.receiveMessage({ - name: MSG_INSTALL_ADDONS, - target: element, - data: installs, - }); - return; - } - } - - // Fall back to sending through the message manager - let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIContentFrameMessageManager); - - messageManager.sendAsyncMessage(MSG_INSTALL_ADDONS, installs); - }, - - classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]), - - log : function(aMsg) { - let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg); - Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService). - logStringMessage(msg); - dump(msg + "\n"); - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amContentHandler]); diff --git a/toolkit/mozapps/extensions/amIAddonManager.idl b/toolkit/mozapps/extensions/amIAddonManager.idl deleted file mode 100644 index 58a58b62d..000000000 --- a/toolkit/mozapps/extensions/amIAddonManager.idl +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIURI; - -/** - * A service to make some AddonManager functionality available to C++ callers. - * Javascript callers should still use AddonManager.jsm directly. - */ -[scriptable, function, uuid(7b45d82d-7ad5-48d7-9b05-f32eb9818cd4)] -interface amIAddonManager : nsISupports -{ - /** - * Synchronously map a URI to the corresponding Addon ID. - * - * Mappable URIs are limited to in-application resources belonging to the - * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. - * but do not include URIs from meta data, such as the add-on homepage. - * - * @param aURI - * The nsIURI to map - * @return - * true if the URI has been mapped successfully to an Addon ID - */ - boolean mapURIToAddonID(in nsIURI aURI, out AUTF8String aID); -}; diff --git a/toolkit/mozapps/extensions/amIAddonPathService.idl b/toolkit/mozapps/extensions/amIAddonPathService.idl deleted file mode 100644 index 9c9197a61..000000000 --- a/toolkit/mozapps/extensions/amIAddonPathService.idl +++ /dev/null @@ -1,37 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIURI; - -/** - * This service maps file system paths where add-ons reside to the ID - * of the add-on. Paths are added by the add-on manager. They can - * looked up by anyone. - */ -[scriptable, uuid(fcd9e270-dfb1-11e3-8b68-0800200c9a66)] -interface amIAddonPathService : nsISupports -{ - /** - * Given a path to a file, return the ID of the add-on that the file belongs - * to. Returns an empty string if there is no add-on there. Note that if an - * add-on is located at /a/b/c, then looking up the path /a/b/c/d will return - * that add-on. - */ - AString findAddonId(in AString path); - - /** - * Call this function to inform the service that the given file system path is - * associated with the given add-on ID. - */ - void insertPath(in AString path, in AString addonId); - - /** - * Given a URI to a file, return the ID of the add-on that the file belongs - * to. Returns an empty string if there is no add-on there. - */ - AString mapURIToAddonId(in nsIURI aURI); -}; diff --git a/toolkit/mozapps/extensions/amIWebInstallListener.idl b/toolkit/mozapps/extensions/amIWebInstallListener.idl deleted file mode 100644 index eed108097..000000000 --- a/toolkit/mozapps/extensions/amIWebInstallListener.idl +++ /dev/null @@ -1,134 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIDOMElement; -interface nsIURI; -interface nsIVariant; - -/** - * amIWebInstallInfo is used by the default implementation of - * amIWebInstallListener to communicate with the running application and allow - * it to warn the user about blocked installs and start the installs running. - */ -[scriptable, uuid(fa0b47a3-f819-47ac-bc66-4bd1d7f67b1d)] -interface amIWebInstallInfo : nsISupports -{ - readonly attribute nsIDOMElement browser; - readonly attribute nsIURI originatingURI; - readonly attribute nsIVariant installs; - - /** - * Starts all installs. - */ - void install(); -}; - -/** - * The registered amIWebInstallListener is used to notify about new installs - * triggered by websites. The default implementation displays a confirmation - * dialog when add-ons are ready to install and uses the observer service to - * notify when installations are blocked. - */ -[scriptable, uuid(d9240d4b-6b3a-4cad-b402-de6c93337e0c)] -interface amIWebInstallListener : nsISupports -{ - /** - * Called when installation by websites is currently disabled. - * - * @param aBrowser - * The browser that triggered the installs - * @param aUri - * The URI of the site that triggered the installs - * @param aInstalls - * The AddonInstalls that were blocked - * @param aCount - * The number of AddonInstalls - */ - void onWebInstallDisabled(in nsIDOMElement aBrowser, in nsIURI aUri, - [array, size_is(aCount)] in nsIVariant aInstalls, - [optional] in uint32_t aCount); - - /** - * Called when the website is not allowed to directly prompt the user to - * install add-ons. - * - * @param aBrowser - * The browser that triggered the installs - * @param aUri - * The URI of the site that triggered the installs - * @param aInstalls - * The AddonInstalls that were blocked - * @param aCount - * The number of AddonInstalls - * @return true if the caller should start the installs - */ - boolean onWebInstallBlocked(in nsIDOMElement aBrowser, in nsIURI aUri, - [array, size_is(aCount)] in nsIVariant aInstalls, - [optional] in uint32_t aCount); - - /** - * Called when a website wants to ask the user to install add-ons. - * - * @param aBrowser - * The browser that triggered the installs - * @param aUri - * The URI of the site that triggered the installs - * @param aInstalls - * The AddonInstalls that were requested - * @param aCount - * The number of AddonInstalls - * @return true if the caller should start the installs - */ - boolean onWebInstallRequested(in nsIDOMElement aBrowser, in nsIURI aUri, - [array, size_is(aCount)] in nsIVariant aInstalls, - [optional] in uint32_t aCount); -}; - -[scriptable, uuid(a80b89ad-bb1a-4c43-9cb7-3ae656556f78)] -interface amIWebInstallListener2 : nsISupports -{ - /** - * Called when a non-same-origin resource attempted to initiate an install. - * Installs will have already been cancelled and cannot be restarted. - * - * @param aBrowser - * The browser that triggered the installs - * @param aUri - * The URI of the site that triggered the installs - * @param aInstalls - * The AddonInstalls that were blocked - * @param aCount - * The number of AddonInstalls - */ - boolean onWebInstallOriginBlocked(in nsIDOMElement aBrowser, in nsIURI aUri, - [array, size_is(aCount)] in nsIVariant aInstalls, - [optional] in uint32_t aCount); -}; - -/** - * amIWebInstallPrompt is used, if available, by the default implementation of - * amIWebInstallInfo to display a confirmation UI to the user before running - * installs. - */ -[scriptable, uuid(386906f1-4d18-45bf-bc81-5dcd68e42c3b)] -interface amIWebInstallPrompt : nsISupports -{ - /** - * Get a confirmation that the user wants to start the installs. - * - * @param aBrowser - * The browser that triggered the installs - * @param aUri - * The URI of the site that triggered the installs - * @param aInstalls - * The AddonInstalls that were requested - * @param aCount - * The number of AddonInstalls - */ - void confirm(in nsIDOMElement aBrowser, in nsIURI aUri, - [array, size_is(aCount)] in nsIVariant aInstalls, - [optional] in uint32_t aCount); -}; diff --git a/toolkit/mozapps/extensions/amIWebInstaller.idl b/toolkit/mozapps/extensions/amIWebInstaller.idl deleted file mode 100644 index 6c5ebca67..000000000 --- a/toolkit/mozapps/extensions/amIWebInstaller.idl +++ /dev/null @@ -1,82 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIDOMElement; -interface nsIVariant; -interface nsIURI; - -/** - * A callback function used to notify webpages when a requested install has - * ended. - * - * NOTE: This is *not* the same as InstallListener. - */ -[scriptable, function, uuid(bb22f5c0-3ca1-48f6-873c-54e87987700f)] -interface amIInstallCallback : nsISupports -{ - /** - * Called when an install completes or fails. - * - * @param aUrl - * The url of the add-on being installed - * @param aStatus - * 0 if the install was successful or negative if not - */ - void onInstallEnded(in AString aUrl, in int32_t aStatus); -}; - - -/** - * This interface is used to allow webpages to start installing add-ons. - */ -[scriptable, uuid(658d6c09-15e0-4688-bee8-8551030472a9)] -interface amIWebInstaller : nsISupports -{ - /** - * Checks if installation is enabled for a webpage. - * - * @param aMimetype - * The mimetype for the add-on to be installed - * @param referer - * The URL of the webpage trying to install an add-on - * @return true if installation is enabled - */ - boolean isInstallEnabled(in AString aMimetype, in nsIURI aReferer); - - /** - * Installs an array of add-ons at the request of a webpage - * - * @param aMimetype - * The mimetype for the add-ons - * @param aBrowser - * The browser installing the add-ons. - * @param aReferer - * The URI for the webpage installing the add-ons - * @param aUris - * The URIs of add-ons to be installed - * @param aHashes - * The hashes for the add-ons to be installed - * @param aNames - * The names for the add-ons to be installed - * @param aIcons - * The icons for the add-ons to be installed - * @param aCallback - * An optional callback to notify about installation success and - * failure - * @param aInstallCount - * An optional argument including the number of add-ons to install - * @return true if the installation was successfully started - */ - boolean installAddonsFromWebpage(in AString aMimetype, - in nsIDOMElement aBrowser, - in nsIURI aReferer, - [array, size_is(aInstallCount)] in wstring aUris, - [array, size_is(aInstallCount)] in wstring aHashes, - [array, size_is(aInstallCount)] in wstring aNames, - [array, size_is(aInstallCount)] in wstring aIcons, - [optional] in amIInstallCallback aCallback, - [optional] in uint32_t aInstallCount); -}; diff --git a/toolkit/mozapps/extensions/amInstallTrigger.js b/toolkit/mozapps/extensions/amInstallTrigger.js deleted file mode 100644 index 382791d32..000000000 --- a/toolkit/mozapps/extensions/amInstallTrigger.js +++ /dev/null @@ -1,240 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 {classes: Cc, interfaces: Ci, utils: Cu} = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); - -const XPINSTALL_MIMETYPE = "application/x-xpinstall"; - -const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; -const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; -const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; - - -var log = Log.repository.getLogger("AddonManager.InstallTrigger"); -log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"]; - -function CallbackObject(id, callback, urls, mediator) { - this.id = id; - this.callback = callback; - this.urls = new Set(urls); - this.callCallback = function(url, status) { - try { - this.callback(url, status); - } - catch (e) { - log.warn("InstallTrigger callback threw an exception: " + e); - } - - this.urls.delete(url); - if (this.urls.size == 0) - mediator._callbacks.delete(id); - }; -} - -function RemoteMediator(window) { - window.QueryInterface(Ci.nsIInterfaceRequestor); - let utils = window.getInterface(Ci.nsIDOMWindowUtils); - this._windowID = utils.currentInnerWindowID; - - this.mm = window - .getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIContentFrameMessageManager); - this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this); - - this._lastCallbackID = 0; - this._callbacks = new Map(); -} - -RemoteMediator.prototype = { - receiveMessage: function(message) { - if (message.name == MSG_INSTALL_CALLBACK) { - let payload = message.data; - let callbackHandler = this._callbacks.get(payload.callbackID); - if (callbackHandler) { - callbackHandler.callCallback(payload.url, payload.status); - } - } - }, - - enabled: function(url) { - let params = { - mimetype: XPINSTALL_MIMETYPE - }; - return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0]; - }, - - install: function(installs, principal, callback, window) { - let callbackID = this._addCallback(callback, installs.uris); - - installs.mimetype = XPINSTALL_MIMETYPE; - installs.triggeringPrincipal = principal; - installs.callbackID = callbackID; - - if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { - // When running in the main process this might be a frame inside an - // in-content UI page, walk up to find the first frame element in a chrome - // privileged document - let element = window.frameElement; - let ssm = Services.scriptSecurityManager; - while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal)) - element = element.ownerDocument.defaultView.frameElement; - - if (element) { - let listener = Cc["@mozilla.org/addons/integration;1"]. - getService(Ci.nsIMessageListener); - return listener.wrappedJSObject.receiveMessage({ - name: MSG_INSTALL_ADDONS, - target: element, - data: installs, - }); - } - } - - // Fall back to sending through the message manager - let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIContentFrameMessageManager); - - return messageManager.sendSyncMessage(MSG_INSTALL_ADDONS, installs)[0]; - }, - - _addCallback: function(callback, urls) { - if (!callback || typeof callback != "function") - return -1; - - let callbackID = this._windowID + "-" + ++this._lastCallbackID; - let callbackObject = new CallbackObject(callbackID, callback, urls, this); - this._callbacks.set(callbackID, callbackObject); - return callbackID; - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]) -}; - - -function InstallTrigger() { -} - -InstallTrigger.prototype = { - // Here be magic. We've declared ourselves as providing the - // nsIDOMGlobalPropertyInitializer interface, and are registered in the - // "JavaScript-global-property" category in the XPCOM category manager. This - // means that for newly created windows, XPCOM will createinstance this - // object, and then call init, passing in the window for which we need to - // provide an instance. We then initialize ourselves and return the webidl - // version of this object using the webidl-provided _create method, which - // XPCOM will then duly expose as a property value on the window. All this - // indirection is necessary because webidl does not (yet) support statics - // (bug 863952). See bug 926712 for more details about this implementation. - init: function(window) { - this._window = window; - this._principal = window.document.nodePrincipal; - this._url = window.document.documentURIObject; - - try { - this._mediator = new RemoteMediator(window); - } catch (ex) { - // If we can't set up IPC (e.g., because this is a top-level window - // or something), then don't expose InstallTrigger. - return null; - } - - return window.InstallTriggerImpl._create(window, this); - }, - - enabled: function() { - return this._mediator.enabled(this._url.spec); - }, - - updateEnabled: function() { - return this.enabled(); - }, - - install: function(installs, callback) { - let installData = { - uris: [], - hashes: [], - names: [], - icons: [], - }; - - for (let name of Object.keys(installs)) { - let item = installs[name]; - if (typeof item === "string") { - item = { URL: item }; - } - if (!item.URL) { - throw new this._window.Error("Missing URL property for '" + name + "'"); - } - - let url = this._resolveURL(item.URL); - if (!this._checkLoadURIFromScript(url)) { - throw new this._window.Error("Insufficient permissions to install: " + url.spec); - } - - let iconUrl = null; - if (item.IconURL) { - iconUrl = this._resolveURL(item.IconURL); - if (!this._checkLoadURIFromScript(iconUrl)) { - iconUrl = null; // If page can't load the icon, just ignore it - } - } - - installData.uris.push(url.spec); - installData.hashes.push(item.Hash || null); - installData.names.push(name); - installData.icons.push(iconUrl ? iconUrl.spec : null); - } - - return this._mediator.install(installData, this._principal, callback, this._window); - }, - - startSoftwareUpdate: function(url, flags) { - let filename = Services.io.newURI(url, null, null) - .QueryInterface(Ci.nsIURL) - .filename; - let args = {}; - args[filename] = { "URL": url }; - return this.install(args); - }, - - installChrome: function(type, url, skin) { - return this.startSoftwareUpdate(url); - }, - - _resolveURL: function(url) { - return Services.io.newURI(url, null, this._url); - }, - - _checkLoadURIFromScript: function(uri) { - let secman = Services.scriptSecurityManager; - try { - secman.checkLoadURIWithPrincipal(this._principal, - uri, - secman.DISALLOW_INHERIT_PRINCIPAL); - return true; - } - catch (e) { - return false; - } - }, - - classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"), - contractID: "@mozilla.org/addons/installtrigger;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer]) -}; - - - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]); diff --git a/toolkit/mozapps/extensions/amWebAPI.js b/toolkit/mozapps/extensions/amWebAPI.js deleted file mode 100644 index 5ad0d23f1..000000000 --- a/toolkit/mozapps/extensions/amWebAPI.js +++ /dev/null @@ -1,269 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); - -const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; -const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; -const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; -const MSG_INSTALL_CLEANUP = "WebAPICleanup"; -const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; -const MSG_ADDON_EVENT = "WebAPIAddonEvent"; - -class APIBroker { - constructor(mm) { - this.mm = mm; - - this._promises = new Map(); - - // _installMap maps integer ids to DOM AddonInstall instances - this._installMap = new Map(); - - this.mm.addMessageListener(MSG_PROMISE_RESULT, this); - this.mm.addMessageListener(MSG_INSTALL_EVENT, this); - - this._eventListener = null; - } - - receiveMessage(message) { - let payload = message.data; - - switch (message.name) { - case MSG_PROMISE_RESULT: { - if (!this._promises.has(payload.callbackID)) { - return; - } - - let resolve = this._promises.get(payload.callbackID); - this._promises.delete(payload.callbackID); - resolve(payload); - break; - } - - case MSG_INSTALL_EVENT: { - let install = this._installMap.get(payload.id); - if (!install) { - let err = new Error(`Got install event for unknown install ${payload.id}`); - Cu.reportError(err); - return; - } - install._dispatch(payload); - break; - } - - case MSG_ADDON_EVENT: { - if (this._eventListener) { - this._eventListener(payload); - } - } - } - } - - sendRequest(type, ...args) { - return new Promise(resolve => { - let callbackID = APIBroker._nextID++; - - this._promises.set(callbackID, resolve); - this.mm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args }); - }); - } - - setAddonListener(callback) { - this._eventListener = callback; - if (callback) { - this.mm.addMessageListener(MSG_ADDON_EVENT, this); - this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: true}); - } else { - this.mm.removeMessageListener(MSG_ADDON_EVENT, this); - this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: false}); - } - } - - sendCleanup(ids) { - this.setAddonListener(null); - this.mm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids }); - } -} - -APIBroker._nextID = 0; - -// Base class for building classes to back content-exposed interfaces. -class APIObject { - init(window, broker, properties) { - this.window = window; - this.broker = broker; - - // Copy any provided properties onto this object, webidl bindings - // will only expose to content what should be exposed. - for (let key of Object.keys(properties)) { - this[key] = properties[key]; - } - } - - /** - * Helper to implement an asychronous method visible to content, where - * the method is implemented by sending a message to the parent process - * and then wrapping the returned object or error in an appropriate object. - * This helper method ensures that: - * - Returned Promise objects are from the content window - * - Rejected Promises have Error objects from the content window - * - Only non-internal errors are exposed to the caller - * - * @param {string} apiRequest The command to invoke in the parent process. - * @param {array} apiArgs The arguments to include with the - * request to the parent process. - * @param {function} resultConvert If provided, a function called with the - * result from the parent process as an - * argument. Used to convert the result - * into something appropriate for content. - * @returns {Promise} A Promise suitable for passing directly to content. - */ - _apiTask(apiRequest, apiArgs, resultConverter) { - let win = this.window; - let broker = this.broker; - return new win.Promise((resolve, reject) => { - Task.spawn(function*() { - let result = yield broker.sendRequest(apiRequest, ...apiArgs); - if ("reject" in result) { - let err = new win.Error(result.reject.message); - // We don't currently put any other properties onto Errors - // generated by mozAddonManager. If/when we do, they will - // need to get copied here. - reject(err); - return; - } - - let obj = result.resolve; - if (resultConverter) { - obj = resultConverter(obj); - } - resolve(obj); - }).catch(err => { - Cu.reportError(err); - reject(new win.Error("Unexpected internal error")); - }); - }); - } -} - -class Addon extends APIObject { - constructor(...args) { - super(); - this.init(...args); - } - - uninstall() { - return this._apiTask("addonUninstall", [this.id]); - } - - setEnabled(value) { - return this._apiTask("addonSetEnabled", [this.id, value]); - } -} - -class AddonInstall extends APIObject { - constructor(window, broker, properties) { - super(); - this.init(window, broker, properties); - - broker._installMap.set(properties.id, this); - } - - _dispatch(data) { - // The message for the event includes updated copies of all install - // properties. Use the usual "let webidl filter visible properties" trick. - for (let key of Object.keys(data)) { - this[key] = data[key]; - } - - let event = new this.window.Event(data.event); - this.__DOM_IMPL__.dispatchEvent(event); - } - - install() { - return this._apiTask("addonInstallDoInstall", [this.id]); - } - - cancel() { - return this._apiTask("addonInstallCancel", [this.id]); - } -} - -class WebAPI extends APIObject { - constructor() { - super(); - this.allInstalls = []; - this.listenerCount = 0; - } - - init(window) { - let mm = window - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIContentFrameMessageManager); - let broker = new APIBroker(mm); - - super.init(window, broker, {}); - - window.addEventListener("unload", event => { - this.broker.sendCleanup(this.allInstalls); - }); - } - - getAddonByID(id) { - return this._apiTask("getAddonByID", [id], addonInfo => { - if (!addonInfo) { - return null; - } - let addon = new Addon(this.window, this.broker, addonInfo); - return this.window.Addon._create(this.window, addon); - }); - } - - createInstall(options) { - return this._apiTask("createInstall", [options], installInfo => { - if (!installInfo) { - return null; - } - let install = new AddonInstall(this.window, this.broker, installInfo); - this.allInstalls.push(installInfo.id); - return this.window.AddonInstall._create(this.window, install); - }); - } - - eventListenerWasAdded(type) { - if (this.listenerCount == 0) { - this.broker.setAddonListener(data => { - let event = new this.window.AddonEvent(data.event, data); - this.__DOM_IMPL__.dispatchEvent(event); - }); - } - this.listenerCount++; - } - - eventListenerWasRemoved(type) { - this.listenerCount--; - if (this.listenerCount == 0) { - this.broker.setAddonListener(null); - } - } - - QueryInterface(iid) { - if (iid.equals(WebAPI.classID) || iid.equals(Ci.nsISupports) - || iid.equals(Ci.nsIDOMGlobalPropertyInitializer)) { - return this; - } - return Cr.NS_ERROR_NO_INTERFACE; - } -} - -WebAPI.prototype.classID = Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"); -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebAPI]); diff --git a/toolkit/mozapps/extensions/amWebInstallListener.js b/toolkit/mozapps/extensions/amWebInstallListener.js deleted file mode 100644 index 0bcc345e8..000000000 --- a/toolkit/mozapps/extensions/amWebInstallListener.js +++ /dev/null @@ -1,348 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This is a default implementation of amIWebInstallListener that should work - * for most applications but can be overriden. It notifies the observer service - * about blocked installs. For normal installs it pops up an install - * confirmation when all the add-ons have been downloaded. - */ - -"use strict"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/AddonManager.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils", "resource://gre/modules/SharedPromptUtils.jsm"); - -const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; - -// Installation can begin from any of these states -const READY_STATES = [ - AddonManager.STATE_AVAILABLE, - AddonManager.STATE_DOWNLOAD_FAILED, - AddonManager.STATE_INSTALL_FAILED, - AddonManager.STATE_CANCELLED -]; - -Cu.import("resource://gre/modules/Log.jsm"); -const LOGGER_ID = "addons.weblistener"; - -// Create a new logger for use by the Addons Web Listener -// (Requires AddonManager.jsm) -var logger = Log.repository.getLogger(LOGGER_ID); - -function notifyObservers(aTopic, aBrowser, aUri, aInstalls) { - let info = { - browser: aBrowser, - originatingURI: aUri, - installs: aInstalls, - - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) - }; - Services.obs.notifyObservers(info, aTopic, null); -} - -/** - * Creates a new installer to monitor downloads and prompt to install when - * ready - * - * @param aBrowser - * The browser that started the installations - * @param aUrl - * The URL that started the installations - * @param aInstalls - * An array of AddonInstalls - */ -function Installer(aBrowser, aUrl, aInstalls) { - this.browser = aBrowser; - this.url = aUrl; - this.downloads = aInstalls; - this.installed = []; - - notifyObservers("addon-install-started", aBrowser, aUrl, aInstalls); - - for (let install of aInstalls) { - install.addListener(this); - - // Start downloading if it hasn't already begun - if (READY_STATES.indexOf(install.state) != -1) - install.install(); - } - - this.checkAllDownloaded(); -} - -Installer.prototype = { - browser: null, - downloads: null, - installed: null, - isDownloading: true, - - /** - * Checks if all downloads are now complete and if so prompts to install. - */ - checkAllDownloaded: function() { - // Prevent re-entrancy caused by the confirmation dialog cancelling unwanted - // installs. - if (!this.isDownloading) - return; - - var failed = []; - var installs = []; - - for (let install of this.downloads) { - switch (install.state) { - case AddonManager.STATE_AVAILABLE: - case AddonManager.STATE_DOWNLOADING: - // Exit early if any add-ons haven't started downloading yet or are - // still downloading - return; - case AddonManager.STATE_DOWNLOAD_FAILED: - failed.push(install); - break; - case AddonManager.STATE_DOWNLOADED: - // App disabled items are not compatible and so fail to install - if (install.addon.appDisabled) - failed.push(install); - else - installs.push(install); - - if (install.linkedInstalls) { - for (let linkedInstall of install.linkedInstalls) { - linkedInstall.addListener(this); - // Corrupt or incompatible items fail to install - if (linkedInstall.state == AddonManager.STATE_DOWNLOAD_FAILED || linkedInstall.addon.appDisabled) - failed.push(linkedInstall); - else - installs.push(linkedInstall); - } - } - break; - case AddonManager.STATE_CANCELLED: - // Just ignore cancelled downloads - break; - default: - logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " + - install.state); - } - } - - this.isDownloading = false; - this.downloads = installs; - - if (failed.length > 0) { - // Stop listening and cancel any installs that are failed because of - // compatibility reasons. - for (let install of failed) { - if (install.state == AddonManager.STATE_DOWNLOADED) { - install.removeListener(this); - install.cancel(); - } - } - notifyObservers("addon-install-failed", this.browser, this.url, failed); - } - - // If none of the downloads were successful then exit early - if (this.downloads.length == 0) - return; - - // Check for a custom installation prompt that may be provided by the - // applicaton - if ("@mozilla.org/addons/web-install-prompt;1" in Cc) { - try { - let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"]. - getService(Ci.amIWebInstallPrompt); - prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length); - return; - } - catch (e) {} - } - - if (Preferences.get("xpinstall.customConfirmationUI", false)) { - notifyObservers("addon-install-confirmation", this.browser, this.url, this.downloads); - return; - } - - let args = {}; - args.url = this.url; - args.installs = this.downloads; - args.wrappedJSObject = args; - - try { - Cc["@mozilla.org/base/telemetry;1"]. - getService(Ci.nsITelemetry). - getHistogramById("SECURITY_UI"). - add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL); - let parentWindow = null; - if (this.browser) { - parentWindow = this.browser.ownerDocument.defaultView; - PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser); - } - Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG, - null, "chrome,modal,centerscreen", args); - } catch (e) { - logger.warn("Exception showing install confirmation dialog", e); - for (let install of this.downloads) { - install.removeListener(this); - // Cancel the installs, as currently there is no way to make them fail - // from here. - install.cancel(); - } - notifyObservers("addon-install-cancelled", this.browser, this.url, - this.downloads); - } - }, - - /** - * Checks if all installs are now complete and if so notifies observers. - */ - checkAllInstalled: function() { - var failed = []; - - for (let install of this.downloads) { - switch (install.state) { - case AddonManager.STATE_DOWNLOADED: - case AddonManager.STATE_INSTALLING: - // Exit early if any add-ons haven't started installing yet or are - // still installing - return; - case AddonManager.STATE_INSTALL_FAILED: - failed.push(install); - break; - } - } - - this.downloads = null; - - if (failed.length > 0) - notifyObservers("addon-install-failed", this.browser, this.url, failed); - - if (this.installed.length > 0) - notifyObservers("addon-install-complete", this.browser, this.url, this.installed); - this.installed = null; - }, - - onDownloadCancelled: function(aInstall) { - aInstall.removeListener(this); - this.checkAllDownloaded(); - }, - - onDownloadFailed: function(aInstall) { - aInstall.removeListener(this); - this.checkAllDownloaded(); - }, - - onDownloadEnded: function(aInstall) { - this.checkAllDownloaded(); - return false; - }, - - onInstallCancelled: function(aInstall) { - aInstall.removeListener(this); - this.checkAllInstalled(); - }, - - onInstallFailed: function(aInstall) { - aInstall.removeListener(this); - this.checkAllInstalled(); - }, - - onInstallEnded: function(aInstall) { - aInstall.removeListener(this); - this.installed.push(aInstall); - - // If installing a theme that is disabled and can be enabled then enable it - if (aInstall.addon.type == "theme" && - aInstall.addon.userDisabled == true && - aInstall.addon.appDisabled == false) { - aInstall.addon.userDisabled = false; - } - - this.checkAllInstalled(); - } -}; - -function extWebInstallListener() { -} - -extWebInstallListener.prototype = { - /** - * @see amIWebInstallListener.idl - */ - onWebInstallDisabled: function(aBrowser, aUri, aInstalls) { - let info = { - browser: aBrowser, - originatingURI: aUri, - installs: aInstalls, - - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) - }; - Services.obs.notifyObservers(info, "addon-install-disabled", null); - }, - - /** - * @see amIWebInstallListener.idl - */ - onWebInstallOriginBlocked: function(aBrowser, aUri, aInstalls) { - let info = { - browser: aBrowser, - originatingURI: aUri, - installs: aInstalls, - - install: function() { - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) - }; - Services.obs.notifyObservers(info, "addon-install-origin-blocked", null); - - return false; - }, - - /** - * @see amIWebInstallListener.idl - */ - onWebInstallBlocked: function(aBrowser, aUri, aInstalls) { - let info = { - browser: aBrowser, - originatingURI: aUri, - installs: aInstalls, - - install: function() { - new Installer(this.browser, this.originatingURI, this.installs); - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) - }; - Services.obs.notifyObservers(info, "addon-install-blocked", null); - - return false; - }, - - /** - * @see amIWebInstallListener.idl - */ - onWebInstallRequested: function(aBrowser, aUri, aInstalls) { - new Installer(aBrowser, aUri, aInstalls); - - // We start the installs ourself - return false; - }, - - classDescription: "XPI Install Handler", - contractID: "@mozilla.org/addons/web-install-listener;1", - classID: Components.ID("{0f38e086-89a3-40a5-8ffc-9b694de1d04a}"), - QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallListener, - Ci.amIWebInstallListener2]) -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([extWebInstallListener]); diff --git a/toolkit/mozapps/extensions/content/OpenH264-license.txt b/toolkit/mozapps/extensions/content/OpenH264-license.txt deleted file mode 100644 index ad37989b8..000000000 --- a/toolkit/mozapps/extensions/content/OpenH264-license.txt +++ /dev/null @@ -1,59 +0,0 @@ -------------------------------------------------------- -About The Cisco-Provided Binary of OpenH264 Video Codec -------------------------------------------------------- - -Cisco provides this program under the terms of the BSD license. - -Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met. - -As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk. Your rights from Cisco under the BSD license are not affected by this choice. - -For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary - -A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org - ------------ -BSD License ------------ - -Copyright © 2014 Cisco Systems, Inc. - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS†AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ------------------------------------------ -AVC/H.264 Patent Portfolio License Notice ------------------------------------------ - -The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software: - -THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEOâ€) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO. NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM - -Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla - ---------------------------------------------- -AVC/H.264 Patent Portfolio License Conditions ---------------------------------------------- - -In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met: - -1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device; - -2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary; - -3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text: - - "OpenH264 Video Codec provided by Cisco Systems, Inc." - -4. Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user. - - - - v1.0 diff --git a/toolkit/mozapps/extensions/content/about.js b/toolkit/mozapps/extensions/content/about.js deleted file mode 100644 index 4f8fb353e..000000000 --- a/toolkit/mozapps/extensions/content/about.js +++ /dev/null @@ -1,103 +0,0 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.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"; - -/* import-globals-from ../../../content/contentAreaUtils.js */ - -var Cu = Components.utils; -Cu.import("resource://gre/modules/AddonManager.jsm"); - -function init() { - var addon = window.arguments[0]; - var extensionsStrings = document.getElementById("extensionsStrings"); - - document.documentElement.setAttribute("addontype", addon.type); - - var iconURL = AddonManager.getPreferredIconURL(addon, 48, window); - if (iconURL) { - var extensionIcon = document.getElementById("extensionIcon"); - extensionIcon.src = iconURL; - } - - document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [addon.name]); - var extensionName = document.getElementById("extensionName"); - extensionName.textContent = addon.name; - - var extensionVersion = document.getElementById("extensionVersion"); - if (addon.version) - extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [addon.version])); - else - extensionVersion.hidden = true; - - var extensionDescription = document.getElementById("extensionDescription"); - if (addon.description) - extensionDescription.textContent = addon.description; - else - extensionDescription.hidden = true; - - var numDetails = 0; - - var extensionCreator = document.getElementById("extensionCreator"); - if (addon.creator) { - extensionCreator.setAttribute("value", addon.creator); - numDetails++; - } else { - extensionCreator.hidden = true; - var extensionCreatorLabel = document.getElementById("extensionCreatorLabel"); - extensionCreatorLabel.hidden = true; - } - - var extensionHomepage = document.getElementById("extensionHomepage"); - var homepageURL = addon.homepageURL; - if (homepageURL) { - extensionHomepage.setAttribute("homepageURL", homepageURL); - extensionHomepage.setAttribute("tooltiptext", homepageURL); - numDetails++; - } else { - extensionHomepage.hidden = true; - } - - numDetails += appendToList("extensionDevelopers", "developersBox", addon.developers); - numDetails += appendToList("extensionTranslators", "translatorsBox", addon.translators); - numDetails += appendToList("extensionContributors", "contributorsBox", addon.contributors); - - if (numDetails == 0) { - var groove = document.getElementById("groove"); - groove.hidden = true; - var extensionDetailsBox = document.getElementById("extensionDetailsBox"); - extensionDetailsBox.hidden = true; - } - - var acceptButton = document.documentElement.getButton("accept"); - acceptButton.label = extensionsStrings.getString("aboutWindowCloseButton"); - - setTimeout(sizeToContent, 0); -} - -function appendToList(aHeaderId, aNodeId, aItems) { - var header = document.getElementById(aHeaderId); - var node = document.getElementById(aNodeId); - - if (!aItems || aItems.length == 0) { - header.hidden = true; - return 0; - } - - for (let currentItem of aItems) { - var label = document.createElement("label"); - label.textContent = currentItem; - label.setAttribute("class", "contributor"); - node.appendChild(label); - } - - return aItems.length; -} - -function loadHomepage(aEvent) { - window.close(); - openURL(aEvent.target.getAttribute("homepageURL")); -} diff --git a/toolkit/mozapps/extensions/content/about.xul b/toolkit/mozapps/extensions/content/about.xul deleted file mode 100644 index 6effcf37a..000000000 --- a/toolkit/mozapps/extensions/content/about.xul +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - -

Test page for the discovery pane

-

Direct install

-

JS install

- - diff --git a/toolkit/mozapps/extensions/test/browser/head.js b/toolkit/mozapps/extensions/test/browser/head.js deleted file mode 100644 index 5a749099d..000000000 --- a/toolkit/mozapps/extensions/test/browser/head.js +++ /dev/null @@ -1,1468 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -/* globals end_test*/ - -Components.utils.import("resource://gre/modules/NetUtil.jsm"); - -var tmp = {}; -Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp); -Components.utils.import("resource://gre/modules/Log.jsm", tmp); -var AddonManager = tmp.AddonManager; -var AddonManagerPrivate = tmp.AddonManagerPrivate; -var Log = tmp.Log; - -var pathParts = gTestPath.split("/"); -// Drop the test filename -pathParts.splice(pathParts.length - 1, pathParts.length); - -var gTestInWindow = /-window$/.test(pathParts[pathParts.length - 1]); - -// Drop the UI type -if (gTestInWindow) { - pathParts.splice(pathParts.length - 1, pathParts.length); -} - -const RELATIVE_DIR = pathParts.slice(4).join("/") + "/"; - -const TESTROOT = "http://example.com/" + RELATIVE_DIR; -const SECURE_TESTROOT = "https://example.com/" + RELATIVE_DIR; -const TESTROOT2 = "http://example.org/" + RELATIVE_DIR; -const SECURE_TESTROOT2 = "https://example.org/" + RELATIVE_DIR; -const CHROMEROOT = pathParts.join("/") + "/"; -const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; -const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; -const PREF_XPI_ENABLED = "xpinstall.enabled"; -const PREF_UPDATEURL = "extensions.update.url"; -const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; -const PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI = "xpinstall.customConfirmationUI"; -const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory"; - -const MANAGER_URI = "about:addons"; -const INSTALL_URI = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; -const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; -const PREF_SEARCH_MAXRESULTS = "extensions.getAddons.maxResults"; -const PREF_STRICT_COMPAT = "extensions.strictCompatibility"; - -var PREF_CHECK_COMPATIBILITY; -(function() { - var channel = "default"; - try { - channel = Services.prefs.getCharPref("app.update.channel"); - } catch (e) { } - if (channel != "aurora" && - channel != "beta" && - channel != "release" && - channel != "esr") { - var version = "nightly"; - } else { - version = Services.appinfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1"); - } - PREF_CHECK_COMPATIBILITY = "extensions.checkCompatibility." + version; -})(); - -var gPendingTests = []; -var gTestsRun = 0; -var gTestStart = null; - -var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window); - -var gRestorePrefs = [{name: PREF_LOGGING_ENABLED}, - {name: PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI}, - {name: "extensions.webservice.discoverURL"}, - {name: "extensions.update.url"}, - {name: "extensions.update.background.url"}, - {name: "extensions.update.enabled"}, - {name: "extensions.update.autoUpdateDefault"}, - {name: "extensions.getAddons.get.url"}, - {name: "extensions.getAddons.getWithPerformance.url"}, - {name: "extensions.getAddons.search.browseURL"}, - {name: "extensions.getAddons.search.url"}, - {name: "extensions.getAddons.cache.enabled"}, - {name: "devtools.chrome.enabled"}, - {name: PREF_SEARCH_MAXRESULTS}, - {name: PREF_STRICT_COMPAT}, - {name: PREF_CHECK_COMPATIBILITY}]; - -for (let pref of gRestorePrefs) { - if (!Services.prefs.prefHasUserValue(pref.name)) { - pref.type = "clear"; - continue; - } - pref.type = Services.prefs.getPrefType(pref.name); - if (pref.type == Services.prefs.PREF_BOOL) - pref.value = Services.prefs.getBoolPref(pref.name); - else if (pref.type == Services.prefs.PREF_INT) - pref.value = Services.prefs.getIntPref(pref.name); - else if (pref.type == Services.prefs.PREF_STRING) - pref.value = Services.prefs.getCharPref(pref.name); -} - -// Turn logging on for all tests -Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); - -Services.prefs.setBoolPref(PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI, false); - -// Helper to register test failures and close windows if any are left open -function checkOpenWindows(aWindowID) { - let windows = Services.wm.getEnumerator(aWindowID); - let found = false; - while (windows.hasMoreElements()) { - let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow); - if (!win.closed) { - found = true; - win.close(); - } - } - if (found) - ok(false, "Found unexpected " + aWindowID + " window still open"); -} - -// Tools to disable and re-enable the background update and blocklist timers -// so that tests can protect themselves from unwanted timer events. -var gCatMan = Components.classes["@mozilla.org/categorymanager;1"] - .getService(Components.interfaces.nsICategoryManager); -// Default values from toolkit/mozapps/extensions/extensions.manifest, but disable*UpdateTimer() -// records the actual value so we can put it back in enable*UpdateTimer() -var backgroundUpdateConfig = "@mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400"; -var blocklistUpdateConfig = "@mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400"; - -var UTIMER = "update-timer"; -var AMANAGER = "addonManager"; -var BLOCKLIST = "nsBlocklistService"; - -function disableBackgroundUpdateTimer() { - info("Disabling " + UTIMER + " " + AMANAGER); - backgroundUpdateConfig = gCatMan.getCategoryEntry(UTIMER, AMANAGER); - gCatMan.deleteCategoryEntry(UTIMER, AMANAGER, true); -} - -function enableBackgroundUpdateTimer() { - info("Enabling " + UTIMER + " " + AMANAGER); - gCatMan.addCategoryEntry(UTIMER, AMANAGER, backgroundUpdateConfig, false, true); -} - -function disableBlocklistUpdateTimer() { - info("Disabling " + UTIMER + " " + BLOCKLIST); - blocklistUpdateConfig = gCatMan.getCategoryEntry(UTIMER, BLOCKLIST); - gCatMan.deleteCategoryEntry(UTIMER, BLOCKLIST, true); -} - -function enableBlocklistUpdateTimer() { - info("Enabling " + UTIMER + " " + BLOCKLIST); - gCatMan.addCategoryEntry(UTIMER, BLOCKLIST, blocklistUpdateConfig, false, true); -} - -registerCleanupFunction(function() { - // Restore prefs - for (let pref of gRestorePrefs) { - if (pref.type == "clear") - Services.prefs.clearUserPref(pref.name); - else if (pref.type == Services.prefs.PREF_BOOL) - Services.prefs.setBoolPref(pref.name, pref.value); - else if (pref.type == Services.prefs.PREF_INT) - Services.prefs.setIntPref(pref.name, pref.value); - else if (pref.type == Services.prefs.PREF_STRING) - Services.prefs.setCharPref(pref.name, pref.value); - } - - // Throw an error if the add-ons manager window is open anywhere - checkOpenWindows("Addons:Manager"); - checkOpenWindows("Addons:Compatibility"); - checkOpenWindows("Addons:Install"); - - return new Promise((resolve, reject) => AddonManager.getAllInstalls(resolve)) - .then(aInstalls => { - for (let install of aInstalls) { - if (install instanceof MockInstall) - continue; - - ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state); - install.cancel(); - } - }); -}); - -function log_exceptions(aCallback, ...aArgs) { - try { - return aCallback.apply(null, aArgs); - } - catch (e) { - info("Exception thrown: " + e); - throw e; - } -} - -function log_callback(aPromise, aCallback) { - aPromise.then(aCallback) - .then(null, e => info("Exception thrown: " + e)); - return aPromise; -} - -function add_test(test) { - gPendingTests.push(test); -} - -function run_next_test() { - // Make sure we're not calling run_next_test from inside an add_task() test - // We're inside the browser_test.js 'testScope' here - if (this.__tasks) { - throw new Error("run_next_test() called from an add_task() test function. " + - "run_next_test() should not be called from inside add_task() " + - "under any circumstances!"); - } - if (gTestsRun > 0) - info("Test " + gTestsRun + " took " + (Date.now() - gTestStart) + "ms"); - - if (gPendingTests.length == 0) { - executeSoon(end_test); - return; - } - - gTestsRun++; - var test = gPendingTests.shift(); - if (test.name) - info("Running test " + gTestsRun + " (" + test.name + ")"); - else - info("Running test " + gTestsRun); - - gTestStart = Date.now(); - executeSoon(() => log_exceptions(test)); -} - -var get_tooltip_info = Task.async(function*(addon) { - let managerWindow = addon.ownerDocument.defaultView; - - // The popup code uses a triggering event's target to set the - // document.tooltipNode property. - let nameNode = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "name"); - let event = new managerWindow.CustomEvent("TriggerEvent"); - nameNode.dispatchEvent(event); - - let tooltip = managerWindow.document.getElementById("addonitem-tooltip"); - - let promise = BrowserTestUtils.waitForEvent(tooltip, "popupshown"); - tooltip.openPopup(nameNode, "after_start", 0, 0, false, false, event); - yield promise; - - let tiptext = tooltip.label; - - promise = BrowserTestUtils.waitForEvent(tooltip, "popuphidden"); - tooltip.hidePopup(); - yield promise; - - let expectedName = addon.getAttribute("name"); - ok(tiptext.substring(0, expectedName.length), expectedName, - "Tooltip should always start with the expected name"); - - if (expectedName.length == tiptext.length) { - return { - name: tiptext, - version: undefined - }; - } - return { - name: tiptext.substring(0, expectedName.length), - version: tiptext.substring(expectedName.length + 1) - }; -}); - -function get_addon_file_url(aFilename) { - try { - var cr = Cc["@mozilla.org/chrome/chrome-registry;1"]. - getService(Ci.nsIChromeRegistry); - var fileurl = cr.convertChromeURL(makeURI(CHROMEROOT + "addons/" + aFilename)); - return fileurl.QueryInterface(Ci.nsIFileURL); - } catch (ex) { - var jar = getJar(CHROMEROOT + "addons/" + aFilename); - var tmpDir = extractJarToTmp(jar); - tmpDir.append(aFilename); - - return Services.io.newFileURI(tmpDir).QueryInterface(Ci.nsIFileURL); - } -} - -function get_current_view(aManager) { - let view = aManager.document.getElementById("view-port").selectedPanel; - if (view.id == "headered-views") { - view = aManager.document.getElementById("headered-views-content").selectedPanel; - } - is(view, aManager.gViewController.displayedView, "view controller is tracking the displayed view correctly"); - return view; -} - -function get_test_items_in_list(aManager) { - var tests = "@tests.mozilla.org"; - - let view = get_current_view(aManager); - let listid = view.id == "search-view" ? "search-list" : "addon-list"; - let item = aManager.document.getElementById(listid).firstChild; - let items = []; - - while (item) { - if (item.localName != "richlistitem") { - item = item.nextSibling; - continue; - } - - if (!item.mAddon || item.mAddon.id.substring(item.mAddon.id.length - tests.length) == tests) - items.push(item); - item = item.nextSibling; - } - - return items; -} - -function check_all_in_list(aManager, aIds, aIgnoreExtras) { - var doc = aManager.document; - var view = get_current_view(aManager); - var listid = view.id == "search-view" ? "search-list" : "addon-list"; - var list = doc.getElementById(listid); - - var inlist = []; - var node = list.firstChild; - while (node) { - if (node.value) - inlist.push(node.value); - node = node.nextSibling; - } - - for (let id of aIds) { - if (inlist.indexOf(id) == -1) - ok(false, "Should find " + id + " in the list"); - } - - if (aIgnoreExtras) - return; - - for (let inlistItem of inlist) { - if (aIds.indexOf(inlistItem) == -1) - ok(false, "Shouldn't have seen " + inlistItem + " in the list"); - } -} - -function get_addon_element(aManager, aId) { - var doc = aManager.document; - var view = get_current_view(aManager); - var listid = "addon-list"; - if (view.id == "search-view") - listid = "search-list"; - else if (view.id == "updates-view") - listid = "updates-list"; - var list = doc.getElementById(listid); - - var node = list.firstChild; - while (node) { - if (node.value == aId) - return node; - node = node.nextSibling; - } - return null; -} - -function wait_for_view_load(aManagerWindow, aCallback, aForceWait, aLongerTimeout) { - requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); - - if (!aForceWait && !aManagerWindow.gViewController.isLoading) { - log_exceptions(aCallback, aManagerWindow); - return; - } - - aManagerWindow.document.addEventListener("ViewChanged", function() { - aManagerWindow.document.removeEventListener("ViewChanged", arguments.callee, false); - log_exceptions(aCallback, aManagerWindow); - }, false); -} - -function wait_for_manager_load(aManagerWindow, aCallback) { - if (!aManagerWindow.gIsInitializing) { - log_exceptions(aCallback, aManagerWindow); - return; - } - - info("Waiting for initialization"); - aManagerWindow.document.addEventListener("Initialized", function() { - aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false); - log_exceptions(aCallback, aManagerWindow); - }, false); -} - -function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) { - let p = new Promise((resolve, reject) => { - - function setup_manager(aManagerWindow) { - if (aLoadCallback) - log_exceptions(aLoadCallback, aManagerWindow); - - if (aView) - aManagerWindow.loadView(aView); - - ok(aManagerWindow != null, "Should have an add-ons manager window"); - is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI"); - - waitForFocus(function() { - info("window has focus, waiting for manager load"); - wait_for_manager_load(aManagerWindow, function() { - info("Manager waiting for view load"); - wait_for_view_load(aManagerWindow, function() { - resolve(aManagerWindow); - }, null, aLongerTimeout); - }); - }, aManagerWindow); - } - - if (gUseInContentUI) { - info("Loading manager window in tab"); - Services.obs.addObserver(function (aSubject, aTopic, aData) { - Services.obs.removeObserver(arguments.callee, aTopic); - if (aSubject.location.href != MANAGER_URI) { - info("Ignoring load event for " + aSubject.location.href); - return; - } - setup_manager(aSubject); - }, "EM-loaded", false); - - gBrowser.selectedTab = gBrowser.addTab(); - switchToTabHavingURI(MANAGER_URI, true); - } else { - info("Loading manager window in dialog"); - Services.obs.addObserver(function (aSubject, aTopic, aData) { - Services.obs.removeObserver(arguments.callee, aTopic); - setup_manager(aSubject); - }, "EM-loaded", false); - - openDialog(MANAGER_URI); - } - }); - - // The promise resolves with the manager window, so it is passed to the callback - return log_callback(p, aCallback); -} - -function close_manager(aManagerWindow, aCallback, aLongerTimeout) { - let p = new Promise((resolve, reject) => { - requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); - - ok(aManagerWindow != null, "Should have an add-ons manager window to close"); - is(aManagerWindow.location, MANAGER_URI, "Should be closing window with correct URI"); - - aManagerWindow.addEventListener("unload", function() { - try { - dump("Manager window unload handler\n"); - this.removeEventListener("unload", arguments.callee, false); - resolve(); - } catch (e) { - reject(e); - } - }, false); - }); - - info("Telling manager window to close"); - aManagerWindow.close(); - info("Manager window close() call returned"); - - return log_callback(p, aCallback); -} - -function restart_manager(aManagerWindow, aView, aCallback, aLoadCallback) { - if (!aManagerWindow) { - return open_manager(aView, aCallback, aLoadCallback); - } - - return close_manager(aManagerWindow) - .then(() => open_manager(aView, aCallback, aLoadCallback)); -} - -function wait_for_window_open(aCallback) { - Services.wm.addListener({ - onOpenWindow: function(aWindow) { - Services.wm.removeListener(this); - - let domwindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - domwindow.addEventListener("load", function() { - domwindow.removeEventListener("load", arguments.callee, false); - executeSoon(function() { - aCallback(domwindow); - }); - }, false); - }, - - onCloseWindow: function(aWindow) { - }, - - onWindowTitleChange: function(aWindow, aTitle) { - } - }); -} - -function get_string(aName, ...aArgs) { - var bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/extensions.properties"); - if (aArgs.length == 0) - return bundle.GetStringFromName(aName); - return bundle.formatStringFromName(aName, aArgs, aArgs.length); -} - -function formatDate(aDate) { - const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] - .getService(Ci.nsIXULChromeRegistry) - .getSelectedLocale("global", true); - const dtOptions = { year: 'numeric', month: 'long', day: 'numeric' }; - return aDate.toLocaleDateString(locale, dtOptions); -} - -function is_hidden(aElement) { - var style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, ""); - if (style.display == "none") - return true; - if (style.visibility != "visible") - return true; - - // Hiding a parent element will hide all its children - if (aElement.parentNode != aElement.ownerDocument) - return is_hidden(aElement.parentNode); - - return false; -} - -function is_element_visible(aElement, aMsg) { - isnot(aElement, null, "Element should not be null, when checking visibility"); - ok(!is_hidden(aElement), aMsg || (aElement + " should be visible")); -} - -function is_element_hidden(aElement, aMsg) { - isnot(aElement, null, "Element should not be null, when checking visibility"); - ok(is_hidden(aElement), aMsg || (aElement + " should be hidden")); -} - -function promiseAddonByID(aId) { - return new Promise(resolve => { - AddonManager.getAddonByID(aId, resolve); - }); -} - -function promiseAddonsByIDs(aIDs) { - return new Promise(resolve => { - AddonManager.getAddonsByIDs(aIDs, resolve); - }); -} -/** - * Install an add-on and call a callback when complete. - * - * The callback will receive the Addon for the installed add-on. - */ -function install_addon(path, cb, pathPrefix=TESTROOT) { - let p = new Promise((resolve, reject) => { - AddonManager.getInstallForURL(pathPrefix + path, (install) => { - install.addListener({ - onInstallEnded: () => resolve(install.addon), - }); - - install.install(); - }, "application/x-xpinstall"); - }); - - return log_callback(p, cb); -} - -function CategoryUtilities(aManagerWindow) { - this.window = aManagerWindow; - - var self = this; - this.window.addEventListener("unload", function() { - self.window.removeEventListener("unload", arguments.callee, false); - self.window = null; - }, false); -} - -CategoryUtilities.prototype = { - window: null, - - get selectedCategory() { - isnot(this.window, null, "Should not get selected category when manager window is not loaded"); - var selectedItem = this.window.document.getElementById("categories").selectedItem; - isnot(selectedItem, null, "A category should be selected"); - var view = this.window.gViewController.parseViewId(selectedItem.value); - return (view.type == "list") ? view.param : view.type; - }, - - get: function(aCategoryType, aAllowMissing) { - isnot(this.window, null, "Should not get category when manager window is not loaded"); - var categories = this.window.document.getElementById("categories"); - - var viewId = "addons://list/" + aCategoryType; - var items = categories.getElementsByAttribute("value", viewId); - if (items.length) - return items[0]; - - viewId = "addons://" + aCategoryType + "/"; - items = categories.getElementsByAttribute("value", viewId); - if (items.length) - return items[0]; - - if (!aAllowMissing) - ok(false, "Should have found a category with type " + aCategoryType); - return null; - }, - - getViewId: function(aCategoryType) { - isnot(this.window, null, "Should not get view id when manager window is not loaded"); - return this.get(aCategoryType).value; - }, - - isVisible: function(aCategory) { - isnot(this.window, null, "Should not check visible state when manager window is not loaded"); - if (aCategory.hasAttribute("disabled") && - aCategory.getAttribute("disabled") == "true") - return false; - - return !is_hidden(aCategory); - }, - - isTypeVisible: function(aCategoryType) { - return this.isVisible(this.get(aCategoryType)); - }, - - open: function(aCategory, aCallback) { - - isnot(this.window, null, "Should not open category when manager window is not loaded"); - ok(this.isVisible(aCategory), "Category should be visible if attempting to open it"); - - EventUtils.synthesizeMouse(aCategory, 2, 2, { }, this.window); - let p = new Promise((resolve, reject) => wait_for_view_load(this.window, resolve)); - - return log_callback(p, aCallback); - }, - - openType: function(aCategoryType, aCallback) { - return this.open(this.get(aCategoryType), aCallback); - } -} - -function CertOverrideListener(host, bits) { - this.host = host; - this.bits = bits; -} - -CertOverrideListener.prototype = { - host: null, - bits: null, - - getInterface: function (aIID) { - return this.QueryInterface(aIID); - }, - - QueryInterface: function(aIID) { - if (aIID.equals(Ci.nsIBadCertListener2) || - aIID.equals(Ci.nsIInterfaceRequestor) || - aIID.equals(Ci.nsISupports)) - return this; - - throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE); - }, - - notifyCertProblem: function (socketInfo, sslStatus, targetHost) { - var cert = sslStatus.QueryInterface(Components.interfaces.nsISSLStatus) - .serverCert; - var cos = Cc["@mozilla.org/security/certoverride;1"]. - getService(Ci.nsICertOverrideService); - cos.rememberValidityOverride(this.host, -1, cert, this.bits, false); - return true; - } -} - -// Add overrides for the bad certificates -function addCertOverride(host, bits) { - var req = new XMLHttpRequest(); - try { - req.open("GET", "https://" + host + "/", false); - req.channel.notificationCallbacks = new CertOverrideListener(host, bits); - req.send(null); - } - catch (e) { - // This request will fail since the SSL server is not trusted yet - } -} - -/** *** Mock Provider *****/ - -function MockProvider(aUseAsyncCallbacks, aTypes) { - this.addons = []; - this.installs = []; - this.callbackTimers = []; - this.timerLocations = new Map(); - this.useAsyncCallbacks = (aUseAsyncCallbacks === undefined) ? true : aUseAsyncCallbacks; - this.types = (aTypes === undefined) ? [{ - id: "extension", - name: "Extensions", - uiPriority: 4000, - flags: AddonManager.TYPE_UI_VIEW_LIST | - AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL, - }] : aTypes; - - var self = this; - registerCleanupFunction(function() { - if (self.started) - self.unregister(); - }); - - this.register(); -} - -MockProvider.prototype = { - addons: null, - installs: null, - started: null, - apiDelay: 10, - callbackTimers: null, - timerLocations: null, - useAsyncCallbacks: null, - types: null, - - /** *** Utility functions *****/ - - /** - * Register this provider with the AddonManager - */ - register: function MP_register() { - info("Registering mock add-on provider"); - AddonManagerPrivate.registerProvider(this, this.types); - }, - - /** - * Unregister this provider with the AddonManager - */ - unregister: function MP_unregister() { - info("Unregistering mock add-on provider"); - AddonManagerPrivate.unregisterProvider(this); - }, - - /** - * Adds an add-on to the list of add-ons that this provider exposes to the - * AddonManager, dispatching appropriate events in the process. - * - * @param aAddon - * The add-on to add - */ - addAddon: function MP_addAddon(aAddon) { - var oldAddons = this.addons.filter(aOldAddon => aOldAddon.id == aAddon.id); - var oldAddon = oldAddons.length > 0 ? oldAddons[0] : null; - - this.addons = this.addons.filter(aOldAddon => aOldAddon.id != aAddon.id); - - this.addons.push(aAddon); - aAddon._provider = this; - - if (!this.started) - return; - - let requiresRestart = (aAddon.operationsRequiringRestart & - AddonManager.OP_NEEDS_RESTART_INSTALL) != 0; - AddonManagerPrivate.callInstallListeners("onExternalInstall", null, aAddon, - oldAddon, requiresRestart) - }, - - /** - * Removes an add-on from the list of add-ons that this provider exposes to - * the AddonManager, dispatching the onUninstalled event in the process. - * - * @param aAddon - * The add-on to add - */ - removeAddon: function MP_removeAddon(aAddon) { - var pos = this.addons.indexOf(aAddon); - if (pos == -1) { - ok(false, "Tried to remove an add-on that wasn't registered with the mock provider"); - return; - } - - this.addons.splice(pos, 1); - - if (!this.started) - return; - - AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon); - }, - - /** - * Adds an add-on install to the list of installs that this provider exposes - * to the AddonManager, dispatching appropriate events in the process. - * - * @param aInstall - * The add-on install to add - */ - addInstall: function MP_addInstall(aInstall) { - this.installs.push(aInstall); - aInstall._provider = this; - - if (!this.started) - return; - - aInstall.callListeners("onNewInstall"); - }, - - removeInstall: function MP_removeInstall(aInstall) { - var pos = this.installs.indexOf(aInstall); - if (pos == -1) { - ok(false, "Tried to remove an install that wasn't registered with the mock provider"); - return; - } - - this.installs.splice(pos, 1); - }, - - /** - * Creates a set of mock add-on objects and adds them to the list of add-ons - * managed by this provider. - * - * @param aAddonProperties - * An array of objects containing properties describing the add-ons - * @return Array of the new MockAddons - */ - createAddons: function MP_createAddons(aAddonProperties) { - var newAddons = []; - for (let addonProp of aAddonProperties) { - let addon = new MockAddon(addonProp.id); - for (let prop in addonProp) { - if (prop == "id") - continue; - if (prop == "applyBackgroundUpdates") { - addon._applyBackgroundUpdates = addonProp[prop]; - continue; - } - if (prop == "appDisabled") { - addon._appDisabled = addonProp[prop]; - continue; - } - addon[prop] = addonProp[prop]; - } - if (!addon.optionsType && !!addon.optionsURL) - addon.optionsType = AddonManager.OPTIONS_TYPE_DIALOG; - - // Make sure the active state matches the passed in properties - addon.isActive = addon.shouldBeActive; - - this.addAddon(addon); - newAddons.push(addon); - } - - return newAddons; - }, - - /** - * Creates a set of mock add-on install objects and adds them to the list - * of installs managed by this provider. - * - * @param aInstallProperties - * An array of objects containing properties describing the installs - * @return Array of the new MockInstalls - */ - createInstalls: function MP_createInstalls(aInstallProperties) { - var newInstalls = []; - for (let installProp of aInstallProperties) { - let install = new MockInstall(installProp.name || null, - installProp.type || null, - null); - for (let prop in installProp) { - switch (prop) { - case "name": - case "type": - break; - case "sourceURI": - install[prop] = NetUtil.newURI(installProp[prop]); - break; - default: - install[prop] = installProp[prop]; - } - } - this.addInstall(install); - newInstalls.push(install); - } - - return newInstalls; - }, - - /** *** AddonProvider implementation *****/ - - /** - * Called to initialize the provider. - */ - startup: function MP_startup() { - this.started = true; - }, - - /** - * Called when the provider should shutdown. - */ - shutdown: function MP_shutdown() { - if (this.callbackTimers.length) { - info("MockProvider: pending callbacks at shutdown(): calling immediately"); - } - while (this.callbackTimers.length > 0) { - // When we notify the callback timer, it removes itself from our array - let timer = this.callbackTimers[0]; - try { - let setAt = this.timerLocations.get(timer); - info("Notifying timer set at " + (setAt || "unknown location")); - timer.callback.notify(timer); - timer.cancel(); - } catch (e) { - info("Timer notify failed: " + e); - } - } - this.callbackTimers = []; - this.timerLocations = null; - - this.started = false; - }, - - /** - * Called to get an Addon with a particular ID. - * - * @param aId - * The ID of the add-on to retrieve - * @param aCallback - * A callback to pass the Addon to - */ - getAddonByID: function MP_getAddon(aId, aCallback) { - for (let addon of this.addons) { - if (addon.id == aId) { - this._delayCallback(aCallback, addon); - return; - } - } - - aCallback(null); - }, - - /** - * Called to get Addons of a particular type. - * - * @param aTypes - * An array of types to fetch. Can be null to get all types. - * @param callback - * A callback to pass an array of Addons to - */ - getAddonsByTypes: function MP_getAddonsByTypes(aTypes, aCallback) { - var addons = this.addons.filter(function(aAddon) { - if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1) - return false; - return true; - }); - this._delayCallback(aCallback, addons); - }, - - /** - * Called to get Addons that have pending operations. - * - * @param aTypes - * An array of types to fetch. Can be null to get all types - * @param aCallback - * A callback to pass an array of Addons to - */ - getAddonsWithOperationsByTypes: function MP_getAddonsWithOperationsByTypes(aTypes, aCallback) { - var addons = this.addons.filter(function(aAddon) { - if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1) - return false; - return aAddon.pendingOperations != 0; - }); - this._delayCallback(aCallback, addons); - }, - - /** - * Called to get the current AddonInstalls, optionally restricting by type. - * - * @param aTypes - * An array of types or null to get all types - * @param aCallback - * A callback to pass the array of AddonInstalls to - */ - getInstallsByTypes: function MP_getInstallsByTypes(aTypes, aCallback) { - var installs = this.installs.filter(function(aInstall) { - // Appear to have actually removed cancelled installs from the provider - if (aInstall.state == AddonManager.STATE_CANCELLED) - return false; - - if (aTypes && aTypes.length > 0 && aTypes.indexOf(aInstall.type) == -1) - return false; - - return true; - }); - this._delayCallback(aCallback, installs); - }, - - /** - * Called when a new add-on has been enabled when only one add-on of that type - * can be enabled. - * - * @param aId - * The ID of the newly enabled add-on - * @param aType - * The type of the newly enabled add-on - * @param aPendingRestart - * true if the newly enabled add-on will only become enabled after a - * restart - */ - addonChanged: function MP_addonChanged(aId, aType, aPendingRestart) { - // Not implemented - }, - - /** - * Update the appDisabled property for all add-ons. - */ - updateAddonAppDisabledStates: function MP_updateAddonAppDisabledStates() { - // Not needed - }, - - /** - * Called to get an AddonInstall to download and install an add-on from a URL. - * - * @param aUrl - * The URL to be installed - * @param aHash - * A hash for the install - * @param aName - * A name for the install - * @param aIconURL - * An icon URL for the install - * @param aVersion - * A version for the install - * @param aLoadGroup - * An nsILoadGroup to associate requests with - * @param aCallback - * A callback to pass the AddonInstall to - */ - getInstallForURL: function MP_getInstallForURL(aUrl, aHash, aName, aIconURL, - aVersion, aLoadGroup, aCallback) { - // Not yet implemented - }, - - /** - * Called to get an AddonInstall to install an add-on from a local file. - * - * @param aFile - * The file to be installed - * @param aCallback - * A callback to pass the AddonInstall to - */ - getInstallForFile: function MP_getInstallForFile(aFile, aCallback) { - // Not yet implemented - }, - - /** - * Called to test whether installing add-ons is enabled. - * - * @return true if installing is enabled - */ - isInstallEnabled: function MP_isInstallEnabled() { - return false; - }, - - /** - * Called to test whether this provider supports installing a particular - * mimetype. - * - * @param aMimetype - * The mimetype to check for - * @return true if the mimetype is supported - */ - supportsMimetype: function MP_supportsMimetype(aMimetype) { - return false; - }, - - /** - * Called to test whether installing add-ons from a URI is allowed. - * - * @param aUri - * The URI being installed from - * @return true if installing is allowed - */ - isInstallAllowed: function MP_isInstallAllowed(aUri) { - return false; - }, - - - /** *** Internal functions *****/ - - /** - * Delay calling a callback to fake a time-consuming async operation. - * The delay is specified by the apiDelay property, in milliseconds. - * Parameters to send to the callback should be specified as arguments after - * the aCallback argument. - * - * @param aCallback Callback to eventually call - */ - _delayCallback: function MP_delayCallback(aCallback, ...aArgs) { - if (!this.useAsyncCallbacks) { - aCallback(...aArgs); - return; - } - - let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - // Need to keep a reference to the timer, so it doesn't get GC'ed - this.callbackTimers.push(timer); - // Capture a stack trace where the timer was set - // needs the 'new Error' hack until bug 1007656 - this.timerLocations.set(timer, Log.stackTrace(new Error("dummy"))); - timer.initWithCallback(() => { - let idx = this.callbackTimers.indexOf(timer); - if (idx == -1) { - dump("MockProvider._delayCallback lost track of timer set at " - + (this.timerLocations.get(timer) || "unknown location") + "\n"); - } else { - this.callbackTimers.splice(idx, 1); - } - this.timerLocations.delete(timer); - aCallback(...aArgs); - }, this.apiDelay, timer.TYPE_ONE_SHOT); - } -}; - -/** *** Mock Addon object for the Mock Provider *****/ - -function MockAddon(aId, aName, aType, aOperationsRequiringRestart) { - // Only set required attributes. - this.id = aId || ""; - this.name = aName || ""; - this.type = aType || "extension"; - this.version = ""; - this.isCompatible = true; - this.providesUpdatesSecurely = true; - this.blocklistState = 0; - this._appDisabled = false; - this._userDisabled = false; - this._applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE; - this.scope = AddonManager.SCOPE_PROFILE; - this.isActive = true; - this.creator = ""; - this.pendingOperations = 0; - this._permissions = AddonManager.PERM_CAN_UNINSTALL | - AddonManager.PERM_CAN_ENABLE | - AddonManager.PERM_CAN_DISABLE | - AddonManager.PERM_CAN_UPGRADE; - this.operationsRequiringRestart = (aOperationsRequiringRestart != undefined) ? - aOperationsRequiringRestart : - (AddonManager.OP_NEEDS_RESTART_INSTALL | - AddonManager.OP_NEEDS_RESTART_UNINSTALL | - AddonManager.OP_NEEDS_RESTART_ENABLE | - AddonManager.OP_NEEDS_RESTART_DISABLE); -} - -MockAddon.prototype = { - get isCorrectlySigned() { - if (this.signedState === AddonManager.SIGNEDSTATE_NOT_REQUIRED) - return true; - return this.signedState > AddonManager.SIGNEDSTATE_MISSING; - }, - - get shouldBeActive() { - return !this.appDisabled && !this._userDisabled && - !(this.pendingOperations & AddonManager.PENDING_UNINSTALL); - }, - - get appDisabled() { - return this._appDisabled; - }, - - set appDisabled(val) { - if (val == this._appDisabled) - return val; - - AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["appDisabled"]); - - var currentActive = this.shouldBeActive; - this._appDisabled = val; - var newActive = this.shouldBeActive; - this._updateActiveState(currentActive, newActive); - - return val; - }, - - get userDisabled() { - return this._userDisabled; - }, - - set userDisabled(val) { - if (val == this._userDisabled) - return val; - - var currentActive = this.shouldBeActive; - this._userDisabled = val; - var newActive = this.shouldBeActive; - this._updateActiveState(currentActive, newActive); - - return val; - }, - - get permissions() { - let permissions = this._permissions; - if (this.appDisabled || !this._userDisabled) - permissions &= ~AddonManager.PERM_CAN_ENABLE; - if (this.appDisabled || this._userDisabled) - permissions &= ~AddonManager.PERM_CAN_DISABLE; - return permissions; - }, - - set permissions(val) { - return this._permissions = val; - }, - - get applyBackgroundUpdates() { - return this._applyBackgroundUpdates; - }, - - set applyBackgroundUpdates(val) { - if (val != AddonManager.AUTOUPDATE_DEFAULT && - val != AddonManager.AUTOUPDATE_DISABLE && - val != AddonManager.AUTOUPDATE_ENABLE) { - ok(false, "addon.applyBackgroundUpdates set to an invalid value: " + val); - } - this._applyBackgroundUpdates = val; - AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]); - }, - - isCompatibleWith: function(aAppVersion, aPlatformVersion) { - return true; - }, - - findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) { - // Tests can implement this if they need to - }, - - uninstall: function(aAlwaysAllowUndo = false) { - if ((this.operationsRequiringRestart & AddonManager.OP_NEED_RESTART_UNINSTALL) - && this.pendingOperations & AddonManager.PENDING_UNINSTALL) - throw Components.Exception("Add-on is already pending uninstall"); - - var needsRestart = aAlwaysAllowUndo || !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL); - this.pendingOperations |= AddonManager.PENDING_UNINSTALL; - AddonManagerPrivate.callAddonListeners("onUninstalling", this, needsRestart); - if (!needsRestart) { - this.pendingOperations -= AddonManager.PENDING_UNINSTALL; - this._provider.removeAddon(this); - } else if (!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)) { - this.isActive = false; - } - }, - - cancelUninstall: function() { - if (!(this.pendingOperations & AddonManager.PENDING_UNINSTALL)) - throw Components.Exception("Add-on is not pending uninstall"); - - this.pendingOperations -= AddonManager.PENDING_UNINSTALL; - this.isActive = this.shouldBeActive; - AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); - }, - - markAsSeen: function() { - this.seen = true; - }, - - _updateActiveState: function(currentActive, newActive) { - if (currentActive == newActive) - return; - - if (newActive == this.isActive) { - this.pendingOperations -= (newActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE); - AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); - } - else if (newActive) { - let needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE); - this.pendingOperations |= AddonManager.PENDING_ENABLE; - AddonManagerPrivate.callAddonListeners("onEnabling", this, needsRestart); - if (!needsRestart) { - this.isActive = newActive; - this.pendingOperations -= AddonManager.PENDING_ENABLE; - AddonManagerPrivate.callAddonListeners("onEnabled", this); - } - } - else { - let needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE); - this.pendingOperations |= AddonManager.PENDING_DISABLE; - AddonManagerPrivate.callAddonListeners("onDisabling", this, needsRestart); - if (!needsRestart) { - this.isActive = newActive; - this.pendingOperations -= AddonManager.PENDING_DISABLE; - AddonManagerPrivate.callAddonListeners("onDisabled", this); - } - } - } -}; - -/** *** Mock AddonInstall object for the Mock Provider *****/ - -function MockInstall(aName, aType, aAddonToInstall) { - this.name = aName || ""; - // Don't expose type until download completed - this._type = aType || "extension"; - this.type = null; - this.version = "1.0"; - this.iconURL = ""; - this.infoURL = ""; - this.state = AddonManager.STATE_AVAILABLE; - this.error = 0; - this.sourceURI = null; - this.file = null; - this.progress = 0; - this.maxProgress = -1; - this.certificate = null; - this.certName = ""; - this.existingAddon = null; - this.addon = null; - this._addonToInstall = aAddonToInstall; - this.listeners = []; - - // Another type of install listener for tests that want to check the results - // of code run from standard install listeners - this.testListeners = []; -} - -MockInstall.prototype = { - install: function() { - switch (this.state) { - case AddonManager.STATE_AVAILABLE: - this.state = AddonManager.STATE_DOWNLOADING; - if (!this.callListeners("onDownloadStarted")) { - this.state = AddonManager.STATE_CANCELLED; - this.callListeners("onDownloadCancelled"); - return; - } - - this.type = this._type; - - // Adding addon to MockProvider to be implemented when needed - if (this._addonToInstall) - this.addon = this._addonToInstall; - else { - this.addon = new MockAddon("", this.name, this.type); - this.addon.version = this.version; - this.addon.pendingOperations = AddonManager.PENDING_INSTALL; - } - this.addon.install = this; - if (this.existingAddon) { - if (!this.addon.id) - this.addon.id = this.existingAddon.id; - this.existingAddon.pendingUpgrade = this.addon; - this.existingAddon.pendingOperations |= AddonManager.PENDING_UPGRADE; - } - - this.state = AddonManager.STATE_DOWNLOADED; - this.callListeners("onDownloadEnded"); - - case AddonManager.STATE_DOWNLOADED: - this.state = AddonManager.STATE_INSTALLING; - if (!this.callListeners("onInstallStarted")) { - this.state = AddonManager.STATE_CANCELLED; - this.callListeners("onInstallCancelled"); - return; - } - - let needsRestart = (this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_INSTALL); - AddonManagerPrivate.callAddonListeners("onInstalling", this.addon, needsRestart); - if (!needsRestart) { - AddonManagerPrivate.callAddonListeners("onInstalled", this.addon); - } - - this.state = AddonManager.STATE_INSTALLED; - this.callListeners("onInstallEnded"); - break; - case AddonManager.STATE_DOWNLOADING: - case AddonManager.STATE_CHECKING: - case AddonManager.STATE_INSTALLING: - // Installation is already running - return; - default: - ok(false, "Cannot start installing when state = " + this.state); - } - }, - - cancel: function() { - switch (this.state) { - case AddonManager.STATE_AVAILABLE: - this.state = AddonManager.STATE_CANCELLED; - break; - case AddonManager.STATE_INSTALLED: - this.state = AddonManager.STATE_CANCELLED; - this._provider.removeInstall(this); - this.callListeners("onInstallCancelled"); - break; - default: - // Handling cancelling when downloading to be implemented when needed - ok(false, "Cannot cancel when state = " + this.state); - } - }, - - - addListener: function(aListener) { - if (!this.listeners.some(i => i == aListener)) - this.listeners.push(aListener); - }, - - removeListener: function(aListener) { - this.listeners = this.listeners.filter(i => i != aListener); - }, - - addTestListener: function(aListener) { - if (!this.testListeners.some(i => i == aListener)) - this.testListeners.push(aListener); - }, - - removeTestListener: function(aListener) { - this.testListeners = this.testListeners.filter(i => i != aListener); - }, - - callListeners: function(aMethod) { - var result = AddonManagerPrivate.callInstallListeners(aMethod, this.listeners, - this, this.addon); - - // Call test listeners after standard listeners to remove race condition - // between standard and test listeners - for (let listener of this.testListeners) { - try { - if (aMethod in listener) - if (listener[aMethod].call(listener, this, this.addon) === false) - result = false; - } - catch (e) { - ok(false, "Test listener threw exception: " + e); - } - } - - return result; - } -}; - -function waitForCondition(condition, nextTest, errorMsg) { - let tries = 0; - let interval = setInterval(function() { - if (tries >= 30) { - ok(false, errorMsg); - moveOn(); - } - var conditionPassed; - try { - conditionPassed = condition(); - } catch (e) { - ok(false, e + "\n" + e.stack); - conditionPassed = false; - } - if (conditionPassed) { - moveOn(); - } - tries++; - }, 100); - let moveOn = function() { clearInterval(interval); nextTest(); }; -} - -function getTestPluginTag() { - let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - let tags = ph.getPluginTags(); - - // Find the test plugin - for (let i = 0; i < tags.length; i++) { - if (tags[i].name == "Test Plug-in") - return tags[i]; - } - ok(false, "Unable to find plugin"); - return null; -} diff --git a/toolkit/mozapps/extensions/test/browser/more_options.xul b/toolkit/mozapps/extensions/test/browser/more_options.xul deleted file mode 100644 index 28dbb0a2e..000000000 --- a/toolkit/mozapps/extensions/test/browser/more_options.xul +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/toolkit/mozapps/extensions/test/browser/moz.build b/toolkit/mozapps/extensions/test/browser/moz.build deleted file mode 100644 index af04aaeef..000000000 --- a/toolkit/mozapps/extensions/test/browser/moz.build +++ /dev/null @@ -1,10 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -BROWSER_CHROME_MANIFESTS += [ - 'browser-window.ini', - 'browser.ini', -] diff --git a/toolkit/mozapps/extensions/test/browser/options.xul b/toolkit/mozapps/extensions/test/browser/options.xul deleted file mode 100644 index 1b6827915..000000000 --- a/toolkit/mozapps/extensions/test/browser/options.xul +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Description Text Node - - This is a test, -

-

- - diff --git a/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs deleted file mode 100644 index 92bccd9ec..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/cookieRedirect.sjs +++ /dev/null @@ -1,24 +0,0 @@ -// Simple script redirects to the query part of the uri if the cookie "xpinstall" -// has the value "true", otherwise gives a 500 error. - -function handleRequest(request, response) -{ - let cookie = null; - if (request.hasHeader("Cookie")) { - let cookies = request.getHeader("Cookie").split(";"); - for (let i = 0; i < cookies.length; i++) { - if (cookies[i].substring(0, 10) == "xpinstall=") - cookie = cookies[i].substring(10); - } - } - - if (cookie == "true") { - response.setStatusLine(request.httpVersion, 302, "Found"); - response.setHeader("Location", request.queryString); - response.write("See " + request.queryString); - } - else { - response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); - response.write("Invalid request"); - } -} diff --git a/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi b/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi deleted file mode 100644 index 35d7bd5e5..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi +++ /dev/null @@ -1 +0,0 @@ -This is a corrupt zip file diff --git a/toolkit/mozapps/extensions/test/xpinstall/empty.xpi b/toolkit/mozapps/extensions/test/xpinstall/empty.xpi deleted file mode 100644 index 74ed2b817..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/empty.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/enabled.html b/toolkit/mozapps/extensions/test/xpinstall/enabled.html deleted file mode 100644 index 370cde8fb..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/enabled.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - -InstallTrigger tests - - - -

InstallTrigger tests

-

- - diff --git a/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs deleted file mode 100644 index 324a092a3..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs +++ /dev/null @@ -1,15 +0,0 @@ -// Simple script redirects takes the query part of te request and splits it on -// the | character. Anything before is included as the X-Target-Digest header -// the latter part is used as the url to redirect to - -function handleRequest(request, response) -{ - let pos = request.queryString.indexOf("|"); - let header = request.queryString.substring(0, pos); - let url = request.queryString.substring(pos + 1); - - response.setStatusLine(request.httpVersion, 302, "Found"); - response.setHeader("X-Target-Digest", header); - response.setHeader("Location", url); - response.write("See " + url); -} diff --git a/toolkit/mozapps/extensions/test/xpinstall/head.js b/toolkit/mozapps/extensions/test/xpinstall/head.js deleted file mode 100644 index 197fe3fac..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/head.js +++ /dev/null @@ -1,434 +0,0 @@ -const RELATIVE_DIR = "toolkit/mozapps/extensions/test/xpinstall/"; - -const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; -const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR; -const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; -const PROMPT_URL = "chrome://global/content/commonDialog.xul"; -const ADDONS_URL = "chrome://mozapps/content/extensions/extensions.xul"; -const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; -const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; -const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin"; -const PREF_CUSTOM_CONFIRMATION_UI = "xpinstall.customConfirmationUI"; -const CHROME_NAME = "mochikit"; - -function getChromeRoot(path) { - if (path === undefined) { - return "chrome://" + CHROME_NAME + "/content/browser/" + RELATIVE_DIR - } - return getRootDirectory(path); -} - -function extractChromeRoot(path) { - var chromeRootPath = getChromeRoot(path); - var jar = getJar(chromeRootPath); - if (jar) { - var tmpdir = extractJarToTmp(jar); - return "file://" + tmpdir.path + "/"; - } - return chromeRootPath; -} - -Services.prefs.setBoolPref(PREF_CUSTOM_CONFIRMATION_UI, false); -registerCleanupFunction(() => { - Services.prefs.clearUserPref(PREF_CUSTOM_CONFIRMATION_UI); -}); - -/** - * This is a test harness designed to handle responding to UI during the process - * of installing an XPI. A test can set callbacks to hear about specific parts - * of the sequence. - * Before use setup must be called and finish must be called afterwards. - */ -var Harness = { - // If set then the callback is called when an install is attempted and - // software installation is disabled. - installDisabledCallback: null, - // If set then the callback is called when an install is attempted and - // then canceled. - installCancelledCallback: null, - // If set then the callback will be called when an install's origin is blocked. - installOriginBlockedCallback: null, - // If set then the callback will be called when an install is blocked by the - // whitelist. The callback should return true to continue with the install - // anyway. - installBlockedCallback: null, - // If set will be called in the event of authentication being needed to get - // the xpi. Should return a 2 element array of username and password, or - // null to not authenticate. - authenticationCallback: null, - // If set this will be called to allow checking the contents of the xpinstall - // confirmation dialog. The callback should return true to continue the install. - installConfirmCallback: null, - // If set will be called when downloading of an item has begun. - downloadStartedCallback: null, - // If set will be called during the download of an item. - downloadProgressCallback: null, - // If set will be called when an xpi fails to download. - downloadFailedCallback: null, - // If set will be called when an xpi download is cancelled. - downloadCancelledCallback: null, - // If set will be called when downloading of an item has ended. - downloadEndedCallback: null, - // If set will be called when installation by the extension manager of an xpi - // item starts - installStartedCallback: null, - // If set will be called when an xpi fails to install. - installFailedCallback: null, - // If set will be called when each xpi item to be installed completes - // installation. - installEndedCallback: null, - // If set will be called when all triggered items are installed or the install - // is canceled. - installsCompletedCallback: null, - // If set the harness will wait for this DOM event before calling - // installsCompletedCallback - finalContentEvent: null, - - waitingForEvent: false, - pendingCount: null, - installCount: null, - runningInstalls: null, - - waitingForFinish: false, - - // A unique value to return from the installConfirmCallback to indicate that - // the install UI shouldn't be closed automatically - leaveOpen: {}, - - // Setup and tear down functions - setup: function() { - if (!this.waitingForFinish) { - waitForExplicitFinish(); - this.waitingForFinish = true; - - Services.prefs.setBoolPref(PREF_INSTALL_REQUIRESECUREORIGIN, false); - - Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); - Services.obs.addObserver(this, "addon-install-started", false); - Services.obs.addObserver(this, "addon-install-disabled", false); - Services.obs.addObserver(this, "addon-install-origin-blocked", false); - Services.obs.addObserver(this, "addon-install-blocked", false); - Services.obs.addObserver(this, "addon-install-failed", false); - Services.obs.addObserver(this, "addon-install-complete", false); - - AddonManager.addInstallListener(this); - - Services.wm.addListener(this); - - var self = this; - registerCleanupFunction(function() { - Services.prefs.clearUserPref(PREF_LOGGING_ENABLED); - Services.prefs.clearUserPref(PREF_INSTALL_REQUIRESECUREORIGIN); - Services.obs.removeObserver(self, "addon-install-started"); - Services.obs.removeObserver(self, "addon-install-disabled"); - Services.obs.removeObserver(self, "addon-install-origin-blocked"); - Services.obs.removeObserver(self, "addon-install-blocked"); - Services.obs.removeObserver(self, "addon-install-failed"); - Services.obs.removeObserver(self, "addon-install-complete"); - - AddonManager.removeInstallListener(self); - - Services.wm.removeListener(self); - - AddonManager.getAllInstalls(function(aInstalls) { - is(aInstalls.length, 0, "Should be no active installs at the end of the test"); - aInstalls.forEach(function(aInstall) { - info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state); - aInstall.cancel(); - }); - }); - }); - } - - this.installCount = 0; - this.pendingCount = 0; - this.runningInstalls = []; - }, - - finish: function() { - finish(); - }, - - endTest: function() { - let callback = this.installsCompletedCallback; - let count = this.installCount; - - is(this.runningInstalls.length, 0, "Should be no running installs left"); - this.runningInstalls.forEach(function(aInstall) { - info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state); - }); - - this.installOriginBlockedCallback = null; - this.installBlockedCallback = null; - this.authenticationCallback = null; - this.installConfirmCallback = null; - this.downloadStartedCallback = null; - this.downloadProgressCallback = null; - this.downloadCancelledCallback = null; - this.downloadFailedCallback = null; - this.downloadEndedCallback = null; - this.installStartedCallback = null; - this.installFailedCallback = null; - this.installEndedCallback = null; - this.installsCompletedCallback = null; - this.runningInstalls = null; - - if (callback) - executeSoon(() => callback(count)); - }, - - // Window open handling - windowReady: function(window) { - if (window.document.location.href == XPINSTALL_URL) { - if (this.installBlockedCallback) - ok(false, "Should have been blocked by the whitelist"); - this.pendingCount = window.document.getElementById("itemList").childNodes.length; - - // If there is a confirm callback then its return status determines whether - // to install the items or not. If not the test is over. - let result = true; - if (this.installConfirmCallback) { - result = this.installConfirmCallback(window); - if (result === this.leaveOpen) - return; - } - - if (!result) { - window.document.documentElement.cancelDialog(); - } - else { - // Initially the accept button is disabled on a countdown timer - var button = window.document.documentElement.getButton("accept"); - button.disabled = false; - window.document.documentElement.acceptDialog(); - } - } - else if (window.document.location.href == PROMPT_URL) { - var promptType = window.args.promptType; - switch (promptType) { - case "alert": - case "alertCheck": - case "confirmCheck": - case "confirm": - case "confirmEx": - window.document.documentElement.acceptDialog(); - break; - case "promptUserAndPass": - // This is a login dialog, hopefully an authentication prompt - // for the xpi. - if (this.authenticationCallback) { - var auth = this.authenticationCallback(); - if (auth && auth.length == 2) { - window.document.getElementById("loginTextbox").value = auth[0]; - window.document.getElementById("password1Textbox").value = auth[1]; - window.document.documentElement.acceptDialog(); - } - else { - window.document.documentElement.cancelDialog(); - } - } - else { - window.document.documentElement.cancelDialog(); - } - break; - default: - ok(false, "prompt type " + promptType + " not handled in test."); - break; - } - } - }, - - // Install blocked handling - - installDisabled: function(installInfo) { - ok(!!this.installDisabledCallback, "Installation shouldn't have been disabled"); - if (this.installDisabledCallback) - this.installDisabledCallback(installInfo); - this.expectingCancelled = true; - this.expectingCancelled = false; - this.endTest(); - }, - - installCancelled: function(installInfo) { - if (this.expectingCancelled) - return; - - ok(!!this.installCancelledCallback, "Installation shouldn't have been cancelled"); - if (this.installCancelledCallback) - this.installCancelledCallback(installInfo); - this.endTest(); - }, - - installOriginBlocked: function(installInfo) { - ok(!!this.installOriginBlockedCallback, "Shouldn't have been blocked"); - if (this.installOriginBlockedCallback) - this.installOriginBlockedCallback(installInfo); - this.endTest(); - }, - - installBlocked: function(installInfo) { - ok(!!this.installBlockedCallback, "Shouldn't have been blocked by the whitelist"); - if (this.installBlockedCallback && this.installBlockedCallback(installInfo)) { - this.installBlockedCallback = null; - installInfo.install(); - } - else { - this.expectingCancelled = true; - installInfo.installs.forEach(function(install) { - install.cancel(); - }); - this.expectingCancelled = false; - this.endTest(); - } - }, - - // nsIWindowMediatorListener - - onWindowTitleChange: function(window, title) { - }, - - onOpenWindow: function(window) { - var domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) - .getInterface(Components.interfaces.nsIDOMWindow); - var self = this; - waitForFocus(function() { - self.windowReady(domwindow); - }, domwindow); - }, - - onCloseWindow: function(window) { - }, - - // Addon Install Listener - - onNewInstall: function(install) { - this.runningInstalls.push(install); - - if (this.finalContentEvent && !this.waitingForEvent) { - this.waitingForEvent = true; - info("Waiting for " + this.finalContentEvent); - let mm = gBrowser.selectedBrowser.messageManager; - mm.loadFrameScript(`data:,content.addEventListener("${this.finalContentEvent}", () => { sendAsyncMessage("Test:GotNewInstallEvent"); });`, false); - let win = gBrowser.contentWindow; - let listener = () => { - info("Saw " + this.finalContentEvent); - mm.removeMessageListener("Test:GotNewInstallEvent", listener); - this.waitingForEvent = false; - if (this.pendingCount == 0) - this.endTest(); - } - mm.addMessageListener("Test:GotNewInstallEvent", listener); - } - }, - - onDownloadStarted: function(install) { - this.pendingCount++; - if (this.downloadStartedCallback) - this.downloadStartedCallback(install); - }, - - onDownloadProgress: function(install) { - if (this.downloadProgressCallback) - this.downloadProgressCallback(install); - }, - - onDownloadEnded: function(install) { - if (this.downloadEndedCallback) - this.downloadEndedCallback(install); - }, - - onDownloadCancelled: function(install) { - isnot(this.runningInstalls.indexOf(install), -1, - "Should only see cancelations for started installs"); - this.runningInstalls.splice(this.runningInstalls.indexOf(install), 1); - - if (this.downloadCancelledCallback) - this.downloadCancelledCallback(install); - this.checkTestEnded(); - }, - - onDownloadFailed: function(install) { - if (this.downloadFailedCallback) - this.downloadFailedCallback(install); - this.checkTestEnded(); - }, - - onInstallStarted: function(install) { - if (this.installStartedCallback) - this.installStartedCallback(install); - }, - - onInstallEnded: function(install, addon) { - if (this.installEndedCallback) - this.installEndedCallback(install, addon); - this.installCount++; - this.checkTestEnded(); - }, - - onInstallFailed: function(install) { - if (this.installFailedCallback) - this.installFailedCallback(install); - this.checkTestEnded(); - }, - - checkTestEnded: function() { - if (--this.pendingCount == 0 && !this.waitingForEvent) - this.endTest(); - }, - - // nsIObserver - - observe: function(subject, topic, data) { - var installInfo = subject.QueryInterface(Components.interfaces.amIWebInstallInfo); - switch (topic) { - case "addon-install-started": - is(this.runningInstalls.length, installInfo.installs.length, - "Should have seen the expected number of installs started"); - break; - case "addon-install-disabled": - this.installDisabled(installInfo); - break; - case "addon-install-cancelled": - this.installCancelled(installInfo); - break; - case "addon-install-origin-blocked": - this.installOriginBlocked(installInfo); - break; - case "addon-install-blocked": - this.installBlocked(installInfo); - break; - case "addon-install-failed": - installInfo.installs.forEach(function(aInstall) { - isnot(this.runningInstalls.indexOf(aInstall), -1, - "Should only see failures for started installs"); - - ok(aInstall.error != 0 || aInstall.addon.appDisabled, - "Failed installs should have an error or be appDisabled"); - - this.runningInstalls.splice(this.runningInstalls.indexOf(aInstall), 1); - }, this); - break; - case "addon-install-complete": - installInfo.installs.forEach(function(aInstall) { - isnot(this.runningInstalls.indexOf(aInstall), -1, - "Should only see completed events for started installs"); - - is(aInstall.error, 0, "Completed installs should have no error"); - ok(!aInstall.appDisabled, "Completed installs should not be appDisabled"); - - // Complete installs are either in the INSTALLED or CANCELLED state - // since the test may cancel installs the moment they complete. - ok(aInstall.state == AddonManager.STATE_INSTALLED || - aInstall.state == AddonManager.STATE_CANCELLED, - "Completed installs should be in the right state"); - - this.runningInstalls.splice(this.runningInstalls.indexOf(aInstall), 1); - }, this); - break; - } - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, - Ci.nsIWindowMediatorListener, - Ci.nsISupports]) -} diff --git a/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi b/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi deleted file mode 100644 index 262ed38a7..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/installchrome.html b/toolkit/mozapps/extensions/test/xpinstall/installchrome.html deleted file mode 100644 index 6abee2ef3..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/installchrome.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - -InstallTrigger tests - - - -

InstallTrigger tests

- - diff --git a/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html b/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html deleted file mode 100644 index 65cab1ef1..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - -InstallTrigger tests - - - -

InstallTrigger tests

-

-

- - diff --git a/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html b/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html deleted file mode 100644 index 2b302642e..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/installtrigger_frame.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - -InstallTrigger frame tests - - - - - - -

InstallTrigger tests

-

-

- - diff --git a/toolkit/mozapps/extensions/test/xpinstall/multipackage.xpi b/toolkit/mozapps/extensions/test/xpinstall/multipackage.xpi deleted file mode 100644 index d52f28c28..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/multipackage.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/navigate.html b/toolkit/mozapps/extensions/test/xpinstall/navigate.html deleted file mode 100644 index 5a6903eb9..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/navigate.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - -Navigation tests - - - - -

Test Link

- - diff --git a/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs b/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs deleted file mode 100644 index d248bfbc7..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs +++ /dev/null @@ -1,45 +0,0 @@ -// Script has two modes based on the query string. If the mode is "setup" then -// parameters from the query string configure the redirection. If the mode is -// "redirect" then a redirect is returned - -function handleRequest(request, response) -{ - let parts = request.queryString.split("&"); - let settings = {}; - - parts.forEach(function(aString) { - let [k, v] = aString.split("="); - settings[k] = decodeURIComponent(v); - }) - - if (settings.mode == "setup") { - delete settings.mode; - - // Object states must be an nsISupports - var state = { - settings: settings, - QueryInterface: function(aIid) { - if (aIid.equals(Components.interfaces.nsISupports)) - return settings; - throw Components.results.NS_ERROR_NO_INTERFACE; - } - } - state.wrappedJSObject = state; - - setObjectState("xpinstall-redirect-settings", state); - response.setStatusLine(request.httpVersion, 200, "Ok"); - response.setHeader("Content-Type", "text/plain"); - response.write("Setup complete"); - } - else if (settings.mode == "redirect") { - getObjectState("xpinstall-redirect-settings", function(aObject) { - settings = aObject.wrappedJSObject.settings; - }); - - response.setStatusLine(request.httpVersion, 302, "Found"); - for (var name in settings) { - response.setHeader(name, settings[name]); - } - response.write("Done"); - } -} diff --git a/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi b/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi deleted file mode 100644 index 8e76bd052..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi b/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi deleted file mode 100644 index 9fee8f60b..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-multipackage.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-multipackage.xpi deleted file mode 100644 index 11fbe1861..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/signed-multipackage.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-no-cn.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-no-cn.xpi deleted file mode 100644 index 90d3a3ce6..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/signed-no-cn.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-no-o.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-no-o.xpi deleted file mode 100644 index 19b754038..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/signed-no-o.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-tampered.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-tampered.xpi deleted file mode 100644 index 8c951881e..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/signed-tampered.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed-untrusted.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed-untrusted.xpi deleted file mode 100644 index 09789d189..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/signed-untrusted.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed.xpi deleted file mode 100644 index bd7f78b7c..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/signed.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/signed2.xpi b/toolkit/mozapps/extensions/test/xpinstall/signed2.xpi deleted file mode 100644 index 085efbbf7..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/signed2.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs b/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs deleted file mode 100644 index 5f767a8f4..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs +++ /dev/null @@ -1,101 +0,0 @@ -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); - -const RELATIVE_PATH = "browser/toolkit/mozapps/extensions/test/xpinstall" -const NOTIFICATION_TOPIC = "slowinstall-complete"; - -/** - * Helper function to create a JS object representing the url parameters from - * the request's queryString. - * - * @param aQueryString - * The request's query string. - * @return A JS object representing the url parameters from the request's - * queryString. - */ -function parseQueryString(aQueryString) { - var paramArray = aQueryString.split("&"); - var regex = /^([^=]+)=(.*)$/; - var params = {}; - for (var i = 0, sz = paramArray.length; i < sz; i++) { - var match = regex.exec(paramArray[i]); - if (!match) - throw "Bad parameter in queryString! '" + paramArray[i] + "'"; - params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]); - } - - return params; -} - -function handleRequest(aRequest, aResponse) { - let id = +getState("ID"); - setState("ID", "" + (id + 1)); - - function LOG(str) { - dump("slowinstall.sjs[" + id + "]: " + str + "\n"); - } - - aResponse.setStatusLine(aRequest.httpVersion, 200, "OK"); - - var params = { }; - if (aRequest.queryString) - params = parseQueryString(aRequest.queryString); - - if (params.file) { - let xpiFile = ""; - - function complete_download() { - LOG("Completing download"); - downloadPaused = false; - - try { - // Doesn't seem to be a sane way to read using OS.File and write to an - // nsIOutputStream so here we are. - let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); - file.initWithPath(xpiFile); - let stream = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - stream.init(file, -1, -1, stream.DEFER_OPEN + stream.CLOSE_ON_EOF); - - NetUtil.asyncCopy(stream, aResponse.bodyOutputStream, () => { - LOG("Download complete"); - aResponse.finish(); - }); - } - catch (e) { - LOG("Exception " + e); - } - } - - let waitForComplete = new Promise(resolve => { - function complete() { - Services.obs.removeObserver(complete, NOTIFICATION_TOPIC); - resolve(); - } - - Services.obs.addObserver(complete, NOTIFICATION_TOPIC, false); - }); - - aResponse.processAsync(); - - OS.File.getCurrentDirectory().then(dir => { - xpiFile = OS.Path.join(dir, ...RELATIVE_PATH.split("/"), params.file); - LOG("Starting slow download of " + xpiFile); - - OS.File.stat(xpiFile).then(info => { - aResponse.setHeader("Content-Type", "binary/octet-stream"); - aResponse.setHeader("Content-Length", info.size.toString()); - - LOG("Download paused"); - waitForComplete.then(complete_download); - }); - }); - } - else if (params.continue) { - dump("slowinstall.sjs: Received signal to complete all current downloads.\n"); - Services.obs.notifyObservers(null, NOTIFICATION_TOPIC, null); - } -} diff --git a/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html b/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html deleted file mode 100644 index 50083ca90..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/startsoftwareupdate.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -InstallTrigger tests - - - -

InstallTrigger tests

- - diff --git a/toolkit/mozapps/extensions/test/xpinstall/theme.xpi b/toolkit/mozapps/extensions/test/xpinstall/theme.xpi deleted file mode 100644 index 74e650b4a..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/theme.xpi and /dev/null differ diff --git a/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html b/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html deleted file mode 100644 index 42e0e1cd0..000000000 --- a/toolkit/mozapps/extensions/test/xpinstall/triggerredirect.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - -InstallTrigger tests - - - -

InstallTrigger tests

-

-

- - diff --git a/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi b/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi deleted file mode 100644 index 51b00475a..000000000 Binary files a/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi and /dev/null differ diff --git a/toolkit/mozapps/webextensions/.eslintrc.js b/toolkit/mozapps/webextensions/.eslintrc.js new file mode 100644 index 000000000..2b90bd053 --- /dev/null +++ b/toolkit/mozapps/webextensions/.eslintrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { // eslint-disable-line no-undef + "rules": { + // No using undeclared variables + "no-undef": "error", + } +}; diff --git a/toolkit/mozapps/webextensions/AddonContentPolicy.cpp b/toolkit/mozapps/webextensions/AddonContentPolicy.cpp new file mode 100644 index 000000000..90e53b2ea --- /dev/null +++ b/toolkit/mozapps/webextensions/AddonContentPolicy.cpp @@ -0,0 +1,478 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AddonContentPolicy.h" + +#include "mozilla/dom/nsCSPUtils.h" +#include "nsCOMPtr.h" +#include "nsContentPolicyUtils.h" +#include "nsContentTypeParser.h" +#include "nsContentUtils.h" +#include "nsIConsoleService.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIEffectiveTLDService.h" +#include "nsIScriptError.h" +#include "nsIStringBundle.h" +#include "nsIUUIDGenerator.h" +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" + +using namespace mozilla; + +/* Enforces content policies for WebExtension scopes. Currently: + * + * - Prevents loading scripts with a non-default JavaScript version. + * - Checks custom content security policies for sufficiently stringent + * script-src and object-src directives. + */ + +#define VERSIONED_JS_BLOCKED_MESSAGE \ + u"Versioned JavaScript is a non-standard, deprecated extension, and is " \ + u"not supported in WebExtension code. For alternatives, please see: " \ + u"https://developer.mozilla.org/Add-ons/WebExtensions/Tips" + +AddonContentPolicy::AddonContentPolicy() +{ +} + +AddonContentPolicy::~AddonContentPolicy() +{ +} + +NS_IMPL_ISUPPORTS(AddonContentPolicy, nsIContentPolicy, nsIAddonContentPolicy) + +static nsresult +GetWindowIDFromContext(nsISupports* aContext, uint64_t *aResult) +{ + NS_ENSURE_TRUE(aContext, NS_ERROR_FAILURE); + + nsCOMPtr content = do_QueryInterface(aContext); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + + nsCOMPtr document = content->OwnerDoc(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + nsCOMPtr window = document->GetInnerWindow(); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + *aResult = window->WindowID(); + return NS_OK; +} + +static nsresult +LogMessage(const nsAString &aMessage, nsIURI* aSourceURI, const nsAString &aSourceSample, + nsISupports* aContext) +{ + nsCOMPtr error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + NS_ENSURE_TRUE(error, NS_ERROR_OUT_OF_MEMORY); + + nsCString sourceName = aSourceURI->GetSpecOrDefault(); + + uint64_t windowID = 0; + GetWindowIDFromContext(aContext, &windowID); + + nsresult rv = + error->InitWithWindowID(aMessage, NS_ConvertUTF8toUTF16(sourceName), + aSourceSample, 0, 0, nsIScriptError::errorFlag, + "JavaScript", windowID); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr console = do_GetService(NS_CONSOLESERVICE_CONTRACTID); + NS_ENSURE_TRUE(console, NS_ERROR_OUT_OF_MEMORY); + + console->LogMessage(error); + return NS_OK; +} + + +// Content policy enforcement: + +NS_IMETHODIMP +AddonContentPolicy::ShouldLoad(uint32_t aContentType, + nsIURI* aContentLocation, + nsIURI* aRequestOrigin, + nsISupports* aContext, + const nsACString& aMimeTypeGuess, + nsISupports* aExtra, + nsIPrincipal* aRequestPrincipal, + int16_t* aShouldLoad) +{ + MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), + "We should only see external content policy types here."); + + *aShouldLoad = nsIContentPolicy::ACCEPT; + + if (!aRequestOrigin) { + return NS_OK; + } + + // Only apply this policy to requests from documents loaded from + // moz-extension URLs, or to resources being loaded from moz-extension URLs. + bool equals; + if (!((NS_SUCCEEDED(aContentLocation->SchemeIs("moz-extension", &equals)) && equals) || + (NS_SUCCEEDED(aRequestOrigin->SchemeIs("moz-extension", &equals)) && equals))) { + return NS_OK; + } + + if (aContentType == nsIContentPolicy::TYPE_SCRIPT) { + NS_ConvertUTF8toUTF16 typeString(aMimeTypeGuess); + nsContentTypeParser mimeParser(typeString); + + // Reject attempts to load JavaScript scripts with a non-default version. + nsAutoString mimeType, version; + if (NS_SUCCEEDED(mimeParser.GetType(mimeType)) && + nsContentUtils::IsJavascriptMIMEType(mimeType) && + NS_SUCCEEDED(mimeParser.GetParameter("version", version))) { + *aShouldLoad = nsIContentPolicy::REJECT_REQUEST; + + LogMessage(NS_MULTILINE_LITERAL_STRING(VERSIONED_JS_BLOCKED_MESSAGE), + aRequestOrigin, typeString, aContext); + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +AddonContentPolicy::ShouldProcess(uint32_t aContentType, + nsIURI* aContentLocation, + nsIURI* aRequestOrigin, + nsISupports* aRequestingContext, + const nsACString& aMimeTypeGuess, + nsISupports* aExtra, + nsIPrincipal* aRequestPrincipal, + int16_t* aShouldProcess) +{ + MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), + "We should only see external content policy types here."); + + *aShouldProcess = nsIContentPolicy::ACCEPT; + return NS_OK; +} + + +// CSP Validation: + +static const char* allowedSchemes[] = { + "blob", + "filesystem", + nullptr +}; + +static const char* allowedHostSchemes[] = { + "https", + "moz-extension", + nullptr +}; + +/** + * Validates a CSP directive to ensure that it is sufficiently stringent. + * In particular, ensures that: + * + * - No remote sources are allowed other than from https: schemes + * + * - No remote sources specify host wildcards for generic domains + * (*.blogspot.com, *.com, *) + * + * - All remote sources and local extension sources specify a host + * + * - No scheme sources are allowed other than blob:, filesystem:, + * moz-extension:, and https: + * + * - No keyword sources are allowed other than 'none', 'self', 'unsafe-eval', + * and hash sources. + */ +class CSPValidator final : public nsCSPSrcVisitor { + public: + CSPValidator(nsAString& aURL, CSPDirective aDirective, bool aDirectiveRequired = true) : + mURL(aURL), + mDirective(CSP_CSPDirectiveToString(aDirective)), + mFoundSelf(false) + { + // Start with the default error message for a missing directive, since no + // visitors will be called if the directive isn't present. + if (aDirectiveRequired) { + FormatError("csp.error.missing-directive"); + } + } + + // Visitors + + bool visitSchemeSrc(const nsCSPSchemeSrc& src) override + { + nsAutoString scheme; + src.getScheme(scheme); + + if (SchemeInList(scheme, allowedHostSchemes)) { + FormatError("csp.error.missing-host", scheme); + return false; + } + if (!SchemeInList(scheme, allowedSchemes)) { + FormatError("csp.error.illegal-protocol", scheme); + return false; + } + return true; + }; + + bool visitHostSrc(const nsCSPHostSrc& src) override + { + nsAutoString scheme, host; + + src.getScheme(scheme); + src.getHost(host); + + if (scheme.LowerCaseEqualsLiteral("https")) { + if (!HostIsAllowed(host)) { + FormatError("csp.error.illegal-host-wildcard", scheme); + return false; + } + } else if (scheme.LowerCaseEqualsLiteral("moz-extension")) { + // The CSP parser silently converts 'self' keywords to the origin + // URL, so we need to reconstruct the URL to see if it was present. + if (!mFoundSelf) { + nsAutoString url(u"moz-extension://"); + url.Append(host); + + mFoundSelf = url.Equals(mURL); + } + + if (host.IsEmpty() || host.EqualsLiteral("*")) { + FormatError("csp.error.missing-host", scheme); + return false; + } + } else if (!SchemeInList(scheme, allowedSchemes)) { + FormatError("csp.error.illegal-protocol", scheme); + return false; + } + + return true; + }; + + bool visitKeywordSrc(const nsCSPKeywordSrc& src) override + { + switch (src.getKeyword()) { + case CSP_NONE: + case CSP_SELF: + case CSP_UNSAFE_EVAL: + return true; + + default: + NS_ConvertASCIItoUTF16 keyword(CSP_EnumToKeyword(src.getKeyword())); + + FormatError("csp.error.illegal-keyword", keyword); + return false; + } + }; + + bool visitNonceSrc(const nsCSPNonceSrc& src) override + { + FormatError("csp.error.illegal-keyword", NS_LITERAL_STRING("'nonce-*'")); + return false; + }; + + bool visitHashSrc(const nsCSPHashSrc& src) override + { + return true; + }; + + // Accessors + + inline nsAString& GetError() + { + return mError; + }; + + inline bool FoundSelf() + { + return mFoundSelf; + }; + + + // Formatters + + template + inline void FormatError(const char* aName, const T ...aParams) + { + const char16_t* params[] = { mDirective.get(), aParams.get()... }; + FormatErrorParams(aName, params, MOZ_ARRAY_LENGTH(params)); + }; + + private: + // Validators + + bool HostIsAllowed(nsAString& host) + { + if (host.First() == '*') { + if (host.EqualsLiteral("*") || host[1] != '.') { + return false; + } + + host.Cut(0, 2); + + nsCOMPtr tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + + if (!tldService) { + return false; + } + + NS_ConvertUTF16toUTF8 cHost(host); + nsAutoCString publicSuffix; + + nsresult rv = tldService->GetPublicSuffixFromHost(cHost, publicSuffix); + + return NS_SUCCEEDED(rv) && !cHost.Equals(publicSuffix); + } + + return true; + }; + + bool SchemeInList(nsAString& scheme, const char** schemes) + { + for (; *schemes; schemes++) { + if (scheme.LowerCaseEqualsASCII(*schemes)) { + return true; + } + } + return false; + }; + + + // Formatters + + already_AddRefed + GetStringBundle() + { + nsCOMPtr sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, nullptr); + + nsCOMPtr stringBundle; + sbs->CreateBundle("chrome://global/locale/extensions.properties", + getter_AddRefs(stringBundle)); + + return stringBundle.forget(); + }; + + void FormatErrorParams(const char* aName, const char16_t** aParams, int32_t aLength) + { + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr stringBundle = GetStringBundle(); + + if (stringBundle) { + NS_ConvertASCIItoUTF16 name(aName); + + rv = stringBundle->FormatStringFromName(name.get(), aParams, aLength, + getter_Copies(mError)); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + mError.AssignLiteral("An unexpected error occurred"); + } + }; + + + // Data members + + nsAutoString mURL; + NS_ConvertASCIItoUTF16 mDirective; + nsXPIDLString mError; + + bool mFoundSelf; +}; + +/** + * Validates a custom content security policy string for use by an add-on. + * In particular, ensures that: + * + * - Both object-src and script-src directives are present, and meet + * the policies required by the CSPValidator class + * + * - The script-src directive includes the source 'self' + */ +NS_IMETHODIMP +AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString, + nsAString& aResult) +{ + nsresult rv; + + // Validate against a randomly-generated extension origin. + // There is no add-on-specific behavior in the CSP code, beyond the ability + // for add-ons to specify a custom policy, but the parser requires a valid + // origin in order to operate correctly. + nsAutoString url(u"moz-extension://"); + { + nsCOMPtr uuidgen = services::GetUUIDGenerator(); + NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE); + + nsID id; + rv = uuidgen->GenerateUUIDInPlace(&id); + NS_ENSURE_SUCCESS(rv, rv); + + char idString[NSID_LENGTH]; + id.ToProvidedString(idString); + + MOZ_RELEASE_ASSERT(idString[0] == '{' && idString[NSID_LENGTH - 2] == '}', + "UUID generator did not return a valid UUID"); + + url.AppendASCII(idString + 1, NSID_LENGTH - 3); + } + + + RefPtr principal = + BasePrincipal::CreateCodebasePrincipal(NS_ConvertUTF16toUTF8(url)); + + nsCOMPtr csp; + rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp)); + NS_ENSURE_SUCCESS(rv, rv); + + + csp->AppendPolicy(aPolicyString, false, false); + + const nsCSPPolicy* policy = csp->GetPolicy(0); + if (!policy) { + CSPValidator validator(url, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE); + aResult.Assign(validator.GetError()); + return NS_OK; + } + + bool haveValidDefaultSrc = false; + { + CSPDirective directive = nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; + CSPValidator validator(url, directive); + + haveValidDefaultSrc = policy->visitDirectiveSrcs(directive, &validator); + } + + aResult.SetIsVoid(true); + { + CSPDirective directive = nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE; + CSPValidator validator(url, directive, !haveValidDefaultSrc); + + if (!policy->visitDirectiveSrcs(directive, &validator)) { + aResult.Assign(validator.GetError()); + } else if (!validator.FoundSelf()) { + validator.FormatError("csp.error.missing-source", NS_LITERAL_STRING("'self'")); + aResult.Assign(validator.GetError()); + } + } + + if (aResult.IsVoid()) { + CSPDirective directive = nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE; + CSPValidator validator(url, directive, !haveValidDefaultSrc); + + if (!policy->visitDirectiveSrcs(directive, &validator)) { + aResult.Assign(validator.GetError()); + } + } + + return NS_OK; +} diff --git a/toolkit/mozapps/webextensions/AddonContentPolicy.h b/toolkit/mozapps/webextensions/AddonContentPolicy.h new file mode 100644 index 000000000..4c8af4828 --- /dev/null +++ b/toolkit/mozapps/webextensions/AddonContentPolicy.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIContentPolicy.h" +#include "nsIAddonPolicyService.h" + +class AddonContentPolicy : public nsIContentPolicy, + public nsIAddonContentPolicy +{ +protected: + virtual ~AddonContentPolicy(); + +public: + AddonContentPolicy(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTPOLICY + NS_DECL_NSIADDONCONTENTPOLICY +}; diff --git a/toolkit/mozapps/webextensions/AddonManager.jsm b/toolkit/mozapps/webextensions/AddonManager.jsm new file mode 100644 index 000000000..c5cb80091 --- /dev/null +++ b/toolkit/mozapps/webextensions/AddonManager.jsm @@ -0,0 +1,3674 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as +// most tests later register different nsIAppInfo implementations, which +// wouldn't be reflected in Services.appinfo anymore, as the lazy getter +// underlying it would have been initialized if we used it here. +if ("@mozilla.org/xre/app-info;1" in Cc) { + let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); + if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { + // Refuse to run in child processes. + throw new Error("You cannot use the AddonManager in child processes!"); + } +} + +Cu.import("resource://gre/modules/AppConstants.jsm"); + +const MOZ_COMPATIBILITY_NIGHTLY = !['aurora', 'beta', 'release', 'esr'].includes(AppConstants.MOZ_UPDATE_CHANNEL); + +const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; +const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled"; +const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled"; +const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; +const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion"; +const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; +const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; +const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; +const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url"; +const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; +const PREF_APP_UPDATE_AUTO = "app.update.auto"; +const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; +const PREF_EM_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion"; +const PREF_EM_HOTFIX_URL = "extensions.hotfix.url"; +const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; +const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; +const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; +const PREF_SELECTED_LOCALE = "general.useragent.locale"; +const UNKNOWN_XPCOM_ABI = "unknownABI"; + +const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; +const PREF_WEBAPI_TESTING = "extensions.webapi.testing"; + +const UPDATE_REQUEST_VERSION = 2; +const CATEGORY_UPDATE_PARAMS = "extension-update-params"; + +const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; + +const KEY_PROFILEDIR = "ProfD"; +const KEY_APPDIR = "XCurProcD"; +const FILE_BLOCKLIST = "blocklist.xml"; + +const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi; +const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility"; +var PREF_EM_CHECK_COMPATIBILITY = MOZ_COMPATIBILITY_NIGHTLY ? + PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly" : + undefined; + +const TOOLKIT_ID = "toolkit@mozilla.org"; + +const VALID_TYPES_REGEXP = /^[\w\-]+$/; + +const WEBAPI_INSTALL_HOSTS = ["addons.mozilla.org", "testpilot.firefox.com"]; +const WEBAPI_TEST_INSTALL_HOSTS = [ + "addons.allizom.org", "addons-dev.allizom.org", + "testpilot.stage.mozaws.net", "testpilot.dev.mozaws.net", + "example.com", +]; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/AsyncShutdown.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", + "resource://gre/modules/addons/AddonRepository.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Extension", + "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "CertUtils", function() { + let certUtils = {}; + Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); + return certUtils; +}); + +const INTEGER = /^[1-9]\d*$/; + +this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ]; + +const CATEGORY_PROVIDER_MODULE = "addon-provider-module"; + +// A list of providers to load by default +const DEFAULT_PROVIDERS = [ + "resource://gre/modules/addons/XPIProvider.jsm", + "resource://gre/modules/LightweightThemeManager.jsm" +]; + +Cu.import("resource://gre/modules/Log.jsm"); +// Configure a logger at the parent 'addons' level to format +// messages for all the modules under addons.* +const PARENT_LOGGER_ID = "addons"; +var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID); +parentLogger.level = Log.Level.Warn; +var formatter = new Log.BasicFormatter(); +// Set parent logger (and its children) to append to +// the Javascript section of the Browser Console +parentLogger.addAppender(new Log.ConsoleAppender(formatter)); +// Set parent logger (and its children) to +// also append to standard out +parentLogger.addAppender(new Log.DumpAppender(formatter)); + +// Create a new logger (child of 'addons' logger) +// for use by the Addons Manager +const LOGGER_ID = "addons.manager"; +var logger = Log.repository.getLogger(LOGGER_ID); + +// Provide the ability to enable/disable logging +// messages at runtime. +// If the "extensions.logging.enabled" preference is +// missing or 'false', messages at the WARNING and higher +// severity should be logged to the JS console and standard error. +// If "extensions.logging.enabled" is set to 'true', messages +// at DEBUG and higher should go to JS console and standard error. +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; + +const UNNAMED_PROVIDER = ""; +function providerName(aProvider) { + return aProvider.name || UNNAMED_PROVIDER; +} + +/** + * Preference listener which listens for a change in the + * "extensions.logging.enabled" preference and changes the logging level of the + * parent 'addons' level logger accordingly. + */ +var PrefObserver = { + init: function() { + Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); + Services.obs.addObserver(this, "xpcom-shutdown", false); + this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); + }, + + observe: function(aSubject, aTopic, aData) { + if (aTopic == "xpcom-shutdown") { + Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); + Services.obs.removeObserver(this, "xpcom-shutdown"); + } + else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { + let debugLogEnabled = false; + try { + debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); + } + catch (e) { + } + if (debugLogEnabled) { + parentLogger.level = Log.Level.Debug; + } + else { + parentLogger.level = Log.Level.Warn; + } + } + } +}; + +PrefObserver.init(); + +/** + * Calls a callback method consuming any thrown exception. Any parameters after + * the callback parameter will be passed to the callback. + * + * @param aCallback + * The callback method to call + */ +function safeCall(aCallback, ...aArgs) { + try { + aCallback.apply(null, aArgs); + } + catch (e) { + logger.warn("Exception calling callback", e); + } +} + +/** + * Creates a function that will call the passed callback catching and logging + * any exceptions. + * + * @param aCallback + * The callback method to call + */ +function makeSafe(aCallback) { + return function(...aArgs) { + safeCall(aCallback, ...aArgs); + } +} + +/** + * Report an exception thrown by a provider API method. + */ +function reportProviderError(aProvider, aMethod, aError) { + let method = `provider ${providerName(aProvider)}.${aMethod}`; + AddonManagerPrivate.recordException("AMI", method, aError); + logger.error("Exception calling " + method, aError); +} + +/** + * Calls a method on a provider if it exists and consumes any thrown exception. + * Any parameters after the aDefault parameter are passed to the provider's method. + * + * @param aProvider + * The provider to call + * @param aMethod + * The method name to call + * @param aDefault + * A default return value if the provider does not implement the named + * method or throws an error. + * @return the return value from the provider, or aDefault if the provider does not + * implement method or throws an error + */ +function callProvider(aProvider, aMethod, aDefault, ...aArgs) { + if (!(aMethod in aProvider)) + return aDefault; + + try { + return aProvider[aMethod].apply(aProvider, aArgs); + } + catch (e) { + reportProviderError(aProvider, aMethod, e); + return aDefault; + } +} + +/** + * Calls a method on a provider if it exists and consumes any thrown exception. + * Parameters after aMethod are passed to aProvider.aMethod(). + * The last parameter must be a callback function. + * If the provider does not implement the method, or the method throws, calls + * the callback with 'undefined'. + * + * @param aProvider + * The provider to call + * @param aMethod + * The method name to call + */ +function callProviderAsync(aProvider, aMethod, ...aArgs) { + let callback = aArgs[aArgs.length - 1]; + if (!(aMethod in aProvider)) { + callback(undefined); + return undefined; + } + try { + return aProvider[aMethod].apply(aProvider, aArgs); + } + catch (e) { + reportProviderError(aProvider, aMethod, e); + callback(undefined); + return undefined; + } +} + +/** + * Calls a method on a provider if it exists and consumes any thrown exception. + * Parameters after aMethod are passed to aProvider.aMethod() and an additional + * callback is added for the provider to return a result to. + * + * @param aProvider + * The provider to call + * @param aMethod + * The method name to call + * @return {Promise} + * @resolves The result the provider returns, or |undefined| if the provider + * does not implement the method or the method throws. + * @rejects Never + */ +function promiseCallProvider(aProvider, aMethod, ...aArgs) { + return new Promise(resolve => { + callProviderAsync(aProvider, aMethod, ...aArgs, resolve); + }); +} + +/** + * Gets the currently selected locale for display. + * @return the selected locale or "en-US" if none is selected + */ +function getLocale() { + try { + if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE)) + return Services.locale.getLocaleComponentForUserAgent(); + } + catch (e) { } + + try { + let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, + Ci.nsIPrefLocalizedString); + if (locale) + return locale; + } + catch (e) { } + + try { + return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); + } + catch (e) { } + + return "en-US"; +} + +function webAPIForAddon(addon) { + if (!addon) { + return null; + } + + let result = {}; + + // By default just pass through any plain property, the webidl will + // control access. Also filter out private properties, regular Addon + // objects are okay but MockAddon used in tests has non-serializable + // private properties. + for (let prop in addon) { + if (prop[0] != "_" && typeof(addon[prop]) != "function") { + result[prop] = addon[prop]; + } + } + + // A few properties are computed for a nicer API + result.isEnabled = !addon.userDisabled; + result.canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL); + + return result; +} + +/** + * A helper class to repeatedly call a listener with each object in an array + * optionally checking whether the object has a method in it. + * + * @param aObjects + * The array of objects to iterate through + * @param aMethod + * An optional method name, if not null any objects without this method + * will not be passed to the listener + * @param aListener + * A listener implementing nextObject and noMoreObjects methods. The + * former will be called with the AsyncObjectCaller as the first + * parameter and the object as the second. noMoreObjects will be passed + * just the AsyncObjectCaller + */ +function AsyncObjectCaller(aObjects, aMethod, aListener) { + this.objects = [...aObjects]; + this.method = aMethod; + this.listener = aListener; + + this.callNext(); +} + +AsyncObjectCaller.prototype = { + objects: null, + method: null, + listener: null, + + /** + * Passes the next object to the listener or calls noMoreObjects if there + * are none left. + */ + callNext: function() { + if (this.objects.length == 0) { + this.listener.noMoreObjects(this); + return; + } + + let object = this.objects.shift(); + if (!this.method || this.method in object) + this.listener.nextObject(this, object); + else + this.callNext(); + } +}; + +/** + * Listens for a browser changing origin and cancels the installs that were + * started by it. + */ +function BrowserListener(aBrowser, aInstallingPrincipal, aInstalls) { + this.browser = aBrowser; + this.principal = aInstallingPrincipal; + this.installs = aInstalls; + this.installCount = aInstalls.length; + + aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); + Services.obs.addObserver(this, "message-manager-close", true); + + for (let install of this.installs) + install.addListener(this); + + this.registered = true; +} + +BrowserListener.prototype = { + browser: null, + installs: null, + installCount: null, + registered: false, + + unregister: function() { + if (!this.registered) + return; + this.registered = false; + + Services.obs.removeObserver(this, "message-manager-close"); + // The browser may have already been detached + if (this.browser.removeProgressListener) + this.browser.removeProgressListener(this); + + for (let install of this.installs) + install.removeListener(this); + this.installs = null; + }, + + cancelInstalls: function() { + for (let install of this.installs) { + try { + install.cancel(); + } + catch (e) { + // Some installs may have already failed or been cancelled, ignore these + } + } + }, + + observe: function(subject, topic, data) { + if (subject != this.browser.messageManager) + return; + + // The browser's message manager has closed and so the browser is + // going away, cancel all installs + this.cancelInstalls(); + }, + + onLocationChange: function(webProgress, request, location) { + if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal)) + return; + + // The browser has navigated to a new origin so cancel all installs + this.cancelInstalls(); + }, + + onDownloadCancelled: function(install) { + // Don't need to hear more events from this install + install.removeListener(this); + + // Once all installs have ended unregister everything + if (--this.installCount == 0) + this.unregister(); + }, + + onDownloadFailed: function(install) { + this.onDownloadCancelled(install); + }, + + onInstallFailed: function(install) { + this.onDownloadCancelled(install); + }, + + onInstallEnded: function(install) { + this.onDownloadCancelled(install); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, + Ci.nsIWebProgressListener, + Ci.nsIObserver]) +}; + +/** + * This represents an author of an add-on (e.g. creator or developer) + * + * @param aName + * The name of the author + * @param aURL + * The URL of the author's profile page + */ +function AddonAuthor(aName, aURL) { + this.name = aName; + this.url = aURL; +} + +AddonAuthor.prototype = { + name: null, + url: null, + + // Returns the author's name, defaulting to the empty string + toString: function() { + return this.name || ""; + } +} + +/** + * This represents an screenshot for an add-on + * + * @param aURL + * The URL to the full version of the screenshot + * @param aWidth + * The width in pixels of the screenshot + * @param aHeight + * The height in pixels of the screenshot + * @param aThumbnailURL + * The URL to the thumbnail version of the screenshot + * @param aThumbnailWidth + * The width in pixels of the thumbnail version of the screenshot + * @param aThumbnailHeight + * The height in pixels of the thumbnail version of the screenshot + * @param aCaption + * The caption of the screenshot + */ +function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL, + aThumbnailWidth, aThumbnailHeight, aCaption) { + this.url = aURL; + if (aWidth) this.width = aWidth; + if (aHeight) this.height = aHeight; + if (aThumbnailURL) this.thumbnailURL = aThumbnailURL; + if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth; + if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight; + if (aCaption) this.caption = aCaption; +} + +AddonScreenshot.prototype = { + url: null, + width: null, + height: null, + thumbnailURL: null, + thumbnailWidth: null, + thumbnailHeight: null, + caption: null, + + // Returns the screenshot URL, defaulting to the empty string + toString: function() { + return this.url || ""; + } +} + + +/** + * This represents a compatibility override for an addon. + * + * @param aType + * Overrride type - "compatible" or "incompatible" + * @param aMinVersion + * Minimum version of the addon to match + * @param aMaxVersion + * Maximum version of the addon to match + * @param aAppID + * Application ID used to match appMinVersion and appMaxVersion + * @param aAppMinVersion + * Minimum version of the application to match + * @param aAppMaxVersion + * Maximum version of the application to match + */ +function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID, + aAppMinVersion, aAppMaxVersion) { + this.type = aType; + this.minVersion = aMinVersion; + this.maxVersion = aMaxVersion; + this.appID = aAppID; + this.appMinVersion = aAppMinVersion; + this.appMaxVersion = aAppMaxVersion; +} + +AddonCompatibilityOverride.prototype = { + /** + * Type of override - "incompatible" or "compatible". + * Only "incompatible" is supported for now. + */ + type: null, + + /** + * Min version of the addon to match. + */ + minVersion: null, + + /** + * Max version of the addon to match. + */ + maxVersion: null, + + /** + * Application ID to match. + */ + appID: null, + + /** + * Min version of the application to match. + */ + appMinVersion: null, + + /** + * Max version of the application to match. + */ + appMaxVersion: null +}; + + +/** + * A type of add-on, used by the UI to determine how to display different types + * of add-ons. + * + * @param aID + * The add-on type ID + * @param aLocaleURI + * The URI of a localized properties file to get the displayable name + * for the type from + * @param aLocaleKey + * The key for the string in the properties file or the actual display + * name if aLocaleURI is null. Include %ID% to include the type ID in + * the key + * @param aViewType + * The optional type of view to use in the UI + * @param aUIPriority + * The priority is used by the UI to list the types in order. Lower + * values push the type higher in the list. + * @param aFlags + * An option set of flags that customize the display of the add-on in + * the UI. + */ +function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) { + if (!aID) + throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG); + + if (aViewType && aUIPriority === undefined) + throw Components.Exception("An AddonType with a defined view must have a set UI priority", + Cr.NS_ERROR_INVALID_ARG); + + if (!aLocaleKey) + throw Components.Exception("An AddonType must have a displayable name", + Cr.NS_ERROR_INVALID_ARG); + + this.id = aID; + this.uiPriority = aUIPriority; + this.viewType = aViewType; + this.flags = aFlags; + + if (aLocaleURI) { + XPCOMUtils.defineLazyGetter(this, "name", () => { + let bundle = Services.strings.createBundle(aLocaleURI); + return bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID)); + }); + } + else { + this.name = aLocaleKey; + } +} + +var gStarted = false; +var gStartupComplete = false; +var gCheckCompatibility = true; +var gStrictCompatibility = true; +var gCheckUpdateSecurityDefault = true; +var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; +var gUpdateEnabled = true; +var gAutoUpdateDefault = true; +var gHotfixID = null; +var gWebExtensionsMinPlatformVersion = null; +var gShutdownBarrier = null; +var gRepoShutdownState = ""; +var gShutdownInProgress = false; +var gPluginPageListener = null; + +/** + * This is the real manager, kept here rather than in AddonManager to keep its + * contents hidden from API users. + */ +var AddonManagerInternal = { + managerListeners: [], + installListeners: [], + addonListeners: [], + typeListeners: [], + pendingProviders: new Set(), + providers: new Set(), + providerShutdowns: new Map(), + types: {}, + startupChanges: {}, + // Store telemetry details per addon provider + telemetryDetails: {}, + upgradeListeners: new Map(), + + recordTimestamp: function(name, value) { + this.TelemetryTimestamps.add(name, value); + }, + + validateBlocklist: function() { + let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); + + // If there is no application shipped blocklist then there is nothing to do + if (!appBlocklist.exists()) + return; + + let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); + + // If there is no blocklist in the profile then copy the application shipped + // one there + if (!profileBlocklist.exists()) { + try { + appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); + } + catch (e) { + logger.warn("Failed to copy the application shipped blocklist to the profile", e); + } + return; + } + + let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + try { + let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Ci.nsIConverterInputStream); + fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); + cstream.init(fileStream, "UTF-8", 0, 0); + + let data = ""; + let str = {}; + let read = 0; + do { + read = cstream.readString(0xffffffff, str); + data += str.value; + } while (read != 0); + + let parser = Cc["@mozilla.org/xmlextras/domparser;1"]. + createInstance(Ci.nsIDOMParser); + var doc = parser.parseFromString(data, "text/xml"); + } + catch (e) { + logger.warn("Application shipped blocklist could not be loaded", e); + return; + } + finally { + try { + fileStream.close(); + } + catch (e) { + logger.warn("Unable to close blocklist file stream", e); + } + } + + // If the namespace is incorrect then ignore the application shipped + // blocklist + if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { + logger.warn("Application shipped blocklist has an unexpected namespace (" + + doc.documentElement.namespaceURI + ")"); + return; + } + + // If there is no lastupdate information then ignore the application shipped + // blocklist + if (!doc.documentElement.hasAttribute("lastupdate")) + return; + + // If the application shipped blocklist is older than the profile blocklist + // then do nothing + if (doc.documentElement.getAttribute("lastupdate") <= + profileBlocklist.lastModifiedTime) + return; + + // Otherwise copy the application shipped blocklist to the profile + try { + appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); + } + catch (e) { + logger.warn("Failed to copy the application shipped blocklist to the profile", e); + } + }, + + /** + * Start up a provider, and register its shutdown hook if it has one + */ + _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + logger.debug(`Starting provider: ${providerName(aProvider)}`); + callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion); + if ('shutdown' in aProvider) { + let name = providerName(aProvider); + let AMProviderShutdown = () => { + // If the provider has been unregistered, it will have been removed from + // this.providers. If it hasn't been unregistered, then this is a normal + // shutdown - and we move it to this.pendingProviders incase we're + // running in a test that will start AddonManager again. + if (this.providers.has(aProvider)) { + this.providers.delete(aProvider); + this.pendingProviders.add(aProvider); + } + + return new Promise((resolve, reject) => { + logger.debug("Calling shutdown blocker for " + name); + resolve(aProvider.shutdown()); + }) + .catch(err => { + logger.warn("Failure during shutdown of " + name, err); + AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err); + }); + }; + logger.debug("Registering shutdown blocker for " + name); + this.providerShutdowns.set(aProvider, AMProviderShutdown); + AddonManager.shutdown.addBlocker(name, AMProviderShutdown); + } + + this.pendingProviders.delete(aProvider); + this.providers.add(aProvider); + logger.debug(`Provider finished startup: ${providerName(aProvider)}`); + }, + + _getProviderByName(aName) { + for (let provider of this.providers) { + if (providerName(provider) == aName) + return provider; + } + return undefined; + }, + + /** + * Initializes the AddonManager, loading any known providers and initializing + * them. + */ + startup: function() { + try { + if (gStarted) + return; + + this.recordTimestamp("AMI_startup_begin"); + + // clear this for xpcshell test restarts + for (let provider in this.telemetryDetails) + delete this.telemetryDetails[provider]; + + let appChanged = undefined; + + let oldAppVersion = null; + try { + oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION); + appChanged = Services.appinfo.version != oldAppVersion; + } + catch (e) { } + + Extension.browserUpdated = appChanged; + + let oldPlatformVersion = null; + try { + oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION); + } + catch (e) { } + + if (appChanged !== false) { + logger.debug("Application has been upgraded"); + Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION, + Services.appinfo.version); + Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION, + Services.appinfo.platformVersion); + Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, + (appChanged === undefined ? 0 : -1)); + this.validateBlocklist(); + } + + if (!MOZ_COMPATIBILITY_NIGHTLY) { + PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." + + Services.appinfo.version.replace(BRANCH_REGEXP, "$1"); + } + + try { + gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false); + + try { + gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false); + + try { + let defaultBranch = Services.prefs.getDefaultBranch(""); + gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); + } catch (e) {} + + try { + gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false); + + try { + gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false); + + try { + gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false); + + try { + gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); + + try { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + } catch (e) {} + Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); + + let defaultProvidersEnabled = true; + try { + defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); + } catch (e) {} + AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled); + + // Ensure all default providers have had a chance to register themselves + if (defaultProvidersEnabled) { + for (let url of DEFAULT_PROVIDERS) { + try { + let scope = {}; + Components.utils.import(url, scope); + // Sanity check - make sure the provider exports a symbol that + // has a 'startup' method + let syms = Object.keys(scope); + if ((syms.length < 1) || + (typeof scope[syms[0]].startup != "function")) { + logger.warn("Provider " + url + " has no startup()"); + AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()"); + } + logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource()); + } + catch (e) { + AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); + logger.error("Exception loading default provider \"" + url + "\"", e); + } + } + } + + // Load any providers registered in the category manager + let catman = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE); + while (entries.hasMoreElements()) { + let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data; + let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry); + + try { + Components.utils.import(url, {}); + logger.debug(`Loaded provider scope for ${url}`); + } + catch (e) { + AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); + logger.error("Exception loading provider " + entry + " from category \"" + + url + "\"", e); + } + } + + // Register our shutdown handler with the AsyncShutdown manager + gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down."); + AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.", + this.shutdownManager.bind(this), + {fetchState: this.shutdownState.bind(this)}); + + // Once we start calling providers we must allow all normal methods to work. + gStarted = true; + + for (let provider of this.pendingProviders) { + this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion); + } + + // If this is a new profile just pretend that there were no changes + if (appChanged === undefined) { + for (let type in this.startupChanges) + delete this.startupChanges[type]; + } + + // Support for remote about:plugins. Note that this module isn't loaded + // at the top because Services.appinfo is defined late in tests. + let { RemotePages } = Cu.import("resource://gre/modules/RemotePageManager.jsm", {}); + + gPluginPageListener = new RemotePages("about:plugins"); + gPluginPageListener.addMessageListener("RequestPlugins", this.requestPlugins); + + gStartupComplete = true; + this.recordTimestamp("AMI_startup_end"); + } + catch (e) { + logger.error("startup failed", e); + AddonManagerPrivate.recordException("AMI", "startup failed", e); + } + + logger.debug("Completed startup sequence"); + this.callManagerListeners("onStartup"); + }, + + /** + * Registers a new AddonProvider. + * + * @param aProvider + * The provider to register + * @param aTypes + * An optional array of add-on types + */ + registerProvider: function(aProvider, aTypes) { + if (!aProvider || typeof aProvider != "object") + throw Components.Exception("aProvider must be specified", + Cr.NS_ERROR_INVALID_ARG); + + if (aTypes && !Array.isArray(aTypes)) + throw Components.Exception("aTypes must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + this.pendingProviders.add(aProvider); + + if (aTypes) { + for (let type of aTypes) { + if (!(type.id in this.types)) { + if (!VALID_TYPES_REGEXP.test(type.id)) { + logger.warn("Ignoring invalid type " + type.id); + return; + } + + this.types[type.id] = { + type: type, + providers: [aProvider] + }; + + let typeListeners = this.typeListeners.slice(0); + for (let listener of typeListeners) + safeCall(() => listener.onTypeAdded(type)); + } + else { + this.types[type.id].providers.push(aProvider); + } + } + } + + // If we're registering after startup call this provider's startup. + if (gStarted) { + this._startProvider(aProvider); + } + }, + + /** + * Unregisters an AddonProvider. + * + * @param aProvider + * The provider to unregister + * @return Whatever the provider's 'shutdown' method returns (if anything). + * For providers that have async shutdown methods returning Promises, + * the caller should wait for that Promise to resolve. + */ + unregisterProvider: function(aProvider) { + if (!aProvider || typeof aProvider != "object") + throw Components.Exception("aProvider must be specified", + Cr.NS_ERROR_INVALID_ARG); + + this.providers.delete(aProvider); + // The test harness will unregister XPIProvider *after* shutdown, which is + // after the provider will have been moved from providers to + // pendingProviders. + this.pendingProviders.delete(aProvider); + + for (let type in this.types) { + this.types[type].providers = this.types[type].providers.filter(p => p != aProvider); + if (this.types[type].providers.length == 0) { + let oldType = this.types[type].type; + delete this.types[type]; + + let typeListeners = this.typeListeners.slice(0); + for (let listener of typeListeners) + safeCall(() => listener.onTypeRemoved(oldType)); + } + } + + // If we're unregistering after startup but before shutting down, + // remove the blocker for this provider's shutdown and call it. + // If we're already shutting down, just let gShutdownBarrier call it to avoid races. + if (gStarted && !gShutdownInProgress) { + logger.debug("Unregistering shutdown blocker for " + providerName(aProvider)); + let shutter = this.providerShutdowns.get(aProvider); + if (shutter) { + this.providerShutdowns.delete(aProvider); + gShutdownBarrier.client.removeBlocker(shutter); + return shutter(); + } + } + return undefined; + }, + + /** + * Mark a provider as safe to access via AddonManager APIs, before its + * startup has completed. + * + * Normally a provider isn't marked as safe until after its (synchronous) + * startup() method has returned. Until a provider has been marked safe, + * it won't be used by any of the AddonManager APIs. markProviderSafe() + * allows a provider to mark itself as safe during its startup; this can be + * useful if the provider wants to perform tasks that block startup, which + * happen after its required initialization tasks and therefore when the + * provider is in a safe state. + * + * @param aProvider Provider object to mark safe + */ + markProviderSafe: function(aProvider) { + if (!gStarted) { + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + } + + if (!aProvider || typeof aProvider != "object") { + throw Components.Exception("aProvider must be specified", + Cr.NS_ERROR_INVALID_ARG); + } + + if (!this.pendingProviders.has(aProvider)) { + return; + } + + this.pendingProviders.delete(aProvider); + this.providers.add(aProvider); + }, + + /** + * Calls a method on all registered providers if it exists and consumes any + * thrown exception. Return values are ignored. Any parameters after the + * method parameter are passed to the provider's method. + * WARNING: Do not use for asynchronous calls; callProviders() does not + * invoke callbacks if provider methods throw synchronous exceptions. + * + * @param aMethod + * The method name to call + * @see callProvider + */ + callProviders: function(aMethod, ...aArgs) { + if (!aMethod || typeof aMethod != "string") + throw Components.Exception("aMethod must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let providers = [...this.providers]; + for (let provider of providers) { + try { + if (aMethod in provider) + provider[aMethod].apply(provider, aArgs); + } + catch (e) { + reportProviderError(provider, aMethod, e); + } + } + }, + + /** + * Report the current state of asynchronous shutdown + */ + shutdownState() { + let state = []; + if (gShutdownBarrier) { + state.push({ + name: gShutdownBarrier.client.name, + state: gShutdownBarrier.state + }); + } + state.push({ + name: "AddonRepository: async shutdown", + state: gRepoShutdownState + }); + return state; + }, + + /** + * Shuts down the addon manager and all registered providers, this must clean + * up everything in order for automated tests to fake restarts. + * @return Promise{null} that resolves when all providers and dependent modules + * have finished shutting down + */ + shutdownManager: Task.async(function*() { + logger.debug("shutdown"); + this.callManagerListeners("onShutdown"); + + gRepoShutdownState = "pending"; + gShutdownInProgress = true; + // Clean up listeners + Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this); + Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this); + Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this); + Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this); + Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this); + Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this); + gPluginPageListener.destroy(); + gPluginPageListener = null; + + let savedError = null; + // Only shut down providers if they've been started. + if (gStarted) { + try { + yield gShutdownBarrier.wait(); + } + catch (err) { + savedError = err; + logger.error("Failure during wait for shutdown barrier", err); + AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonManager providers", err); + } + } + + // Shut down AddonRepository after providers (if any). + try { + gRepoShutdownState = "in progress"; + yield AddonRepository.shutdown(); + gRepoShutdownState = "done"; + } + catch (err) { + savedError = err; + logger.error("Failure during AddonRepository shutdown", err); + AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err); + } + + logger.debug("Async provider shutdown done"); + this.managerListeners.splice(0, this.managerListeners.length); + this.installListeners.splice(0, this.installListeners.length); + this.addonListeners.splice(0, this.addonListeners.length); + this.typeListeners.splice(0, this.typeListeners.length); + this.providerShutdowns.clear(); + for (let type in this.startupChanges) + delete this.startupChanges[type]; + gStarted = false; + gStartupComplete = false; + gShutdownBarrier = null; + gShutdownInProgress = false; + if (savedError) { + throw savedError; + } + }), + + requestPlugins: function({ target: port }) { + // Lists all the properties that plugins.html needs + const NEEDED_PROPS = ["name", "pluginLibraries", "pluginFullpath", "version", + "isActive", "blocklistState", "description", + "pluginMimeTypes"]; + function filterProperties(plugin) { + let filtered = {}; + for (let prop of NEEDED_PROPS) { + filtered[prop] = plugin[prop]; + } + return filtered; + } + + AddonManager.getAddonsByTypes(["plugin"], function(aPlugins) { + port.sendAsyncMessage("PluginList", aPlugins.map(filterProperties)); + }); + }, + + /** + * Notified when a preference we're interested in has changed. + * + * @see nsIObserver + */ + observe: function(aSubject, aTopic, aData) { + switch (aData) { + case PREF_EM_CHECK_COMPATIBILITY: { + let oldValue = gCheckCompatibility; + try { + gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); + } catch (e) { + gCheckCompatibility = true; + } + + this.callManagerListeners("onCompatibilityModeChanged"); + + if (gCheckCompatibility != oldValue) + this.updateAddonAppDisabledStates(); + + break; + } + case PREF_EM_STRICT_COMPATIBILITY: { + let oldValue = gStrictCompatibility; + try { + gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); + } catch (e) { + gStrictCompatibility = true; + } + + this.callManagerListeners("onCompatibilityModeChanged"); + + if (gStrictCompatibility != oldValue) + this.updateAddonAppDisabledStates(); + + break; + } + case PREF_EM_CHECK_UPDATE_SECURITY: { + let oldValue = gCheckUpdateSecurity; + try { + gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); + } catch (e) { + gCheckUpdateSecurity = true; + } + + this.callManagerListeners("onCheckUpdateSecurityChanged"); + + if (gCheckUpdateSecurity != oldValue) + this.updateAddonAppDisabledStates(); + + break; + } + case PREF_EM_UPDATE_ENABLED: { + let oldValue = gUpdateEnabled; + try { + gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); + } catch (e) { + gUpdateEnabled = true; + } + + this.callManagerListeners("onUpdateModeChanged"); + break; + } + case PREF_EM_AUTOUPDATE_DEFAULT: { + let oldValue = gAutoUpdateDefault; + try { + gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); + } catch (e) { + gAutoUpdateDefault = true; + } + + this.callManagerListeners("onUpdateModeChanged"); + break; + } + case PREF_EM_HOTFIX_ID: { + try { + gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); + } catch (e) { + gHotfixID = null; + } + break; + } + case PREF_MIN_WEBEXT_PLATFORM_VERSION: { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + break; + } + } + }, + + /** + * Replaces %...% strings in an addon url (update and updateInfo) with + * appropriate values. + * + * @param aAddon + * The Addon representing the add-on + * @param aUri + * The string representation of the URI to escape + * @param aAppVersion + * The optional application version to use for %APP_VERSION% + * @return The appropriately escaped URI. + */ + escapeAddonURI: function(aAddon, aUri, aAppVersion) + { + if (!aAddon || typeof aAddon != "object") + throw Components.Exception("aAddon must be an Addon object", + Cr.NS_ERROR_INVALID_ARG); + + if (!aUri || typeof aUri != "string") + throw Components.Exception("aUri must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (aAppVersion && typeof aAppVersion != "string") + throw Components.Exception("aAppVersion must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled" + : "userEnabled"; + + if (!aAddon.isCompatible) + addonStatus += ",incompatible"; + if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) + addonStatus += ",blocklisted"; + if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) + addonStatus += ",softblocked"; + + try { + var xpcomABI = Services.appinfo.XPCOMABI; + } catch (ex) { + xpcomABI = UNKNOWN_XPCOM_ABI; + } + + let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id); + uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version); + uri = uri.replace(/%ITEM_STATUS%/g, addonStatus); + uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID); + uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion : + Services.appinfo.version); + uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION); + uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS); + uri = uri.replace(/%APP_ABI%/g, xpcomABI); + uri = uri.replace(/%APP_LOCALE%/g, getLocale()); + uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version); + + // Replace custom parameters (names of custom parameters must have at + // least 3 characters to prevent lookups for something like %D0%C8) + var catMan = null; + uri = uri.replace(/%(\w{3,})%/g, function(aMatch, aParam) { + if (!catMan) { + catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + } + + try { + var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam); + var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2); + return paramHandler.getPropertyAsAString(aParam); + } + catch (e) { + return aMatch; + } + }); + + // escape() does not properly encode + symbols in any embedded FVF strings. + return uri.replace(/\+/g, "%2B"); + }, + + /** + * Performs a background update check by starting an update for all add-ons + * that can be updated. + * @return Promise{null} Resolves when the background update check is complete + * (the resulting addon installations may still be in progress). + */ + backgroundUpdateCheck: function() { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + let buPromise = Task.spawn(function*() { + let hotfixID = this.hotfixID; + + let appUpdateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && + Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); + let checkHotfix = hotfixID && appUpdateEnabled; + + logger.debug("Background update check beginning"); + + Services.obs.notifyObservers(null, "addons-background-update-start", null); + + if (this.updateEnabled) { + let scope = {}; + Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope); + scope.LightweightThemeManager.updateCurrentTheme(); + + let allAddons = yield new Promise((resolve, reject) => this.getAllAddons(resolve)); + + // Repopulate repository cache first, to ensure compatibility overrides + // are up to date before checking for addon updates. + yield AddonRepository.backgroundUpdateCheck(); + + // Keep track of all the async add-on updates happening in parallel + let updates = []; + + for (let addon of allAddons) { + if (addon.id == hotfixID) { + continue; + } + + // Check all add-ons for updates so that any compatibility updates will + // be applied + updates.push(new Promise((resolve, reject) => { + addon.findUpdates({ + onUpdateAvailable: function(aAddon, aInstall) { + // Start installing updates when the add-on can be updated and + // background updates should be applied. + logger.debug("Found update for add-on ${id}", aAddon); + if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && + AddonManager.shouldAutoUpdate(aAddon)) { + // XXX we really should resolve when this install is done, + // not when update-available check completes, no? + logger.debug(`Starting upgrade install of ${aAddon.id}`); + aInstall.install(); + } + }, + + onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + })); + } + yield Promise.all(updates); + } + + if (checkHotfix) { + var hotfixVersion = ""; + try { + hotfixVersion = Services.prefs.getCharPref(PREF_EM_HOTFIX_LASTVERSION); + } + catch (e) { } + + let url = null; + if (Services.prefs.getPrefType(PREF_EM_HOTFIX_URL) == Ci.nsIPrefBranch.PREF_STRING) + url = Services.prefs.getCharPref(PREF_EM_HOTFIX_URL); + else + url = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL); + + // Build the URI from a fake add-on data. + url = AddonManager.escapeAddonURI({ + id: hotfixID, + version: hotfixVersion, + userDisabled: false, + appDisabled: false + }, url); + + Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); + let update = null; + try { + let foundUpdates = yield new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(hotfixID, null, url, { + onUpdateCheckComplete: resolve, + onUpdateCheckError: reject + }); + }); + update = AddonUpdateChecker.getNewestCompatibleUpdate(foundUpdates); + } catch (e) { + // AUC.checkForUpdates already logged the error + } + + // Check that we have a hotfix update, and it's newer than the one we already + // have installed (if any) + if (update) { + if (Services.vc.compare(hotfixVersion, update.version) < 0) { + logger.debug("Downloading hotfix version " + update.version); + let aInstall = yield new Promise((resolve, reject) => + AddonManager.getInstallForURL(update.updateURL, resolve, + "application/x-xpinstall", update.updateHash, null, + null, update.version)); + + aInstall.addListener({ + onDownloadEnded: function(aInstall) { + if (aInstall.addon.id != hotfixID) { + logger.warn("The downloaded hotfix add-on did not have the " + + "expected ID and so will not be installed."); + aInstall.cancel(); + return; + } + + // If XPIProvider has reported the hotfix as properly signed then + // there is nothing more to do here + if (aInstall.addon.signedState == AddonManager.SIGNEDSTATE_SIGNED) + return; + + try { + if (!Services.prefs.getBoolPref(PREF_EM_CERT_CHECKATTRIBUTES)) + return; + } + catch (e) { + // By default don't do certificate checks. + return; + } + + try { + CertUtils.validateCert(aInstall.certificate, + CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS)); + } + catch (e) { + logger.warn("The hotfix add-on was not signed by the expected " + + "certificate and so will not be installed.", e); + aInstall.cancel(); + } + }, + + onInstallEnded: function(aInstall) { + // Remember the last successfully installed version. + Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, + aInstall.version); + }, + + onInstallCancelled: function(aInstall) { + // Revert to the previous version if the installation was + // cancelled. + Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, + hotfixVersion); + } + }); + + aInstall.install(); + } + } + } + + if (appUpdateEnabled) { + try { + yield AddonManagerInternal._getProviderByName("XPIProvider").updateSystemAddons(); + } + catch (e) { + logger.warn("Failed to update system addons", e); + } + } + + logger.debug("Background update check complete"); + Services.obs.notifyObservers(null, + "addons-background-update-complete", + null); + }.bind(this)); + // Fork the promise chain so we can log the error and let our caller see it too. + buPromise.then(null, e => logger.warn("Error in background update", e)); + return buPromise; + }, + + /** + * Adds a add-on to the list of detected changes for this startup. If + * addStartupChange is called multiple times for the same add-on in the same + * startup then only the most recent change will be remembered. + * + * @param aType + * The type of change as a string. Providers can define their own + * types of changes or use the existing defined STARTUP_CHANGE_* + * constants + * @param aID + * The ID of the add-on + */ + addStartupChange: function(aType, aID) { + if (!aType || typeof aType != "string") + throw Components.Exception("aType must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (!aID || typeof aID != "string") + throw Components.Exception("aID must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (gStartupComplete) + return; + logger.debug("Registering startup change '" + aType + "' for " + aID); + + // Ensure that an ID is only listed in one type of change + for (let type in this.startupChanges) + this.removeStartupChange(type, aID); + + if (!(aType in this.startupChanges)) + this.startupChanges[aType] = []; + this.startupChanges[aType].push(aID); + }, + + /** + * Removes a startup change for an add-on. + * + * @param aType + * The type of change + * @param aID + * The ID of the add-on + */ + removeStartupChange: function(aType, aID) { + if (!aType || typeof aType != "string") + throw Components.Exception("aType must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (!aID || typeof aID != "string") + throw Components.Exception("aID must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (gStartupComplete) + return; + + if (!(aType in this.startupChanges)) + return; + + this.startupChanges[aType] = this.startupChanges[aType].filter(aItem => aItem != aID); + }, + + /** + * Calls all registered AddonManagerListeners with an event. Any parameters + * after the method parameter are passed to the listener. + * + * @param aMethod + * The method on the listeners to call + */ + callManagerListeners: function(aMethod, ...aArgs) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMethod || typeof aMethod != "string") + throw Components.Exception("aMethod must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let managerListeners = this.managerListeners.slice(0); + for (let listener of managerListeners) { + try { + if (aMethod in listener) + listener[aMethod].apply(listener, aArgs); + } + catch (e) { + logger.warn("AddonManagerListener threw exception when calling " + aMethod, e); + } + } + }, + + /** + * Calls all registered InstallListeners with an event. Any parameters after + * the extraListeners parameter are passed to the listener. + * + * @param aMethod + * The method on the listeners to call + * @param aExtraListeners + * An optional array of extra InstallListeners to also call + * @return false if any of the listeners returned false, true otherwise + */ + callInstallListeners: function(aMethod, + aExtraListeners, ...aArgs) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMethod || typeof aMethod != "string") + throw Components.Exception("aMethod must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (aExtraListeners && !Array.isArray(aExtraListeners)) + throw Components.Exception("aExtraListeners must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + let result = true; + let listeners; + if (aExtraListeners) + listeners = aExtraListeners.concat(this.installListeners); + else + listeners = this.installListeners.slice(0); + + for (let listener of listeners) { + try { + if (aMethod in listener) { + if (listener[aMethod].apply(listener, aArgs) === false) + result = false; + } + } + catch (e) { + logger.warn("InstallListener threw exception when calling " + aMethod, e); + } + } + return result; + }, + + /** + * Calls all registered AddonListeners with an event. Any parameters after + * the method parameter are passed to the listener. + * + * @param aMethod + * The method on the listeners to call + */ + callAddonListeners: function(aMethod, ...aArgs) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMethod || typeof aMethod != "string") + throw Components.Exception("aMethod must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let addonListeners = this.addonListeners.slice(0); + for (let listener of addonListeners) { + try { + if (aMethod in listener) + listener[aMethod].apply(listener, aArgs); + } + catch (e) { + logger.warn("AddonListener threw exception when calling " + aMethod, e); + } + } + }, + + /** + * Notifies all providers that an add-on has been enabled when that type of + * add-on only supports a single add-on being enabled at a time. This allows + * the providers to disable theirs if necessary. + * + * @param aID + * The ID of the enabled add-on + * @param aType + * The type of the enabled add-on + * @param aPendingRestart + * A boolean indicating if the change will only take place the next + * time the application is restarted + */ + notifyAddonChanged: function(aID, aType, aPendingRestart) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (aID && typeof aID != "string") + throw Components.Exception("aID must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + if (!aType || typeof aType != "string") + throw Components.Exception("aType must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + // Temporary hack until bug 520124 lands. + // We can get here during synchronous startup, at which point it's + // considered unsafe (and therefore disallowed by AddonManager.jsm) to + // access providers that haven't been initialized yet. Since this is when + // XPIProvider is starting up, XPIProvider can't access itself via APIs + // going through AddonManager.jsm. Furthermore, LightweightThemeManager may + // not be initialized until after XPIProvider is, and therefore would also + // be unaccessible during XPIProvider startup. Thankfully, these are the + // only two uses of this API, and we know it's safe to use this API with + // both providers; so we have this hack to allow bypassing the normal + // safetey guard. + // The notifyAddonChanged/addonChanged API will be unneeded and therefore + // removed by bug 520124, so this is a temporary quick'n'dirty hack. + let providers = [...this.providers, ...this.pendingProviders]; + for (let provider of providers) { + callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart); + } + }, + + /** + * Notifies all providers they need to update the appDisabled property for + * their add-ons in response to an application change such as a blocklist + * update. + */ + updateAddonAppDisabledStates: function() { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + this.callProviders("updateAddonAppDisabledStates"); + }, + + /** + * Notifies all providers that the repository has updated its data for + * installed add-ons. + * + * @param aCallback + * Function to call when operation is complete. + */ + updateAddonRepositoryData: function(aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "updateAddonRepositoryData", + aCaller.callNext.bind(aCaller)); + }, + noMoreObjects: function(aCaller) { + safeCall(aCallback); + // only tests should care about this + Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null); + } + }); + }, + + /** + * Asynchronously gets an AddonInstall for a URL. + * + * @param aUrl + * The string represenation of the URL the add-on is located at + * @param aCallback + * A callback to pass the AddonInstall to + * @param aMimetype + * The mimetype of the add-on + * @param aHash + * An optional hash of the add-on + * @param aName + * An optional placeholder name while the add-on is being downloaded + * @param aIcons + * Optional placeholder icons while the add-on is being downloaded + * @param aVersion + * An optional placeholder version while the add-on is being downloaded + * @param aLoadGroup + * An optional nsILoadGroup to associate any network requests with + * @throws if the aUrl, aCallback or aMimetype arguments are not specified + */ + getInstallForURL: function(aUrl, aCallback, aMimetype, + aHash, aName, aIcons, + aVersion, aBrowser) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aUrl || typeof aUrl != "string") + throw Components.Exception("aURL must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + if (!aMimetype || typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (aHash && typeof aHash != "string") + throw Components.Exception("aHash must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + if (aName && typeof aName != "string") + throw Components.Exception("aName must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + if (aIcons) { + if (typeof aIcons == "string") + aIcons = { "32": aIcons }; + else if (typeof aIcons != "object") + throw Components.Exception("aIcons must be a string, an object or null", + Cr.NS_ERROR_INVALID_ARG); + } else { + aIcons = {}; + } + + if (aVersion && typeof aVersion != "string") + throw Components.Exception("aVersion must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + if (aBrowser && (!(aBrowser instanceof Ci.nsIDOMElement))) + throw Components.Exception("aBrowser must be a nsIDOMElement or null", + Cr.NS_ERROR_INVALID_ARG); + + let providers = [...this.providers]; + for (let provider of providers) { + if (callProvider(provider, "supportsMimetype", false, aMimetype)) { + callProviderAsync(provider, "getInstallForURL", + aUrl, aHash, aName, aIcons, aVersion, aBrowser, + function getInstallForURL_safeCall(aInstall) { + safeCall(aCallback, aInstall); + }); + return; + } + } + safeCall(aCallback, null); + }, + + /** + * Asynchronously gets an AddonInstall for an nsIFile. + * + * @param aFile + * The nsIFile where the add-on is located + * @param aCallback + * A callback to pass the AddonInstall to + * @param aMimetype + * An optional mimetype hint for the add-on + * @throws if the aFile or aCallback arguments are not specified + */ + getInstallForFile: function(aFile, aCallback, aMimetype) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!(aFile instanceof Ci.nsIFile)) + throw Components.Exception("aFile must be a nsIFile", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + if (aMimetype && typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a string or null", + Cr.NS_ERROR_INVALID_ARG); + + new AsyncObjectCaller(this.providers, "getInstallForFile", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "getInstallForFile", aFile, + function(aInstall) { + if (aInstall) + safeCall(aCallback, aInstall); + else + aCaller.callNext(); + }); + }, + + noMoreObjects: function(aCaller) { + safeCall(aCallback, null); + } + }); + }, + + /** + * Asynchronously gets all current AddonInstalls optionally limiting to a list + * of types. + * + * @param aTypes + * An optional array of types to retrieve. Each type is a string name + * @param aCallback + * A callback which will be passed an array of AddonInstalls + * @throws If the aCallback argument is not specified + */ + getInstallsByTypes: function(aTypes, aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (aTypes && !Array.isArray(aTypes)) + throw Components.Exception("aTypes must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + let installs = []; + + new AsyncObjectCaller(this.providers, "getInstallsByTypes", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "getInstallsByTypes", aTypes, + function(aProviderInstalls) { + if (aProviderInstalls) { + installs = installs.concat(aProviderInstalls); + } + aCaller.callNext(); + }); + }, + + noMoreObjects: function(aCaller) { + safeCall(aCallback, installs); + } + }); + }, + + /** + * Asynchronously gets all current AddonInstalls. + * + * @param aCallback + * A callback which will be passed an array of AddonInstalls + */ + getAllInstalls: function(aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + this.getInstallsByTypes(null, aCallback); + }, + + /** + * Synchronously map a URI to the corresponding Addon ID. + * + * Mappable URIs are limited to in-application resources belonging to the + * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. + * but do not include URIs from meta data, such as the add-on homepage. + * + * @param aURI + * nsIURI to map to an addon id + * @return string containing the Addon ID or null + * @see amIAddonManager.mapURIToAddonID + */ + mapURIToAddonID: function(aURI) { + if (!(aURI instanceof Ci.nsIURI)) { + throw Components.Exception("aURI is not a nsIURI", + Cr.NS_ERROR_INVALID_ARG); + } + + // Try all providers + let providers = [...this.providers]; + for (let provider of providers) { + var id = callProvider(provider, "mapURIToAddonID", null, aURI); + if (id !== null) { + return id; + } + } + + return null; + }, + + /** + * Checks whether installation is enabled for a particular mimetype. + * + * @param aMimetype + * The mimetype to check + * @return true if installation is enabled for the mimetype + */ + isInstallEnabled: function(aMimetype) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMimetype || typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let providers = [...this.providers]; + for (let provider of providers) { + if (callProvider(provider, "supportsMimetype", false, aMimetype) && + callProvider(provider, "isInstallEnabled")) + return true; + } + return false; + }, + + /** + * Checks whether a particular source is allowed to install add-ons of a + * given mimetype. + * + * @param aMimetype + * The mimetype of the add-on + * @param aInstallingPrincipal + * The nsIPrincipal that initiated the install + * @return true if the source is allowed to install this mimetype + */ + isInstallAllowed: function(aMimetype, aInstallingPrincipal) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMimetype || typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal)) + throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal", + Cr.NS_ERROR_INVALID_ARG); + + let providers = [...this.providers]; + for (let provider of providers) { + if (callProvider(provider, "supportsMimetype", false, aMimetype) && + callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal)) + return true; + } + return false; + }, + + /** + * Starts installation of an array of AddonInstalls notifying the registered + * web install listener of blocked or started installs. + * + * @param aMimetype + * The mimetype of add-ons being installed + * @param aBrowser + * The optional browser element that started the installs + * @param aInstallingPrincipal + * The nsIPrincipal that initiated the install + * @param aInstalls + * The array of AddonInstalls to be installed + */ + installAddonsFromWebpage: function(aMimetype, aBrowser, + aInstallingPrincipal, aInstalls) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aMimetype || typeof aMimetype != "string") + throw Components.Exception("aMimetype must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement)) + throw Components.Exception("aSource must be a nsIDOMElement, or null", + Cr.NS_ERROR_INVALID_ARG); + + if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal)) + throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal", + Cr.NS_ERROR_INVALID_ARG); + + if (!Array.isArray(aInstalls)) + throw Components.Exception("aInstalls must be an array", + Cr.NS_ERROR_INVALID_ARG); + + if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) { + logger.warn("No web installer available, cancelling all installs"); + for (let install of aInstalls) + install.cancel(); + return; + } + + // When a chrome in-content UI has loaded a inside to host a + // website we want to do our security checks on the inner-browser but + // notify front-end that install events came from the outer-browser (the + // main tab's browser). Check this by seeing if the browser we've been + // passed is in a content type docshell and if so get the outer-browser. + let topBrowser = aBrowser; + let docShell = aBrowser.ownerDocument.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIDocShellTreeItem); + if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent) + topBrowser = docShell.chromeEventHandler; + + try { + let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]. + getService(Ci.amIWebInstallListener); + + if (!this.isInstallEnabled(aMimetype)) { + for (let install of aInstalls) + install.cancel(); + + weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI, + aInstalls, aInstalls.length); + return; + } + else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) { + for (let install of aInstalls) + install.cancel(); + + if (weblistener instanceof Ci.amIWebInstallListener2) { + weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI, + aInstalls, aInstalls.length); + } + return; + } + + // The installs may start now depending on the web install listener, + // listen for the browser navigating to a new origin and cancel the + // installs in that case. + new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls); + + if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) { + if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI, + aInstalls, aInstalls.length)) { + for (let install of aInstalls) + install.install(); + } + } + else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI, + aInstalls, aInstalls.length)) { + for (let install of aInstalls) + install.install(); + } + } + catch (e) { + // In the event that the weblistener throws during instantiation or when + // calling onWebInstallBlocked or onWebInstallRequested all of the + // installs should get cancelled. + logger.warn("Failure calling web installer", e); + for (let install of aInstalls) + install.cancel(); + } + }, + + /** + * Adds a new InstallListener if the listener is not already registered. + * + * @param aListener + * The InstallListener to add + */ + addInstallListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be a InstallListener object", + Cr.NS_ERROR_INVALID_ARG); + + if (!this.installListeners.some(function(i) { + return i == aListener; })) + this.installListeners.push(aListener); + }, + + /** + * Removes an InstallListener if the listener is registered. + * + * @param aListener + * The InstallListener to remove + */ + removeInstallListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be a InstallListener object", + Cr.NS_ERROR_INVALID_ARG); + + let pos = 0; + while (pos < this.installListeners.length) { + if (this.installListeners[pos] == aListener) + this.installListeners.splice(pos, 1); + else + pos++; + } + }, + /* + * Adds new or overrides existing UpgradeListener. + * + * @param aInstanceID + * The instance ID of an addon to register a listener for. + * @param aCallback + * The callback to invoke when updates are available for this addon. + * @throws if there is no addon matching the instanceID + */ + addUpgradeListener: function(aInstanceID, aCallback) { + if (!aInstanceID || typeof aInstanceID != "symbol") + throw Components.Exception("aInstanceID must be a symbol", + Cr.NS_ERROR_INVALID_ARG); + + if (!aCallback || typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + this.getAddonByInstanceID(aInstanceID).then(wrapper => { + if (!wrapper) { + throw Error("No addon matching instanceID:", aInstanceID.toString()); + } + let addonId = wrapper.addonId(); + logger.debug(`Registering upgrade listener for ${addonId}`); + this.upgradeListeners.set(addonId, aCallback); + }); + }, + + /** + * Removes an UpgradeListener if the listener is registered. + * + * @param aInstanceID + * The instance ID of the addon to remove + */ + removeUpgradeListener: function(aInstanceID) { + if (!aInstanceID || typeof aInstanceID != "symbol") + throw Components.Exception("aInstanceID must be a symbol", + Cr.NS_ERROR_INVALID_ARG); + + this.getAddonByInstanceID(aInstanceID).then(addon => { + if (!addon) { + throw Error("No addon for instanceID:", aInstanceID.toString()); + } + if (this.upgradeListeners.has(addon.id)) { + this.upgradeListeners.delete(addon.id); + } else { + throw Error("No upgrade listener registered for addon ID:", addon.id); + } + }); + }, + + /** + * Installs a temporary add-on from a local file or directory. + * @param aFile + * An nsIFile for the file or directory of the add-on to be + * temporarily installed. + * @return a Promise that rejects if the add-on is not a valid restartless + * add-on or if the same ID is already temporarily installed. + */ + installTemporaryAddon: function(aFile) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!(aFile instanceof Ci.nsIFile)) + throw Components.Exception("aFile must be a nsIFile", + Cr.NS_ERROR_INVALID_ARG); + + return AddonManagerInternal._getProviderByName("XPIProvider") + .installTemporaryAddon(aFile); + }, + + installAddonFromSources: function(aFile) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!(aFile instanceof Ci.nsIFile)) + throw Components.Exception("aFile must be a nsIFile", + Cr.NS_ERROR_INVALID_ARG); + + return AddonManagerInternal._getProviderByName("XPIProvider") + .installAddonFromSources(aFile); + }, + + /** + * Returns an Addon corresponding to an instance ID. + * @param aInstanceID + * An Addon Instance ID symbol + * @return {Promise} + * @resolves The found Addon or null if no such add-on exists. + * @rejects Never + * @throws if the aInstanceID argument is not specified + * or the AddonManager is not initialized + */ + getAddonByInstanceID: function(aInstanceID) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aInstanceID || typeof aInstanceID != "symbol") + throw Components.Exception("aInstanceID must be a Symbol()", + Cr.NS_ERROR_INVALID_ARG); + + return AddonManagerInternal._getProviderByName("XPIProvider") + .getAddonByInstanceID(aInstanceID); + }, + + /** + * Gets an icon from the icon set provided by the add-on + * that is closest to the specified size. + * + * The optional window parameter will be used to determine + * the screen resolution and select a more appropriate icon. + * Calling this method with 48px on retina screens will try to + * match an icon of size 96px. + * + * @param aAddon + * An addon object, meaning: + * An object with either an icons property that is a key-value + * list of icon size and icon URL, or an object having an iconURL + * and icon64URL property. + * @param aSize + * Ideal icon size in pixels + * @param aWindow + * Optional window object for determining the correct scale. + * @return {String} The absolute URL of the icon or null if the addon doesn't have icons + */ + getPreferredIconURL: function(aAddon, aSize, aWindow = undefined) { + if (aWindow && aWindow.devicePixelRatio) { + aSize *= aWindow.devicePixelRatio; + } + + let icons = aAddon.icons; + + // certain addon-types only have iconURLs + if (!icons) { + icons = {}; + if (aAddon.iconURL) { + icons[32] = aAddon.iconURL; + icons[48] = aAddon.iconURL; + } + if (aAddon.icon64URL) { + icons[64] = aAddon.icon64URL; + } + } + + // quick return if the exact size was found + if (icons[aSize]) { + return icons[aSize]; + } + + let bestSize = null; + + for (let size of Object.keys(icons)) { + if (!INTEGER.test(size)) { + throw Components.Exception("Invalid icon size, must be an integer", + Cr.NS_ERROR_ILLEGAL_VALUE); + } + + size = parseInt(size, 10); + + if (!bestSize) { + bestSize = size; + continue; + } + + if (size > aSize && bestSize > aSize) { + // If both best size and current size are larger than the wanted size then choose + // the one closest to the wanted size + bestSize = Math.min(bestSize, size); + } + else { + // Otherwise choose the largest of the two so we'll prefer sizes as close to below aSize + // or above aSize + bestSize = Math.max(bestSize, size); + } + } + + return icons[bestSize] || null; + }, + + /** + * Asynchronously gets an add-on with a specific ID. + * + * @param aID + * The ID of the add-on to retrieve + * @return {Promise} + * @resolves The found Addon or null if no such add-on exists. + * @rejects Never + * @throws if the aID argument is not specified + */ + getAddonByID: function(aID) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aID || typeof aID != "string") + throw Components.Exception("aID must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + let promises = Array.from(this.providers, + p => promiseCallProvider(p, "getAddonByID", aID)); + return Promise.all(promises).then(aAddons => { + return aAddons.find(a => !!a) || null; + }); + }, + + /** + * Asynchronously get an add-on with a specific Sync GUID. + * + * @param aGUID + * String GUID of add-on to retrieve + * @param aCallback + * The callback to pass the retrieved add-on to. + * @throws if the aGUID or aCallback arguments are not specified + */ + getAddonBySyncGUID: function(aGUID, aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!aGUID || typeof aGUID != "string") + throw Components.Exception("aGUID must be a non-empty string", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "getAddonBySyncGUID", aGUID, + function(aAddon) { + if (aAddon) { + safeCall(aCallback, aAddon); + } else { + aCaller.callNext(); + } + }); + }, + + noMoreObjects: function(aCaller) { + safeCall(aCallback, null); + } + }); + }, + + /** + * Asynchronously gets an array of add-ons. + * + * @param aIDs + * The array of IDs to retrieve + * @return {Promise} + * @resolves The array of found add-ons. + * @rejects Never + * @throws if the aIDs argument is not specified + */ + getAddonsByIDs: function(aIDs) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!Array.isArray(aIDs)) + throw Components.Exception("aIDs must be an array", + Cr.NS_ERROR_INVALID_ARG); + + let promises = aIDs.map(a => AddonManagerInternal.getAddonByID(a)); + return Promise.all(promises); + }, + + /** + * Asynchronously gets add-ons of specific types. + * + * @param aTypes + * An optional array of types to retrieve. Each type is a string name + * @param aCallback + * The callback to pass an array of Addons to. + * @throws if the aCallback argument is not specified + */ + getAddonsByTypes: function(aTypes, aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (aTypes && !Array.isArray(aTypes)) + throw Components.Exception("aTypes must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + let addons = []; + + new AsyncObjectCaller(this.providers, "getAddonsByTypes", { + nextObject: function(aCaller, aProvider) { + callProviderAsync(aProvider, "getAddonsByTypes", aTypes, + function(aProviderAddons) { + if (aProviderAddons) { + addons = addons.concat(aProviderAddons); + } + aCaller.callNext(); + }); + }, + + noMoreObjects: function(aCaller) { + safeCall(aCallback, addons); + } + }); + }, + + /** + * Asynchronously gets all installed add-ons. + * + * @param aCallback + * A callback which will be passed an array of Addons + */ + getAllAddons: function(aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + this.getAddonsByTypes(null, aCallback); + }, + + /** + * Asynchronously gets add-ons that have operations waiting for an application + * restart to complete. + * + * @param aTypes + * An optional array of types to retrieve. Each type is a string name + * @param aCallback + * The callback to pass the array of Addons to + * @throws if the aCallback argument is not specified + */ + getAddonsWithOperationsByTypes: function(aTypes, aCallback) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (aTypes && !Array.isArray(aTypes)) + throw Components.Exception("aTypes must be an array or null", + Cr.NS_ERROR_INVALID_ARG); + + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + let addons = []; + + new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", { + nextObject: function getAddonsWithOperationsByTypes_nextObject + (aCaller, aProvider) { + callProviderAsync(aProvider, "getAddonsWithOperationsByTypes", aTypes, + function getAddonsWithOperationsByTypes_concatAddons + (aProviderAddons) { + if (aProviderAddons) { + addons = addons.concat(aProviderAddons); + } + aCaller.callNext(); + }); + }, + + noMoreObjects: function(caller) { + safeCall(aCallback, addons); + } + }); + }, + + /** + * Adds a new AddonManagerListener if the listener is not already registered. + * + * @param aListener + * The listener to add + */ + addManagerListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be an AddonManagerListener object", + Cr.NS_ERROR_INVALID_ARG); + + if (!this.managerListeners.some(i => i == aListener)) + this.managerListeners.push(aListener); + }, + + /** + * Removes an AddonManagerListener if the listener is registered. + * + * @param aListener + * The listener to remove + */ + removeManagerListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be an AddonManagerListener object", + Cr.NS_ERROR_INVALID_ARG); + + let pos = 0; + while (pos < this.managerListeners.length) { + if (this.managerListeners[pos] == aListener) + this.managerListeners.splice(pos, 1); + else + pos++; + } + }, + + /** + * Adds a new AddonListener if the listener is not already registered. + * + * @param aListener + * The AddonListener to add + */ + addAddonListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be an AddonListener object", + Cr.NS_ERROR_INVALID_ARG); + + if (!this.addonListeners.some(i => i == aListener)) + this.addonListeners.push(aListener); + }, + + /** + * Removes an AddonListener if the listener is registered. + * + * @param aListener + * The AddonListener to remove + */ + removeAddonListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be an AddonListener object", + Cr.NS_ERROR_INVALID_ARG); + + let pos = 0; + while (pos < this.addonListeners.length) { + if (this.addonListeners[pos] == aListener) + this.addonListeners.splice(pos, 1); + else + pos++; + } + }, + + /** + * Adds a new TypeListener if the listener is not already registered. + * + * @param aListener + * The TypeListener to add + */ + addTypeListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be a TypeListener object", + Cr.NS_ERROR_INVALID_ARG); + + if (!this.typeListeners.some(i => i == aListener)) + this.typeListeners.push(aListener); + }, + + /** + * Removes an TypeListener if the listener is registered. + * + * @param aListener + * The TypeListener to remove + */ + removeTypeListener: function(aListener) { + if (!aListener || typeof aListener != "object") + throw Components.Exception("aListener must be a TypeListener object", + Cr.NS_ERROR_INVALID_ARG); + + let pos = 0; + while (pos < this.typeListeners.length) { + if (this.typeListeners[pos] == aListener) + this.typeListeners.splice(pos, 1); + else + pos++; + } + }, + + get addonTypes() { + // A read-only wrapper around the types dictionary + return new Proxy(this.types, { + defineProperty(target, property, descriptor) { + // Not allowed to define properties + return false; + }, + + deleteProperty(target, property) { + // Not allowed to delete properties + return false; + }, + + get(target, property, receiver) { + if (!target.hasOwnProperty(property)) + return undefined; + + return target[property].type; + }, + + getOwnPropertyDescriptor(target, property) { + if (!target.hasOwnProperty(property)) + return undefined; + + return { + value: target[property].type, + writable: false, + // Claim configurability to maintain the proxy invariants. + configurable: true, + enumerable: true + } + }, + + preventExtensions(target) { + // Not allowed to prevent adding new properties + return false; + }, + + set(target, property, value, receiver) { + // Not allowed to set properties + return false; + }, + + setPrototypeOf(target, prototype) { + // Not allowed to change prototype + return false; + } + }); + }, + + get autoUpdateDefault() { + return gAutoUpdateDefault; + }, + + set autoUpdateDefault(aValue) { + aValue = !!aValue; + if (aValue != gAutoUpdateDefault) + Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue); + return aValue; + }, + + get checkCompatibility() { + return gCheckCompatibility; + }, + + set checkCompatibility(aValue) { + aValue = !!aValue; + if (aValue != gCheckCompatibility) { + if (!aValue) + Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false); + else + Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY); + } + return aValue; + }, + + get strictCompatibility() { + return gStrictCompatibility; + }, + + set strictCompatibility(aValue) { + aValue = !!aValue; + if (aValue != gStrictCompatibility) + Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue); + return aValue; + }, + + get checkUpdateSecurityDefault() { + return gCheckUpdateSecurityDefault; + }, + + get checkUpdateSecurity() { + return gCheckUpdateSecurity; + }, + + set checkUpdateSecurity(aValue) { + aValue = !!aValue; + if (aValue != gCheckUpdateSecurity) { + if (aValue != gCheckUpdateSecurityDefault) + Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue); + else + Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY); + } + return aValue; + }, + + get updateEnabled() { + return gUpdateEnabled; + }, + + set updateEnabled(aValue) { + aValue = !!aValue; + if (aValue != gUpdateEnabled) + Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue); + return aValue; + }, + + get hotfixID() { + return gHotfixID; + }, + + webAPI: { + // installs maps integer ids to AddonInstall instances. + installs: new Map(), + nextInstall: 0, + + sendEvent: null, + setEventHandler(fn) { + this.sendEvent = fn; + }, + + getAddonByID(target, id) { + return new Promise(resolve => { + AddonManager.getAddonByID(id, (addon) => { + resolve(webAPIForAddon(addon)); + }); + }); + }, + + // helper to copy (and convert) the properties we care about + copyProps(install, obj) { + obj.state = AddonManager.stateToString(install.state); + obj.error = AddonManager.errorToString(install.error); + obj.progress = install.progress; + obj.maxProgress = install.maxProgress; + }, + + makeListener(id, mm) { + const events = [ + "onDownloadStarted", + "onDownloadProgress", + "onDownloadEnded", + "onDownloadCancelled", + "onDownloadFailed", + "onInstallStarted", + "onInstallEnded", + "onInstallCancelled", + "onInstallFailed", + ]; + + let listener = {}; + events.forEach(event => { + listener[event] = (install) => { + let data = {event, id}; + AddonManager.webAPI.copyProps(install, data); + this.sendEvent(mm, data); + } + }); + return listener; + }, + + forgetInstall(id) { + let info = this.installs.get(id); + if (!info) { + throw new Error(`forgetInstall cannot find ${id}`); + } + info.install.removeListener(info.listener); + this.installs.delete(id); + }, + + createInstall(target, options) { + // Throw an appropriate error if the given URL is not valid + // as an installation source. Return silently if it is okay. + function checkInstallUrl(url) { + let host = Services.io.newURI(options.url, null, null).host; + if (WEBAPI_INSTALL_HOSTS.includes(host)) { + return; + } + if (Services.prefs.getBoolPref(PREF_WEBAPI_TESTING) + && WEBAPI_TEST_INSTALL_HOSTS.includes(host)) { + return; + } + + throw new Error(`Install from ${host} not permitted`); + } + + return new Promise((resolve, reject) => { + try { + checkInstallUrl(options.url); + } catch (err) { + reject({message: err.message}); + return; + } + + let newInstall = install => { + let id = this.nextInstall++; + let listener = this.makeListener(id, target.messageManager); + install.addListener(listener); + + this.installs.set(id, {install, target, listener}); + + let result = {id}; + this.copyProps(install, result); + resolve(result); + }; + AddonManager.getInstallForURL(options.url, newInstall, "application/x-xpinstall", options.hash); + }); + }, + + addonUninstall(target, id) { + return new Promise(resolve => { + AddonManager.getAddonByID(id, addon => { + if (!addon) { + resolve(false); + } + + try { + addon.uninstall(); + resolve(true); + } catch (err) { + Cu.reportError(err); + resolve(false); + } + }); + }); + }, + + addonSetEnabled(target, id, value) { + return new Promise((resolve, reject) => { + AddonManager.getAddonByID(id, addon => { + if (!addon) { + reject({message: `No such addon ${id}`}); + } + addon.userDisabled = !value; + resolve(); + }); + }); + }, + + addonInstallDoInstall(target, id) { + let state = this.installs.get(id); + if (!state) { + return Promise.reject(`invalid id ${id}`); + } + return Promise.resolve(state.install.install()); + }, + + addonInstallCancel(target, id) { + let state = this.installs.get(id); + if (!state) { + return Promise.reject(`invalid id ${id}`); + } + return Promise.resolve(state.install.cancel()); + }, + + clearInstalls(ids) { + for (let id of ids) { + this.forgetInstall(id); + } + }, + + clearInstallsFrom(mm) { + for (let [id, info] of this.installs) { + if (info.target.messageManager == mm) { + this.forgetInstall(id); + } + } + }, + }, +}; + +/** + * Should not be used outside of core Mozilla code. This is a private API for + * the startup and platform integration code to use. Refer to the methods on + * AddonManagerInternal for documentation however note that these methods are + * subject to change at any time. + */ +this.AddonManagerPrivate = { + startup: function() { + AddonManagerInternal.startup(); + }, + + registerProvider: function(aProvider, aTypes) { + AddonManagerInternal.registerProvider(aProvider, aTypes); + }, + + unregisterProvider: function(aProvider) { + AddonManagerInternal.unregisterProvider(aProvider); + }, + + markProviderSafe: function(aProvider) { + AddonManagerInternal.markProviderSafe(aProvider); + }, + + backgroundUpdateCheck: function() { + return AddonManagerInternal.backgroundUpdateCheck(); + }, + + backgroundUpdateTimerHandler() { + // Don't call through to the real update check if no checks are enabled. + let checkHotfix = AddonManagerInternal.hotfixID && + Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && + Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); + + if (!AddonManagerInternal.updateEnabled && !checkHotfix) { + logger.info("Skipping background update check"); + return; + } + // Don't return the promise here, since the caller doesn't care. + AddonManagerInternal.backgroundUpdateCheck(); + }, + + addStartupChange: function(aType, aID) { + AddonManagerInternal.addStartupChange(aType, aID); + }, + + removeStartupChange: function(aType, aID) { + AddonManagerInternal.removeStartupChange(aType, aID); + }, + + notifyAddonChanged: function(aID, aType, aPendingRestart) { + AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart); + }, + + updateAddonAppDisabledStates: function() { + AddonManagerInternal.updateAddonAppDisabledStates(); + }, + + updateAddonRepositoryData: function(aCallback) { + AddonManagerInternal.updateAddonRepositoryData(aCallback); + }, + + callInstallListeners: function(...aArgs) { + return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal, + aArgs); + }, + + callAddonListeners: function(...aArgs) { + AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs); + }, + + AddonAuthor: AddonAuthor, + + AddonScreenshot: AddonScreenshot, + + AddonCompatibilityOverride: AddonCompatibilityOverride, + + AddonType: AddonType, + + recordTimestamp: function(name, value) { + AddonManagerInternal.recordTimestamp(name, value); + }, + + _simpleMeasures: {}, + recordSimpleMeasure: function(name, value) { + this._simpleMeasures[name] = value; + }, + + recordException: function(aModule, aContext, aException) { + let report = { + module: aModule, + context: aContext + }; + + if (typeof aException == "number") { + report.message = Components.Exception("", aException).name; + } + else { + report.message = aException.toString(); + if (aException.fileName) { + report.file = aException.fileName; + report.line = aException.lineNumber; + } + } + + this._simpleMeasures.exception = report; + }, + + getSimpleMeasures: function() { + return this._simpleMeasures; + }, + + getTelemetryDetails: function() { + return AddonManagerInternal.telemetryDetails; + }, + + setTelemetryDetails: function(aProvider, aDetails) { + AddonManagerInternal.telemetryDetails[aProvider] = aDetails; + }, + + // Start a timer, record a simple measure of the time interval when + // timer.done() is called + simpleTimer: function(aName) { + let startTime = Cu.now(); + return { + done: () => this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime)) + }; + }, + + /** + * Helper to call update listeners when no update is available. + * + * This can be used as an implementation for Addon.findUpdates() when + * no update mechanism is available. + */ + callNoUpdateListeners: function(addon, listener, reason, appVersion, platformVersion) { + if ("onNoCompatibilityUpdateAvailable" in listener) { + safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon); + } + if ("onNoUpdateAvailable" in listener) { + safeCall(listener.onNoUpdateAvailable.bind(listener), addon); + } + if ("onUpdateFinished" in listener) { + safeCall(listener.onUpdateFinished.bind(listener), addon); + } + }, + + get webExtensionsMinPlatformVersion() { + return gWebExtensionsMinPlatformVersion; + }, + + hasUpgradeListener: function(aId) { + return AddonManagerInternal.upgradeListeners.has(aId); + }, + + getUpgradeListener: function(aId) { + return AddonManagerInternal.upgradeListeners.get(aId); + }, +}; + +/** + * This is the public API that UI and developers should be calling. All methods + * just forward to AddonManagerInternal. + */ +this.AddonManager = { + // Constants for the AddonInstall.state property + // These will show up as AddonManager.STATE_* (eg, STATE_AVAILABLE) + _states: new Map([ + // The install is available for download. + ["STATE_AVAILABLE", 0], + // The install is being downloaded. + ["STATE_DOWNLOADING", 1], + // The install is checking for compatibility information. + ["STATE_CHECKING", 2], + // The install is downloaded and ready to install. + ["STATE_DOWNLOADED", 3], + // The download failed. + ["STATE_DOWNLOAD_FAILED", 4], + // The install has been postponed. + ["STATE_POSTPONED", 5], + // The add-on is being installed. + ["STATE_INSTALLING", 6], + // The add-on has been installed. + ["STATE_INSTALLED", 7], + // The install failed. + ["STATE_INSTALL_FAILED", 8], + // The install has been cancelled. + ["STATE_CANCELLED", 9], + ]), + + // Constants representing different types of errors while downloading an + // add-on. + // These will show up as AddonManager.ERROR_* (eg, ERROR_NETWORK_FAILURE) + _errors: new Map([ + // The download failed due to network problems. + ["ERROR_NETWORK_FAILURE", -1], + // The downloaded file did not match the provided hash. + ["ERROR_INCORRECT_HASH", -2], + // The downloaded file seems to be corrupted in some way. + ["ERROR_CORRUPT_FILE", -3], + // An error occured trying to write to the filesystem. + ["ERROR_FILE_ACCESS", -4], + // The add-on must be signed and isn't. + ["ERROR_SIGNEDSTATE_REQUIRED", -5], + // The downloaded add-on had a different type than expected. + ["ERROR_UNEXPECTED_ADDON_TYPE", -6], + // The addon did not have the expected ID + ["ERROR_INCORRECT_ID", -7], + ]), + + // These must be kept in sync with AddonUpdateChecker. + // No error was encountered. + UPDATE_STATUS_NO_ERROR: 0, + // The update check timed out + UPDATE_STATUS_TIMEOUT: -1, + // There was an error while downloading the update information. + UPDATE_STATUS_DOWNLOAD_ERROR: -2, + // The update information was malformed in some way. + UPDATE_STATUS_PARSE_ERROR: -3, + // The update information was not in any known format. + UPDATE_STATUS_UNKNOWN_FORMAT: -4, + // The update information was not correctly signed or there was an SSL error. + UPDATE_STATUS_SECURITY_ERROR: -5, + // The update was cancelled. + UPDATE_STATUS_CANCELLED: -6, + + // Constants to indicate why an update check is being performed + // Update check has been requested by the user. + UPDATE_WHEN_USER_REQUESTED: 1, + // Update check is necessary to see if the Addon is compatibile with a new + // version of the application. + UPDATE_WHEN_NEW_APP_DETECTED: 2, + // Update check is necessary because a new application has been installed. + UPDATE_WHEN_NEW_APP_INSTALLED: 3, + // Update check is a regular background update check. + UPDATE_WHEN_PERIODIC_UPDATE: 16, + // Update check is needed to check an Addon that is being installed. + UPDATE_WHEN_ADDON_INSTALLED: 17, + + // Constants for operations in Addon.pendingOperations + // Indicates that the Addon has no pending operations. + PENDING_NONE: 0, + // Indicates that the Addon will be enabled after the application restarts. + PENDING_ENABLE: 1, + // Indicates that the Addon will be disabled after the application restarts. + PENDING_DISABLE: 2, + // Indicates that the Addon will be uninstalled after the application restarts. + PENDING_UNINSTALL: 4, + // Indicates that the Addon will be installed after the application restarts. + PENDING_INSTALL: 8, + PENDING_UPGRADE: 16, + + // Constants for operations in Addon.operationsRequiringRestart + // Indicates that restart isn't required for any operation. + OP_NEEDS_RESTART_NONE: 0, + // Indicates that restart is required for enabling the addon. + OP_NEEDS_RESTART_ENABLE: 1, + // Indicates that restart is required for disabling the addon. + OP_NEEDS_RESTART_DISABLE: 2, + // Indicates that restart is required for uninstalling the addon. + OP_NEEDS_RESTART_UNINSTALL: 4, + // Indicates that restart is required for installing the addon. + OP_NEEDS_RESTART_INSTALL: 8, + + // Constants for permissions in Addon.permissions. + // Indicates that the Addon can be uninstalled. + PERM_CAN_UNINSTALL: 1, + // Indicates that the Addon can be enabled by the user. + PERM_CAN_ENABLE: 2, + // Indicates that the Addon can be disabled by the user. + PERM_CAN_DISABLE: 4, + // Indicates that the Addon can be upgraded. + PERM_CAN_UPGRADE: 8, + // Indicates that the Addon can be set to be optionally enabled + // on a case-by-case basis. + PERM_CAN_ASK_TO_ACTIVATE: 16, + + // General descriptions of where items are installed. + // Installed in this profile. + SCOPE_PROFILE: 1, + // Installed for all of this user's profiles. + SCOPE_USER: 2, + // Installed and owned by the application. + SCOPE_APPLICATION: 4, + // Installed for all users of the computer. + SCOPE_SYSTEM: 8, + // Installed temporarily + SCOPE_TEMPORARY: 16, + // The combination of all scopes. + SCOPE_ALL: 31, + + // Add-on type is expected to be displayed in the UI in a list. + VIEW_TYPE_LIST: "list", + + // Constants describing how add-on types behave. + + // If no add-ons of a type are installed, then the category for that add-on + // type should be hidden in the UI. + TYPE_UI_HIDE_EMPTY: 16, + // Indicates that this add-on type supports the ask-to-activate state. + // That is, add-ons of this type can be set to be optionally enabled + // on a case-by-case basis. + TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32, + // The add-on type natively supports undo for restartless uninstalls. + // If this flag is not specified, the UI is expected to handle this via + // disabling the add-on, and performing the actual uninstall at a later time. + TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL: 64, + + // Constants for Addon.applyBackgroundUpdates. + // Indicates that the Addon should not update automatically. + AUTOUPDATE_DISABLE: 0, + // Indicates that the Addon should update automatically only if + // that's the global default. + AUTOUPDATE_DEFAULT: 1, + // Indicates that the Addon should update automatically. + AUTOUPDATE_ENABLE: 2, + + // Constants for how Addon options should be shown. + // Options will be opened in a new window + OPTIONS_TYPE_DIALOG: 1, + // Options will be displayed within the AM detail view + OPTIONS_TYPE_INLINE: 2, + // Options will be displayed in a new tab, if possible + OPTIONS_TYPE_TAB: 3, + // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown. + // Used to indicate that only non-interactive information will be shown. + OPTIONS_TYPE_INLINE_INFO: 4, + // Similar to OPTIONS_TYPE_INLINE, but rather than generating inline + // options from a specially-formatted XUL file, the contents of the + // file are simply displayed in an inline element. + OPTIONS_TYPE_INLINE_BROWSER: 5, + + // Constants for displayed or hidden options notifications + // Options notification will be displayed + OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed", + // Options notification will be hidden + OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden", + + // Constants for getStartupChanges, addStartupChange and removeStartupChange + // Add-ons that were detected as installed during startup. Doesn't include + // add-ons that were pending installation the last time the application ran. + STARTUP_CHANGE_INSTALLED: "installed", + // Add-ons that were detected as changed during startup. This includes an + // add-on moving to a different location, changing version or just having + // been detected as possibly changed. + STARTUP_CHANGE_CHANGED: "changed", + // Add-ons that were detected as uninstalled during startup. Doesn't include + // add-ons that were pending uninstallation the last time the application ran. + STARTUP_CHANGE_UNINSTALLED: "uninstalled", + // Add-ons that were detected as disabled during startup, normally because of + // an application change making an add-on incompatible. Doesn't include + // add-ons that were pending being disabled the last time the application ran. + STARTUP_CHANGE_DISABLED: "disabled", + // Add-ons that were detected as enabled during startup, normally because of + // an application change making an add-on compatible. Doesn't include + // add-ons that were pending being enabled the last time the application ran. + STARTUP_CHANGE_ENABLED: "enabled", + + // Constants for Addon.signedState. Any states that should cause an add-on + // to be unusable in builds that require signing should have negative values. + // Add-on signing is not required, e.g. because the pref is disabled. + SIGNEDSTATE_NOT_REQUIRED: undefined, + // Add-on is signed but signature verification has failed. + SIGNEDSTATE_BROKEN: -2, + // Add-on may be signed but by an certificate that doesn't chain to our + // our trusted certificate. + SIGNEDSTATE_UNKNOWN: -1, + // Add-on is unsigned. + SIGNEDSTATE_MISSING: 0, + // Add-on is preliminarily reviewed. + SIGNEDSTATE_PRELIMINARY: 1, + // Add-on is fully reviewed. + SIGNEDSTATE_SIGNED: 2, + // Add-on is system add-on. + SIGNEDSTATE_SYSTEM: 3, + + // Constants for the Addon.userDisabled property + // Indicates that the userDisabled state of this add-on is currently + // ask-to-activate. That is, it can be conditionally enabled on a + // case-by-case basis. + STATE_ASK_TO_ACTIVATE: "askToActivate", + + get __AddonManagerInternal__() { + return AppConstants.DEBUG ? AddonManagerInternal : undefined; + }, + + get isReady() { + return gStartupComplete && !gShutdownInProgress; + }, + + init() { + this._stateToString = new Map(); + for (let [name, value] of this._states) { + this[name] = value; + this._stateToString.set(value, name); + } + this._errorToString = new Map(); + for (let [name, value] of this._errors) { + this[name] = value; + this._errorToString.set(value, name); + } + }, + + stateToString(state) { + return this._stateToString.get(state); + }, + + errorToString(err) { + return err ? this._errorToString.get(err) : null; + }, + + getInstallForURL: function(aUrl, aCallback, aMimetype, + aHash, aName, aIcons, + aVersion, aBrowser) { + AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash, + aName, aIcons, aVersion, aBrowser); + }, + + getInstallForFile: function(aFile, aCallback, aMimetype) { + AddonManagerInternal.getInstallForFile(aFile, aCallback, aMimetype); + }, + + /** + * Gets an array of add-on IDs that changed during the most recent startup. + * + * @param aType + * The type of startup change to get + * @return An array of add-on IDs + */ + getStartupChanges: function(aType) { + if (!(aType in AddonManagerInternal.startupChanges)) + return []; + return AddonManagerInternal.startupChanges[aType].slice(0); + }, + + getAddonByID: function(aID, aCallback) { + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + AddonManagerInternal.getAddonByID(aID) + .then(makeSafe(aCallback)) + .catch(logger.error); + }, + + getAddonBySyncGUID: function(aGUID, aCallback) { + AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback); + }, + + getAddonsByIDs: function(aIDs, aCallback) { + if (typeof aCallback != "function") + throw Components.Exception("aCallback must be a function", + Cr.NS_ERROR_INVALID_ARG); + + AddonManagerInternal.getAddonsByIDs(aIDs) + .then(makeSafe(aCallback)) + .catch(logger.error); + }, + + getAddonsWithOperationsByTypes: function(aTypes, aCallback) { + AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes, aCallback); + }, + + getAddonsByTypes: function(aTypes, aCallback) { + AddonManagerInternal.getAddonsByTypes(aTypes, aCallback); + }, + + getAllAddons: function(aCallback) { + AddonManagerInternal.getAllAddons(aCallback); + }, + + getInstallsByTypes: function(aTypes, aCallback) { + AddonManagerInternal.getInstallsByTypes(aTypes, aCallback); + }, + + getAllInstalls: function(aCallback) { + AddonManagerInternal.getAllInstalls(aCallback); + }, + + mapURIToAddonID: function(aURI) { + return AddonManagerInternal.mapURIToAddonID(aURI); + }, + + isInstallEnabled: function(aType) { + return AddonManagerInternal.isInstallEnabled(aType); + }, + + isInstallAllowed: function(aType, aInstallingPrincipal) { + return AddonManagerInternal.isInstallAllowed(aType, aInstallingPrincipal); + }, + + installAddonsFromWebpage: function(aType, aBrowser, aInstallingPrincipal, + aInstalls) { + AddonManagerInternal.installAddonsFromWebpage(aType, aBrowser, + aInstallingPrincipal, + aInstalls); + }, + + installTemporaryAddon: function(aDirectory) { + return AddonManagerInternal.installTemporaryAddon(aDirectory); + }, + + installAddonFromSources: function(aDirectory) { + return AddonManagerInternal.installAddonFromSources(aDirectory); + }, + + getAddonByInstanceID: function(aInstanceID) { + return AddonManagerInternal.getAddonByInstanceID(aInstanceID); + }, + + addManagerListener: function(aListener) { + AddonManagerInternal.addManagerListener(aListener); + }, + + removeManagerListener: function(aListener) { + AddonManagerInternal.removeManagerListener(aListener); + }, + + addInstallListener: function(aListener) { + AddonManagerInternal.addInstallListener(aListener); + }, + + removeInstallListener: function(aListener) { + AddonManagerInternal.removeInstallListener(aListener); + }, + + getUpgradeListener: function(aId) { + return AddonManagerInternal.upgradeListeners.get(aId); + }, + + addUpgradeListener: function(aInstanceID, aCallback) { + AddonManagerInternal.addUpgradeListener(aInstanceID, aCallback); + }, + + removeUpgradeListener: function(aInstanceID) { + AddonManagerInternal.removeUpgradeListener(aInstanceID); + }, + + addAddonListener: function(aListener) { + AddonManagerInternal.addAddonListener(aListener); + }, + + removeAddonListener: function(aListener) { + AddonManagerInternal.removeAddonListener(aListener); + }, + + addTypeListener: function(aListener) { + AddonManagerInternal.addTypeListener(aListener); + }, + + removeTypeListener: function(aListener) { + AddonManagerInternal.removeTypeListener(aListener); + }, + + get addonTypes() { + return AddonManagerInternal.addonTypes; + }, + + /** + * Determines whether an Addon should auto-update or not. + * + * @param aAddon + * The Addon representing the add-on + * @return true if the addon should auto-update, false otherwise. + */ + shouldAutoUpdate: function(aAddon) { + if (!aAddon || typeof aAddon != "object") + throw Components.Exception("aAddon must be specified", + Cr.NS_ERROR_INVALID_ARG); + + if (!("applyBackgroundUpdates" in aAddon)) + return false; + if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE) + return true; + if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE) + return false; + return this.autoUpdateDefault; + }, + + get checkCompatibility() { + return AddonManagerInternal.checkCompatibility; + }, + + set checkCompatibility(aValue) { + AddonManagerInternal.checkCompatibility = aValue; + }, + + get strictCompatibility() { + return AddonManagerInternal.strictCompatibility; + }, + + set strictCompatibility(aValue) { + AddonManagerInternal.strictCompatibility = aValue; + }, + + get checkUpdateSecurityDefault() { + return AddonManagerInternal.checkUpdateSecurityDefault; + }, + + get checkUpdateSecurity() { + return AddonManagerInternal.checkUpdateSecurity; + }, + + set checkUpdateSecurity(aValue) { + AddonManagerInternal.checkUpdateSecurity = aValue; + }, + + get updateEnabled() { + return AddonManagerInternal.updateEnabled; + }, + + set updateEnabled(aValue) { + AddonManagerInternal.updateEnabled = aValue; + }, + + get autoUpdateDefault() { + return AddonManagerInternal.autoUpdateDefault; + }, + + set autoUpdateDefault(aValue) { + AddonManagerInternal.autoUpdateDefault = aValue; + }, + + get hotfixID() { + return AddonManagerInternal.hotfixID; + }, + + escapeAddonURI: function(aAddon, aUri, aAppVersion) { + return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion); + }, + + getPreferredIconURL: function(aAddon, aSize, aWindow = undefined) { + return AddonManagerInternal.getPreferredIconURL(aAddon, aSize, aWindow); + }, + + get webAPI() { + return AddonManagerInternal.webAPI; + }, + + get shutdown() { + return gShutdownBarrier.client; + }, +}; + +this.AddonManager.init(); + +// load the timestamps module into AddonManagerInternal +Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal); +Object.freeze(AddonManagerInternal); +Object.freeze(AddonManagerPrivate); +Object.freeze(AddonManager); diff --git a/toolkit/mozapps/webextensions/AddonManagerWebAPI.cpp b/toolkit/mozapps/webextensions/AddonManagerWebAPI.cpp new file mode 100644 index 000000000..3f2a7a529 --- /dev/null +++ b/toolkit/mozapps/webextensions/AddonManagerWebAPI.cpp @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AddonManagerWebAPI.h" + +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/NavigatorBinding.h" + +#include "mozilla/Preferences.h" +#include "nsGlobalWindow.h" + +#include "nsIDocShell.h" +#include "nsIScriptObjectPrincipal.h" + +namespace mozilla { +using namespace mozilla::dom; + +static bool +IsValidHost(const nsACString& host) { + // This is ugly, but Preferences.h doesn't have support + // for default prefs or locked prefs + nsCOMPtr prefService (do_GetService(NS_PREFSERVICE_CONTRACTID)); + nsCOMPtr prefs; + if (prefService) { + prefService->GetDefaultBranch(nullptr, getter_AddRefs(prefs)); + bool isEnabled; + if (NS_SUCCEEDED(prefs->GetBoolPref("xpinstall.enabled", &isEnabled)) && !isEnabled) { + bool isLocked; + prefs->PrefIsLocked("xpinstall.enabled", &isLocked); + if (isLocked) { + return false; + } + } + } + + if (host.Equals("addons.mozilla.org") || + host.Equals("discovery.addons.mozilla.org") || + host.Equals("testpilot.firefox.com")) { + return true; + } + + // When testing allow access to the developer sites. + if (Preferences::GetBool("extensions.webapi.testing", false)) { + if (host.LowerCaseEqualsLiteral("addons.allizom.org") || + host.LowerCaseEqualsLiteral("discovery.addons.allizom.org") || + host.LowerCaseEqualsLiteral("addons-dev.allizom.org") || + host.LowerCaseEqualsLiteral("discovery.addons-dev.allizom.org") || + host.LowerCaseEqualsLiteral("testpilot.stage.mozaws.net") || + host.LowerCaseEqualsLiteral("testpilot.dev.mozaws.net") || + host.LowerCaseEqualsLiteral("example.com")) { + return true; + } + } + + return false; +} + +// Checks if the given uri is secure and matches one of the hosts allowed to +// access the API. +bool +AddonManagerWebAPI::IsValidSite(nsIURI* uri) +{ + if (!uri) { + return false; + } + + bool isSecure; + nsresult rv = uri->SchemeIs("https", &isSecure); + if (NS_FAILED(rv) || !isSecure) { + return false; + } + + nsAutoCString host; + rv = uri->GetHost(host); + if (NS_FAILED(rv)) { + return false; + } + + return IsValidHost(host); +} + +bool +AddonManagerWebAPI::IsAPIEnabled(JSContext* cx, JSObject* obj) +{ + nsGlobalWindow* global = xpc::WindowGlobalOrNull(obj); + if (!global) { + return false; + } + + nsCOMPtr win = global->AsInner(); + if (!win) { + return false; + } + + // Check that the current window and all parent frames are allowed access to + // the API. + while (win) { + nsCOMPtr sop = do_QueryInterface(win); + if (!sop) { + return false; + } + + nsCOMPtr principal = sop->GetPrincipal(); + if (!principal) { + return false; + } + + // Reaching a window with a system principal means we have reached + // privileged UI of some kind so stop at this point and allow access. + if (principal->GetIsSystemPrincipal()) { + return true; + } + + nsCOMPtr docShell = win->GetDocShell(); + if (!docShell) { + // This window has been torn down so don't allow access to the API. + return false; + } + + if (!IsValidSite(win->GetDocumentURI())) { + return false; + } + + // Checks whether there is a parent frame of the same type. This won't cross + // mozbrowser or chrome boundaries. + nsCOMPtr parent; + nsresult rv = docShell->GetSameTypeParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) { + return false; + } + + if (!parent) { + // No parent means we've hit a mozbrowser or chrome boundary so allow + // access to the API. + return true; + } + + nsIDocument* doc = win->GetDoc(); + if (!doc) { + return false; + } + + doc = doc->GetParentDocument(); + if (!doc) { + // Getting here means something has been torn down so fail safe. + return false; + } + + + win = doc->GetInnerWindow(); + } + + // Found a document with no inner window, don't grant access to the API. + return false; +} + +namespace dom { + +bool +AddonManagerPermissions::IsHostPermitted(const GlobalObject& /*unused*/, const nsAString& host) +{ + return IsValidHost(NS_ConvertUTF16toUTF8(host)); +} + +} // namespace mozilla::dom + + +} // namespace mozilla diff --git a/toolkit/mozapps/webextensions/AddonManagerWebAPI.h b/toolkit/mozapps/webextensions/AddonManagerWebAPI.h new file mode 100644 index 000000000..6830bc91f --- /dev/null +++ b/toolkit/mozapps/webextensions/AddonManagerWebAPI.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef addonmanagerwebapi_h_ +#define addonmanagerwebapi_h_ + +#include "nsPIDOMWindow.h" + +namespace mozilla { + +class AddonManagerWebAPI { +public: + static bool IsAPIEnabled(JSContext* cx, JSObject* obj); + +private: + static bool IsValidSite(nsIURI* uri); +}; + +namespace dom { + +class AddonManagerPermissions { +public: + static bool IsHostPermitted(const GlobalObject&, const nsAString& host); +}; + +} // namespace mozilla::dom + +} // namespace mozilla + +#endif // addonmanagerwebapi_h_ diff --git a/toolkit/mozapps/webextensions/AddonPathService.cpp b/toolkit/mozapps/webextensions/AddonPathService.cpp new file mode 100644 index 000000000..8a405c0ea --- /dev/null +++ b/toolkit/mozapps/webextensions/AddonPathService.cpp @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AddonPathService.h" + +#include "amIAddonManager.h" +#include "nsIURI.h" +#include "nsXULAppAPI.h" +#include "jsapi.h" +#include "nsServiceManagerUtils.h" +#include "nsLiteralString.h" +#include "nsThreadUtils.h" +#include "nsIIOService.h" +#include "nsNetUtil.h" +#include "nsIAddonPolicyService.h" +#include "nsIFileURL.h" +#include "nsIResProtocolHandler.h" +#include "nsIChromeRegistry.h" +#include "nsIJARURI.h" +#include "nsJSUtils.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/AddonPathService.h" +#include "mozilla/Omnijar.h" + +#include + +namespace mozilla { + +struct PathEntryComparator +{ + typedef AddonPathService::PathEntry PathEntry; + + bool Equals(const PathEntry& entry1, const PathEntry& entry2) const + { + return entry1.mPath == entry2.mPath; + } + + bool LessThan(const PathEntry& entry1, const PathEntry& entry2) const + { + return entry1.mPath < entry2.mPath; + } +}; + +AddonPathService::AddonPathService() +{ +} + +AddonPathService::~AddonPathService() +{ + sInstance = nullptr; +} + +NS_IMPL_ISUPPORTS(AddonPathService, amIAddonPathService) + +AddonPathService *AddonPathService::sInstance; + +/* static */ AddonPathService* +AddonPathService::GetInstance() +{ + if (!sInstance) { + sInstance = new AddonPathService(); + } + NS_ADDREF(sInstance); + return sInstance; +} + +static JSAddonId* +ConvertAddonId(const nsAString& addonIdString) +{ + AutoSafeJSContext cx; + JS::RootedValue strv(cx); + if (!mozilla::dom::ToJSValue(cx, addonIdString, &strv)) { + return nullptr; + } + JS::RootedString str(cx, strv.toString()); + return JS::NewAddonId(cx, str); +} + +JSAddonId* +AddonPathService::Find(const nsAString& path) +{ + // Use binary search to find the nearest entry that is <= |path|. + PathEntryComparator comparator; + unsigned index = mPaths.IndexOfFirstElementGt(PathEntry(path, nullptr), comparator); + if (index == 0) { + return nullptr; + } + const PathEntry& entry = mPaths[index - 1]; + + // Return the entry's addon if its path is a prefix of |path|. + if (StringBeginsWith(path, entry.mPath)) { + return entry.mAddonId; + } + return nullptr; +} + +NS_IMETHODIMP +AddonPathService::FindAddonId(const nsAString& path, nsAString& addonIdString) +{ + if (JSAddonId* id = Find(path)) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); + AssignJSFlatString(addonIdString, flat); + } + return NS_OK; +} + +/* static */ JSAddonId* +AddonPathService::FindAddonId(const nsAString& path) +{ + // If no service has been created, then we're not going to find anything. + if (!sInstance) { + return nullptr; + } + + return sInstance->Find(path); +} + +NS_IMETHODIMP +AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdString) +{ + JSAddonId* addonId = ConvertAddonId(addonIdString); + + // Add the new path in sorted order. + PathEntryComparator comparator; + mPaths.InsertElementSorted(PathEntry(path, addonId), comparator); + return NS_OK; +} + +NS_IMETHODIMP +AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString) +{ + if (JSAddonId* id = MapURIToAddonID(aURI)) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); + AssignJSFlatString(addonIdString, flat); + } + return NS_OK; +} + +static nsresult +ResolveURI(nsIURI* aURI, nsAString& out) +{ + bool equals; + nsresult rv; + nsCOMPtr uri; + nsAutoCString spec; + + // Resolve resource:// URIs. At the end of this if/else block, we + // have both spec and uri variables identifying the same URI. + if (NS_SUCCEEDED(aURI->SchemeIs("resource", &equals)) && equals) { + nsCOMPtr ioService = do_GetIOService(&rv); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + nsCOMPtr ph; + rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + nsCOMPtr irph(do_QueryInterface(ph, &rv)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + rv = irph->ResolveURI(aURI, spec); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + } else if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &equals)) && equals) { + nsCOMPtr chromeReg = + mozilla::services::GetChromeRegistryService(); + if (NS_WARN_IF(!chromeReg)) + return NS_ERROR_UNEXPECTED; + + rv = chromeReg->ConvertChromeURL(aURI, getter_AddRefs(uri)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + } else { + uri = aURI; + } + + if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) { + nsCOMPtr jarURI = do_QueryInterface(uri, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + nsCOMPtr jarFileURI; + rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + return ResolveURI(jarFileURI, out); + } + + if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) { + nsCOMPtr baseFileURL = do_QueryInterface(uri, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + nsCOMPtr file; + rv = baseFileURL->GetFile(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + return file->GetPath(out); + } + return NS_ERROR_FAILURE; +} + +JSAddonId* +MapURIToAddonID(nsIURI* aURI) +{ + if (!NS_IsMainThread() || !XRE_IsParentProcess()) { + return nullptr; + } + + bool equals; + nsresult rv; + if (NS_SUCCEEDED(aURI->SchemeIs("moz-extension", &equals)) && equals) { + nsCOMPtr service = do_GetService("@mozilla.org/addons/policy-service;1"); + if (service) { + nsString addonId; + rv = service->ExtensionURIToAddonId(aURI, addonId); + if (NS_FAILED(rv)) + return nullptr; + + return ConvertAddonId(addonId); + } + } + + nsAutoString filePath; + rv = ResolveURI(aURI, filePath); + if (NS_FAILED(rv)) + return nullptr; + + nsCOMPtr greJar = Omnijar::GetPath(Omnijar::GRE); + nsCOMPtr appJar = Omnijar::GetPath(Omnijar::APP); + if (greJar && appJar) { + nsAutoString greJarString, appJarString; + if (NS_FAILED(greJar->GetPath(greJarString)) || NS_FAILED(appJar->GetPath(appJarString))) + return nullptr; + + // If |aURI| is part of either Omnijar, then it can't be part of an + // add-on. This catches pretty much all URLs for Firefox content. + if (filePath.Equals(greJarString) || filePath.Equals(appJarString)) + return nullptr; + } + + // If it's not part of Firefox, we resort to binary searching through the + // add-on paths. + return AddonPathService::FindAddonId(filePath); +} + +} // namespace mozilla diff --git a/toolkit/mozapps/webextensions/AddonPathService.h b/toolkit/mozapps/webextensions/AddonPathService.h new file mode 100644 index 000000000..f739b018f --- /dev/null +++ b/toolkit/mozapps/webextensions/AddonPathService.h @@ -0,0 +1,55 @@ +//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef AddonPathService_h +#define AddonPathService_h + +#include "amIAddonPathService.h" +#include "nsString.h" +#include "nsTArray.h" + +class nsIURI; +class JSAddonId; + +namespace mozilla { + +JSAddonId* +MapURIToAddonID(nsIURI* aURI); + +class AddonPathService final : public amIAddonPathService +{ +public: + AddonPathService(); + + static AddonPathService* GetInstance(); + + JSAddonId* Find(const nsAString& path); + static JSAddonId* FindAddonId(const nsAString& path); + + NS_DECL_ISUPPORTS + NS_DECL_AMIADDONPATHSERVICE + + struct PathEntry + { + nsString mPath; + JSAddonId* mAddonId; + + PathEntry(const nsAString& aPath, JSAddonId* aAddonId) + : mPath(aPath), mAddonId(aAddonId) + {} + }; + +private: + virtual ~AddonPathService(); + + // Paths are stored sorted in order of their mPath. + nsTArray mPaths; + + static AddonPathService* sInstance; +}; + +} // namespace mozilla + +#endif diff --git a/toolkit/mozapps/webextensions/ChromeManifestParser.jsm b/toolkit/mozapps/webextensions/ChromeManifestParser.jsm new file mode 100644 index 000000000..63f1db785 --- /dev/null +++ b/toolkit/mozapps/webextensions/ChromeManifestParser.jsm @@ -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"; + +this.EXPORTED_SYMBOLS = ["ChromeManifestParser"]; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +const MSG_JAR_FLUSH = "AddonJarFlush"; + + +/** + * Sends local and remote notifications to flush a JAR file cache entry + * + * @param aJarFile + * The ZIP/XPI/JAR file as a nsIFile + */ +function flushJarCache(aJarFile) { + Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null); + Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster) + .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path); +} + + +/** + * Parses chrome manifest files. + */ +this.ChromeManifestParser = { + + /** + * Reads and parses a chrome manifest file located at a specified URI, and all + * secondary manifests it references. + * + * @param aURI + * A nsIURI pointing to a chrome manifest. + * Typically a file: or jar: URI. + * @return Array of objects describing each manifest instruction, in the form: + * { type: instruction-type, baseURI: string-uri, args: [arguments] } + **/ + parseSync: function(aURI) { + function parseLine(aLine) { + let line = aLine.trim(); + if (line.length == 0 || line.charAt(0) == '#') + return; + let tokens = line.split(/\s+/); + let type = tokens.shift(); + if (type == "manifest") { + let uri = NetUtil.newURI(tokens.shift(), null, aURI); + data = data.concat(this.parseSync(uri)); + } else { + data.push({type: type, baseURI: baseURI, args: tokens}); + } + } + + let contents = ""; + try { + if (aURI.scheme == "jar") + contents = this._readFromJar(aURI); + else + contents = this._readFromFile(aURI); + } catch (e) { + // Silently fail. + } + + if (!contents) + return []; + + let baseURI = NetUtil.newURI(".", null, aURI).spec; + + let data = []; + let lines = contents.split("\n"); + lines.forEach(parseLine.bind(this)); + return data; + }, + + _readFromJar: function(aURI) { + let data = ""; + let entries = []; + let readers = []; + + try { + // Deconstrict URI, which can be nested jar: URIs. + let uri = aURI.clone(); + while (uri instanceof Ci.nsIJARURI) { + entries.push(uri.JAREntry); + uri = uri.JARFile; + } + + // Open the base jar. + let reader = Cc["@mozilla.org/libjar/zip-reader;1"]. + createInstance(Ci.nsIZipReader); + reader.open(uri.QueryInterface(Ci.nsIFileURL).file); + readers.push(reader); + + // Open the nested jars. + for (let i = entries.length - 1; i > 0; i--) { + let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"]. + createInstance(Ci.nsIZipReader); + innerReader.openInner(reader, entries[i]); + readers.push(innerReader); + reader = innerReader; + } + + // First entry is the actual file we want to read. + let zis = reader.getInputStream(entries[0]); + data = NetUtil.readInputStreamToString(zis, zis.available()); + } + finally { + // Close readers in reverse order. + for (let i = readers.length - 1; i >= 0; i--) { + readers[i].close(); + flushJarCache(readers[i].file); + } + } + + return data; + }, + + _readFromFile: function(aURI) { + let file = aURI.QueryInterface(Ci.nsIFileURL).file; + if (!file.exists() || !file.isFile()) + return ""; + + let data = ""; + let fis = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + try { + fis.init(file, -1, -1, false); + data = NetUtil.readInputStreamToString(fis, fis.available()); + } finally { + fis.close(); + } + return data; + }, + + /** + * Detects if there were any instructions of a specified type in a given + * chrome manifest. + * + * @param aManifest + * Manifest data, as returned by ChromeManifestParser.parseSync(). + * @param aType + * Instruction type to filter by. + * @return True if any matching instructions were found in the manifest. + */ + hasType: function(aManifest, aType) { + return aManifest.some(entry => entry.type == aType); + } +}; diff --git a/toolkit/mozapps/webextensions/DeferredSave.jsm b/toolkit/mozapps/webextensions/DeferredSave.jsm new file mode 100644 index 000000000..89f82b265 --- /dev/null +++ b/toolkit/mozapps/webextensions/DeferredSave.jsm @@ -0,0 +1,275 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 = Components.utils; +const Cc = Components.classes; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/osfile.jsm"); +/* globals OS*/ +Cu.import("resource://gre/modules/Promise.jsm"); + +// Make it possible to mock out timers for testing +var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + +this.EXPORTED_SYMBOLS = ["DeferredSave"]; + +// If delay parameter is not provided, default is 50 milliseconds. +const DEFAULT_SAVE_DELAY_MS = 50; + +Cu.import("resource://gre/modules/Log.jsm"); +// Configure a logger at the parent 'DeferredSave' level to format +// messages for all the modules under DeferredSave.* +const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave"; +var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID); +parentLogger.level = Log.Level.Warn; +var formatter = new Log.BasicFormatter(); +// Set parent logger (and its children) to append to +// the Javascript section of the Browser Console +parentLogger.addAppender(new Log.ConsoleAppender(formatter)); +// Set parent logger (and its children) to +// also append to standard out +parentLogger.addAppender(new Log.DumpAppender(formatter)); + +// Provide the ability to enable/disable logging +// messages at runtime. +// If the "extensions.logging.enabled" preference is +// missing or 'false', messages at the WARNING and higher +// severity should be logged to the JS console and standard error. +// If "extensions.logging.enabled" is set to 'true', messages +// at DEBUG and higher should go to JS console and standard error. +Cu.import("resource://gre/modules/Services.jsm"); + +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; + +/** +* Preference listener which listens for a change in the +* "extensions.logging.enabled" preference and changes the logging level of the +* parent 'addons' level logger accordingly. +*/ +var PrefObserver = { + init: function() { + Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); + Services.obs.addObserver(this, "xpcom-shutdown", false); + this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); + }, + + observe: function(aSubject, aTopic, aData) { + if (aTopic == "xpcom-shutdown") { + Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); + Services.obs.removeObserver(this, "xpcom-shutdown"); + } + else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { + let debugLogEnabled = false; + try { + debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); + } + catch (e) { + } + if (debugLogEnabled) { + parentLogger.level = Log.Level.Debug; + } + else { + parentLogger.level = Log.Level.Warn; + } + } + } +}; + +PrefObserver.init(); + +/** + * A module to manage deferred, asynchronous writing of data files + * to disk. Writing is deferred by waiting for a specified delay after + * a request to save the data, before beginning to write. If more than + * one save request is received during the delay, all requests are + * fulfilled by a single write. + * + * @constructor + * @param aPath + * String representing the full path of the file where the data + * is to be written. + * @param aDataProvider + * Callback function that takes no argument and returns the data to + * be written. If aDataProvider returns an ArrayBufferView, the + * bytes it contains are written to the file as is. + * If aDataProvider returns a String the data are UTF-8 encoded + * and then written to the file. + * @param [optional] aDelay + * The delay in milliseconds between the first saveChanges() call + * that marks the data as needing to be saved, and when the DeferredSave + * begins writing the data to disk. Default 50 milliseconds. + */ +this.DeferredSave = function(aPath, aDataProvider, aDelay) { + // Create a new logger (child of 'DeferredSave' logger) + // for use by this particular instance of DeferredSave object + let leafName = OS.Path.basename(aPath); + let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName; + this.logger = Log.repository.getLogger(logger_id); + + // @type {Deferred|null}, null when no data needs to be written + // @resolves with the result of OS.File.writeAtomic when all writes complete + // @rejects with the error from OS.File.writeAtomic if the write fails, + // or with the error from aDataProvider() if that throws. + this._pending = null; + + // @type {Promise}, completes when the in-progress write (if any) completes, + // kept as a resolved promise at other times to simplify logic. + // Because _deferredSave() always uses _writing.then() to execute + // its next action, we don't need a special case for whether a write + // is in progress - if the previous write is complete (and the _writing + // promise is already resolved/rejected), _writing.then() starts + // the next action immediately. + // + // @resolves with the result of OS.File.writeAtomic + // @rejects with the error from OS.File.writeAtomic + this._writing = Promise.resolve(0); + + // Are we currently waiting for a write to complete + this.writeInProgress = false; + + this._path = aPath; + this._dataProvider = aDataProvider; + + this._timer = null; + + // Some counters for telemetry + // The total number of times the file was written + this.totalSaves = 0; + + // The number of times the data became dirty while + // another save was in progress + this.overlappedSaves = 0; + + // Error returned by the most recent write (if any) + this._lastError = null; + + if (aDelay && (aDelay > 0)) + this._delay = aDelay; + else + this._delay = DEFAULT_SAVE_DELAY_MS; +} + +this.DeferredSave.prototype = { + get dirty() { + return this._pending || this.writeInProgress; + }, + + get lastError() { + return this._lastError; + }, + + // Start the pending timer if data is dirty + _startTimer: function() { + if (!this._pending) { + return; + } + + this.logger.debug("Starting timer"); + if (!this._timer) + this._timer = MakeTimer(); + this._timer.initWithCallback(() => this._deferredSave(), + this._delay, Ci.nsITimer.TYPE_ONE_SHOT); + }, + + /** + * Mark the current stored data dirty, and schedule a flush to disk + * @return A Promise that will be resolved after the data is written to disk; + * the promise is resolved with the number of bytes written. + */ + saveChanges: function() { + this.logger.debug("Save changes"); + if (!this._pending) { + if (this.writeInProgress) { + this.logger.debug("Data changed while write in progress"); + this.overlappedSaves++; + } + this._pending = Promise.defer(); + // Wait until the most recent write completes or fails (if it hasn't already) + // and then restart our timer + this._writing.then(count => this._startTimer(), error => this._startTimer()); + } + return this._pending.promise; + }, + + _deferredSave: function() { + let pending = this._pending; + this._pending = null; + let writing = this._writing; + this._writing = pending.promise; + + // In either the success or the exception handling case, we don't need to handle + // the error from _writing here; it's already being handled in another then() + let toSave = null; + try { + toSave = this._dataProvider(); + } + catch (e) { + this.logger.error("Deferred save dataProvider failed", e); + writing.then(null, error => {}) + .then(count => { + pending.reject(e); + }); + return; + } + + writing.then(null, error => { return 0; }) + .then(count => { + this.logger.debug("Starting write"); + this.totalSaves++; + this.writeInProgress = true; + + OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"}) + .then( + result => { + this._lastError = null; + this.writeInProgress = false; + this.logger.debug("Write succeeded"); + pending.resolve(result); + }, + error => { + this._lastError = error; + this.writeInProgress = false; + this.logger.warn("Write failed", error); + pending.reject(error); + }); + }); + }, + + /** + * Immediately save the dirty data to disk, skipping + * the delay of normal operation. Note that the write + * still happens asynchronously in the worker + * thread from OS.File. + * + * There are four possible situations: + * 1) Nothing to flush + * 2) Data is not currently being written, in-memory copy is dirty + * 3) Data is currently being written, in-memory copy is clean + * 4) Data is being written and in-memory copy is dirty + * + * @return Promise that will resolve when all in-memory data + * has finished being flushed, returning the number of bytes + * written. If all in-memory data is clean, completes with the + * result of the most recent write. + */ + flush: function() { + // If we have pending changes, cancel our timer and set up the write + // immediately (_deferredSave queues the write for after the most + // recent write completes, if it hasn't already) + if (this._pending) { + this.logger.debug("Flush called while data is dirty"); + if (this._timer) { + this._timer.cancel(); + this._timer = null; + } + this._deferredSave(); + } + + return this._writing; + } +}; diff --git a/toolkit/mozapps/webextensions/LightweightThemeManager.jsm b/toolkit/mozapps/webextensions/LightweightThemeManager.jsm new file mode 100644 index 000000000..5dd41831d --- /dev/null +++ b/toolkit/mozapps/webextensions/LightweightThemeManager.jsm @@ -0,0 +1,909 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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.EXPORTED_SYMBOLS = ["LightweightThemeManager"]; + +const Cc = Components.classes; +const Ci = Components.interfaces; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/AddonManager.jsm"); +/* globals AddonManagerPrivate*/ +Components.utils.import("resource://gre/modules/Services.jsm"); + +const ID_SUFFIX = "@personas.mozilla.org"; +const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect"; +const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; +const PREF_EM_DSS_ENABLED = "extensions.dss.enabled"; +const ADDON_TYPE = "theme"; + +const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; + +const STRING_TYPE_NAME = "type.%ID%.name"; + +const DEFAULT_MAX_USED_THEMES_COUNT = 30; + +const MAX_PREVIEW_SECONDS = 30; + +const MANDATORY = ["id", "name", "headerURL"]; +const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL", + "previewURL", "author", "description", "homepageURL", + "updateURL", "version"]; + +const PERSIST_ENABLED = true; +const PERSIST_BYPASS_CACHE = false; +const PERSIST_FILES = { + headerURL: "lightweighttheme-header", + footerURL: "lightweighttheme-footer" +}; + +XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer", + "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest", + "resource://gre/modules/ServiceRequest.jsm"); + + +XPCOMUtils.defineLazyGetter(this, "_prefs", () => { + return Services.prefs.getBranch("lightweightThemes."); +}); + +Object.defineProperty(this, "_maxUsedThemes", { + get: function() { + delete this._maxUsedThemes; + try { + this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes"); + } + catch (e) { + this._maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; + } + return this._maxUsedThemes; + }, + + set: function(val) { + delete this._maxUsedThemes; + return this._maxUsedThemes = val; + }, + configurable: true, +}); + +// Holds the ID of the theme being enabled or disabled while sending out the +// events so cached AddonWrapper instances can return correct values for +// permissions and pendingOperations +var _themeIDBeingEnabled = null; +var _themeIDBeingDisabled = null; + +// Convert from the old storage format (in which the order of usedThemes +// was combined with isThemeSelected to determine which theme was selected) +// to the new one (where a selectedThemeID determines which theme is selected). +(function() { + let wasThemeSelected = false; + try { + wasThemeSelected = _prefs.getBoolPref("isThemeSelected"); + } catch (e) { } + + if (wasThemeSelected) { + _prefs.clearUserPref("isThemeSelected"); + let themes = []; + try { + themes = JSON.parse(_prefs.getComplexValue("usedThemes", + Ci.nsISupportsString).data); + } catch (e) { } + + if (Array.isArray(themes) && themes[0]) { + _prefs.setCharPref("selectedThemeID", themes[0].id); + } + } +})(); + +this.LightweightThemeManager = { + get name() { + return "LightweightThemeManager"; + }, + + // Themes that can be added for an application. They can't be removed, and + // will always show up at the top of the list. + _builtInThemes: new Map(), + + get usedThemes () { + let themes = []; + try { + themes = JSON.parse(_prefs.getComplexValue("usedThemes", + Ci.nsISupportsString).data); + } catch (e) { } + + themes.push(...this._builtInThemes.values()); + return themes; + }, + + get currentTheme () { + let selectedThemeID = null; + try { + selectedThemeID = _prefs.getCharPref("selectedThemeID"); + } catch (e) {} + + let data = null; + if (selectedThemeID) { + data = this.getUsedTheme(selectedThemeID); + } + return data; + }, + + get currentThemeForDisplay () { + var data = this.currentTheme; + + if (data && PERSIST_ENABLED) { + for (let key in PERSIST_FILES) { + try { + if (data[key] && _prefs.getBoolPref("persisted." + key)) + data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec + + "?" + data.id + ";" + _version(data); + } catch (e) {} + } + } + + return data; + }, + + set currentTheme (aData) { + return _setCurrentTheme(aData, false); + }, + + setLocalTheme: function(aData) { + _setCurrentTheme(aData, true); + }, + + getUsedTheme: function(aId) { + var usedThemes = this.usedThemes; + for (let usedTheme of usedThemes) { + if (usedTheme.id == aId) + return usedTheme; + } + return null; + }, + + forgetUsedTheme: function(aId) { + let theme = this.getUsedTheme(aId); + if (!theme || LightweightThemeManager._builtInThemes.has(theme.id)) + return; + + let wrapper = new AddonWrapper(theme); + AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); + + var currentTheme = this.currentTheme; + if (currentTheme && currentTheme.id == aId) { + this.themeChanged(null); + AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false); + } + + _updateUsedThemes(_usedThemesExceptId(aId)); + AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); + }, + + addBuiltInTheme: function(theme) { + if (!theme || !theme.id || this.usedThemes.some(t => t.id == theme.id)) { + throw new Error("Trying to add invalid builtIn theme"); + } + + this._builtInThemes.set(theme.id, theme); + + if (_prefs.getCharPref("selectedThemeID") == theme.id) { + this.currentTheme = theme; + } + }, + + forgetBuiltInTheme: function(id) { + if (!this._builtInThemes.has(id)) { + let currentTheme = this.currentTheme; + if (currentTheme && currentTheme.id == id) { + this.currentTheme = null; + } + } + return this._builtInThemes.delete(id); + }, + + clearBuiltInThemes: function() { + for (let id of this._builtInThemes.keys()) { + this.forgetBuiltInTheme(id); + } + }, + + previewTheme: function(aData) { + let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + cancel.data = false; + Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested", + JSON.stringify(aData)); + if (cancel.data) + return; + + if (_previewTimer) + _previewTimer.cancel(); + else + _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + _previewTimer.initWithCallback(_previewTimerCallback, + MAX_PREVIEW_SECONDS * 1000, + _previewTimer.TYPE_ONE_SHOT); + + _notifyWindows(aData); + }, + + resetPreview: function() { + if (_previewTimer) { + _previewTimer.cancel(); + _previewTimer = null; + _notifyWindows(this.currentThemeForDisplay); + } + }, + + parseTheme: function(aString, aBaseURI) { + try { + return _sanitizeTheme(JSON.parse(aString), aBaseURI, false); + } catch (e) { + return null; + } + }, + + updateCurrentTheme: function() { + try { + if (!_prefs.getBoolPref("update.enabled")) + return; + } catch (e) { + return; + } + + var theme = this.currentTheme; + if (!theme || !theme.updateURL) + return; + + var req = new ServiceRequest(); + + req.mozBackgroundRequest = true; + req.overrideMimeType("text/plain"); + req.open("GET", theme.updateURL, true); + // Prevent the request from reading from the cache. + req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; + // Prevent the request from writing to the cache. + req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; + + req.addEventListener("load", () => { + if (req.status != 200) + return; + + let newData = this.parseTheme(req.responseText, theme.updateURL); + if (!newData || + newData.id != theme.id || + _version(newData) == _version(theme)) + return; + + var currentTheme = this.currentTheme; + if (currentTheme && currentTheme.id == theme.id) + this.currentTheme = newData; + }, false); + + req.send(null); + }, + + /** + * Switches to a new lightweight theme. + * + * @param aData + * The lightweight theme to switch to + */ + themeChanged: function(aData) { + if (_previewTimer) { + _previewTimer.cancel(); + _previewTimer = null; + } + + if (aData) { + let usedThemes = _usedThemesExceptId(aData.id); + usedThemes.unshift(aData); + _updateUsedThemes(usedThemes); + if (PERSIST_ENABLED) { + LightweightThemeImageOptimizer.purge(); + _persistImages(aData, function() { + _notifyWindows(this.currentThemeForDisplay); + }.bind(this)); + } + } + + if (aData) + _prefs.setCharPref("selectedThemeID", aData.id); + else + _prefs.setCharPref("selectedThemeID", ""); + + _notifyWindows(aData); + Services.obs.notifyObservers(null, "lightweight-theme-changed", null); + }, + + /** + * Starts the Addons provider and enables the new lightweight theme if + * necessary. + */ + startup: function() { + if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) { + let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); + if (id) + this.themeChanged(this.getUsedTheme(id)); + else + this.themeChanged(null); + Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); + } + + _prefs.addObserver("", _prefObserver, false); + }, + + /** + * Shuts down the provider. + */ + shutdown: function() { + _prefs.removeObserver("", _prefObserver); + }, + + /** + * Called when a new add-on has been enabled when only one add-on of that type + * can be enabled. + * + * @param aId + * The ID of the newly enabled add-on + * @param aType + * The type of the newly enabled add-on + * @param aPendingRestart + * true if the newly enabled add-on will only become enabled after a + * restart + */ + addonChanged: function(aId, aType, aPendingRestart) { + if (aType != ADDON_TYPE) + return; + + let id = _getInternalID(aId); + let current = this.currentTheme; + + try { + let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); + if (id == next && aPendingRestart) + return; + + Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT); + if (next) { + AddonManagerPrivate.callAddonListeners("onOperationCancelled", + new AddonWrapper(this.getUsedTheme(next))); + } + else if (id == current.id) { + AddonManagerPrivate.callAddonListeners("onOperationCancelled", + new AddonWrapper(current)); + return; + } + } + catch (e) { + } + + if (current) { + if (current.id == id) + return; + _themeIDBeingDisabled = current.id; + let wrapper = new AddonWrapper(current); + if (aPendingRestart) { + Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, ""); + AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true); + } + else { + AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false); + this.themeChanged(null); + AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); + } + _themeIDBeingDisabled = null; + } + + if (id) { + let theme = this.getUsedTheme(id); + _themeIDBeingEnabled = id; + let wrapper = new AddonWrapper(theme); + if (aPendingRestart) { + AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true); + Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id); + + // Flush the preferences to disk so they survive any crash + Services.prefs.savePrefFile(null); + } + else { + AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false); + this.themeChanged(theme); + AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); + } + _themeIDBeingEnabled = null; + } + }, + + /** + * Called to get an Addon with a particular ID. + * + * @param aId + * The ID of the add-on to retrieve + * @param aCallback + * A callback to pass the Addon to + */ + getAddonByID: function(aId, aCallback) { + let id = _getInternalID(aId); + if (!id) { + aCallback(null); + return; + } + + let theme = this.getUsedTheme(id); + if (!theme) { + aCallback(null); + return; + } + + aCallback(new AddonWrapper(theme)); + }, + + /** + * Called to get Addons of a particular type. + * + * @param aTypes + * An array of types to fetch. Can be null to get all types. + * @param aCallback + * A callback to pass an array of Addons to + */ + getAddonsByTypes: function(aTypes, aCallback) { + if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) { + aCallback([]); + return; + } + + aCallback(this.usedThemes.map(a => new AddonWrapper(a))); + }, +}; + +const wrapperMap = new WeakMap(); +let themeFor = wrapper => wrapperMap.get(wrapper); + +/** + * The AddonWrapper wraps lightweight theme to provide the data visible to + * consumers of the AddonManager API. + */ +function AddonWrapper(aTheme) { + wrapperMap.set(this, aTheme); +} + +AddonWrapper.prototype = { + get id() { + return themeFor(this).id + ID_SUFFIX; + }, + + get type() { + return ADDON_TYPE; + }, + + get isActive() { + let current = LightweightThemeManager.currentTheme; + if (current) + return themeFor(this).id == current.id; + return false; + }, + + get name() { + return themeFor(this).name; + }, + + get version() { + let theme = themeFor(this); + return "version" in theme ? theme.version : ""; + }, + + get creator() { + let theme = themeFor(this); + return "author" in theme ? new AddonManagerPrivate.AddonAuthor(theme.author) : null; + }, + + get screenshots() { + let url = themeFor(this).previewURL; + return [new AddonManagerPrivate.AddonScreenshot(url)]; + }, + + get pendingOperations() { + let pending = AddonManager.PENDING_NONE; + if (this.isActive == this.userDisabled) + pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE; + return pending; + }, + + get operationsRequiringRestart() { + // If a non-default theme is in use then a restart will be required to + // enable lightweight themes unless dynamic theme switching is enabled + if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) { + try { + if (Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED)) + return AddonManager.OP_NEEDS_RESTART_NONE; + } + catch (e) { + } + return AddonManager.OP_NEEDS_RESTART_ENABLE; + } + + return AddonManager.OP_NEEDS_RESTART_NONE; + }, + + get size() { + // The size changes depending on whether the theme is in use or not, this is + // probably not worth exposing. + return null; + }, + + get permissions() { + let permissions = 0; + + // Do not allow uninstall of builtIn themes. + if (!LightweightThemeManager._builtInThemes.has(themeFor(this).id)) + permissions = AddonManager.PERM_CAN_UNINSTALL; + if (this.userDisabled) + permissions |= AddonManager.PERM_CAN_ENABLE; + else + permissions |= AddonManager.PERM_CAN_DISABLE; + return permissions; + }, + + get userDisabled() { + let id = themeFor(this).id; + if (_themeIDBeingEnabled == id) + return false; + if (_themeIDBeingDisabled == id) + return true; + + try { + let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT); + return id != toSelect; + } + catch (e) { + let current = LightweightThemeManager.currentTheme; + return !current || current.id != id; + } + }, + + set userDisabled(val) { + if (val == this.userDisabled) + return val; + + if (val) + LightweightThemeManager.currentTheme = null; + else + LightweightThemeManager.currentTheme = themeFor(this); + + return val; + }, + + // Lightweight themes are never disabled by the application + get appDisabled() { + return false; + }, + + // Lightweight themes are always compatible + get isCompatible() { + return true; + }, + + get isPlatformCompatible() { + return true; + }, + + get scope() { + return AddonManager.SCOPE_PROFILE; + }, + + get foreignInstall() { + return false; + }, + + uninstall: function() { + LightweightThemeManager.forgetUsedTheme(themeFor(this).id); + }, + + cancelUninstall: function() { + throw new Error("Theme is not marked to be uninstalled"); + }, + + findUpdates: function(listener, reason, appVersion, platformVersion) { + AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion); + }, + + // Lightweight themes are always compatible + isCompatibleWith: function(appVersion, platformVersion) { + return true; + }, + + // Lightweight themes are always securely updated + get providesUpdatesSecurely() { + return true; + }, + + // Lightweight themes are never blocklisted + get blocklistState() { + return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + } +}; + +["description", "homepageURL", "iconURL"].forEach(function(prop) { + Object.defineProperty(AddonWrapper.prototype, prop, { + get: function() { + let theme = themeFor(this); + return prop in theme ? theme[prop] : null; + }, + enumarable: true, + }); +}); + +["installDate", "updateDate"].forEach(function(prop) { + Object.defineProperty(AddonWrapper.prototype, prop, { + get: function() { + let theme = themeFor(this); + return prop in theme ? new Date(theme[prop]) : null; + }, + enumarable: true, + }); +}); + +/** + * Converts the ID used by the public AddonManager API to an lightweight theme + * ID. + * + * @param id + * The ID to be converted + * + * @return the lightweight theme ID or null if the ID was not for a lightweight + * theme. + */ +function _getInternalID(id) { + if (!id) + return null; + let len = id.length - ID_SUFFIX.length; + if (len > 0 && id.substring(len) == ID_SUFFIX) + return id.substring(0, len); + return null; +} + +function _setCurrentTheme(aData, aLocal) { + aData = _sanitizeTheme(aData, null, aLocal); + + let needsRestart = (ADDON_TYPE == "theme") && + Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN); + + let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + cancel.data = false; + Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested", + JSON.stringify(aData)); + + if (aData) { + let theme = LightweightThemeManager.getUsedTheme(aData.id); + let isInstall = !theme || theme.version != aData.version; + if (isInstall) { + aData.updateDate = Date.now(); + if (theme && "installDate" in theme) + aData.installDate = theme.installDate; + else + aData.installDate = aData.updateDate; + + var oldWrapper = theme ? new AddonWrapper(theme) : null; + var wrapper = new AddonWrapper(aData); + AddonManagerPrivate.callInstallListeners("onExternalInstall", null, + wrapper, oldWrapper, false); + AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false); + } + + let current = LightweightThemeManager.currentTheme; + let usedThemes = _usedThemesExceptId(aData.id); + if (current && current.id != aData.id) + usedThemes.splice(1, 0, aData); + else + usedThemes.unshift(aData); + _updateUsedThemes(usedThemes); + + if (isInstall) + AddonManagerPrivate.callAddonListeners("onInstalled", wrapper); + } + + if (cancel.data) + return null; + + AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null, + ADDON_TYPE, needsRestart); + + return LightweightThemeManager.currentTheme; +} + +function _sanitizeTheme(aData, aBaseURI, aLocal) { + if (!aData || typeof aData != "object") + return null; + + var resourceProtocols = ["http", "https", "resource"]; + if (aLocal) + resourceProtocols.push("file"); + var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):"); + + function sanitizeProperty(prop) { + if (!(prop in aData)) + return null; + if (typeof aData[prop] != "string") + return null; + let val = aData[prop].trim(); + if (!val) + return null; + + if (!/URL$/.test(prop)) + return val; + + try { + val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec; + if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val)) + return val; + return null; + } + catch (e) { + return null; + } + } + + let result = {}; + for (let mandatoryProperty of MANDATORY) { + let val = sanitizeProperty(mandatoryProperty); + if (!val) + throw Components.results.NS_ERROR_INVALID_ARG; + result[mandatoryProperty] = val; + } + + for (let optionalProperty of OPTIONAL) { + let val = sanitizeProperty(optionalProperty); + if (!val) + continue; + result[optionalProperty] = val; + } + + return result; +} + +function _usedThemesExceptId(aId) { + return LightweightThemeManager.usedThemes.filter(function(t) { + return "id" in t && t.id != aId; + }); +} + +function _version(aThemeData) { + return aThemeData.version || ""; +} + +function _makeURI(aURL, aBaseURI) { + return Services.io.newURI(aURL, null, aBaseURI); +} + +function _updateUsedThemes(aList) { + // Remove app-specific themes before saving them to the usedThemes pref. + aList = aList.filter(theme => !LightweightThemeManager._builtInThemes.has(theme.id)); + + // Send uninstall events for all themes that need to be removed. + while (aList.length > _maxUsedThemes) { + let wrapper = new AddonWrapper(aList[aList.length - 1]); + AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); + aList.pop(); + AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); + } + + var str = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + str.data = JSON.stringify(aList); + _prefs.setComplexValue("usedThemes", Ci.nsISupportsString, str); + + Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null); +} + +function _notifyWindows(aThemeData) { + Services.obs.notifyObservers(null, "lightweight-theme-styling-update", + JSON.stringify(aThemeData)); +} + +var _previewTimer; +var _previewTimerCallback = { + notify: function() { + LightweightThemeManager.resetPreview(); + } +}; + +/** + * Called when any of the lightweightThemes preferences are changed. + */ +function _prefObserver(aSubject, aTopic, aData) { + switch (aData) { + case "maxUsedThemes": + try { + _maxUsedThemes = _prefs.getIntPref(aData); + } + catch (e) { + _maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT; + } + // Update the theme list to remove any themes over the number we keep + _updateUsedThemes(LightweightThemeManager.usedThemes); + break; + } +} + +function _persistImages(aData, aCallback) { + function onSuccess(key) { + return function () { + let current = LightweightThemeManager.currentTheme; + if (current && current.id == aData.id) { + _prefs.setBoolPref("persisted." + key, true); + } + if (--numFilesToPersist == 0 && aCallback) { + aCallback(); + } + }; + } + + let numFilesToPersist = 0; + for (let key in PERSIST_FILES) { + _prefs.setBoolPref("persisted." + key, false); + if (aData[key]) { + numFilesToPersist++; + _persistImage(aData[key], PERSIST_FILES[key], onSuccess(key)); + } + } +} + +function _getLocalImageURI(localFileName) { + var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + localFile.append(localFileName); + return Services.io.newFileURI(localFile); +} + +function _persistImage(sourceURL, localFileName, successCallback) { + if (/^(file|resource):/.test(sourceURL)) + return; + + var targetURI = _getLocalImageURI(localFileName); + var sourceURI = _makeURI(sourceURL); + + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + + persist.persistFlags = + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION | + (PERSIST_BYPASS_CACHE ? + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE : + Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE); + + persist.progressListener = new _persistProgressListener(successCallback); + + persist.saveURI(sourceURI, null, + null, Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE, + null, null, targetURI, null); +} + +function _persistProgressListener(successCallback) { + this.onLocationChange = function() {}; + this.onProgressChange = function() {}; + this.onStatusChange = function() {}; + this.onSecurityChange = function() {}; + this.onStateChange = function(aWebProgress, aRequest, aStateFlags, aStatus) { + if (aRequest && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { + try { + if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) { + // success + successCallback(); + return; + } + } catch (e) { } + // failure + } + }; +} + +AddonManagerPrivate.registerProvider(LightweightThemeManager, [ + new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS, + STRING_TYPE_NAME, + AddonManager.VIEW_TYPE_LIST, 5000) +]); diff --git a/toolkit/mozapps/webextensions/addonManager.js b/toolkit/mozapps/webextensions/addonManager.js new file mode 100644 index 000000000..d34cbaf62 --- /dev/null +++ b/toolkit/mozapps/webextensions/addonManager.js @@ -0,0 +1,296 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 component serves as integration between the platform and AddonManager. + * It is responsible for initializing and shutting down the AddonManager as well + * as passing new installs from webpages to the AddonManager. + */ + +"use strict"; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +// The old XPInstall error codes +const EXECUTION_ERROR = -203; +const CANT_READ_ARCHIVE = -207; +const USER_CANCELLED = -210; +const DOWNLOAD_ERROR = -228; +const UNSUPPORTED_TYPE = -244; +const SUCCESS = 0; + +const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; +const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; +const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; + +const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; +const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; +const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; +const MSG_INSTALL_CLEANUP = "WebAPICleanup"; +const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; +const MSG_ADDON_EVENT = "WebAPIAddonEvent"; + +const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js"; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +var gSingleton = null; + +function amManager() { + Cu.import("resource://gre/modules/AddonManager.jsm"); + /* globals AddonManagerPrivate*/ + + Services.mm.loadFrameScript(CHILD_SCRIPT, true); + Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this); + Services.mm.addMessageListener(MSG_INSTALL_ADDONS, this); + Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this); + Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this); + Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this); + + Services.obs.addObserver(this, "message-manager-close", false); + Services.obs.addObserver(this, "message-manager-disconnect", false); + + AddonManager.webAPI.setEventHandler(this.sendEvent); + + // Needed so receiveMessage can be called directly by JS callers + this.wrappedJSObject = this; +} + +amManager.prototype = { + observe: function(aSubject, aTopic, aData) { + switch (aTopic) { + case "addons-startup": + AddonManagerPrivate.startup(); + break; + + case "message-manager-close": + case "message-manager-disconnect": + this.childClosed(aSubject); + break; + } + }, + + /** + * @see amIAddonManager.idl + */ + mapURIToAddonID: function(uri, id) { + id.value = AddonManager.mapURIToAddonID(uri); + return !!id.value; + }, + + /** + * @see amIWebInstaller.idl + */ + isInstallEnabled: function(aMimetype, aReferer) { + return AddonManager.isInstallEnabled(aMimetype); + }, + + /** + * @see amIWebInstaller.idl + */ + installAddonsFromWebpage: function(aMimetype, aBrowser, aInstallingPrincipal, + aUris, aHashes, aNames, aIcons, aCallback) { + if (aUris.length == 0) + return false; + + let retval = true; + if (!AddonManager.isInstallAllowed(aMimetype, aInstallingPrincipal)) { + aCallback = null; + retval = false; + } + + let installs = []; + function buildNextInstall() { + if (aUris.length == 0) { + AddonManager.installAddonsFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, installs); + return; + } + let uri = aUris.shift(); + AddonManager.getInstallForURL(uri, function(aInstall) { + function callCallback(aUri, aStatus) { + try { + aCallback.onInstallEnded(aUri, aStatus); + } + catch (e) { + Components.utils.reportError(e); + } + } + + if (aInstall) { + installs.push(aInstall); + if (aCallback) { + aInstall.addListener({ + onDownloadCancelled: function(aInstall) { + callCallback(uri, USER_CANCELLED); + }, + + onDownloadFailed: function(aInstall) { + if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE) + callCallback(uri, CANT_READ_ARCHIVE); + else + callCallback(uri, DOWNLOAD_ERROR); + }, + + onInstallFailed: function(aInstall) { + callCallback(uri, EXECUTION_ERROR); + }, + + onInstallEnded: function(aInstall, aStatus) { + callCallback(uri, SUCCESS); + } + }); + } + } + else if (aCallback) { + aCallback.onInstallEnded(uri, UNSUPPORTED_TYPE); + } + buildNextInstall(); + }, aMimetype, aHashes.shift(), aNames.shift(), aIcons.shift(), null, aBrowser); + } + buildNextInstall(); + + return retval; + }, + + notify: function(aTimer) { + AddonManagerPrivate.backgroundUpdateTimerHandler(); + }, + + // Maps message manager instances for content processes to the associated + // AddonListener instances. + addonListeners: new Map(), + + _addAddonListener(target) { + if (!this.addonListeners.has(target)) { + let handler = (event, id, needsRestart) => { + target.sendAsyncMessage(MSG_ADDON_EVENT, {event, id, needsRestart}); + }; + let listener = { + onEnabling: (addon, needsRestart) => handler("onEnabling", addon.id, needsRestart), + onEnabled: (addon) => handler("onEnabled", addon.id, false), + onDisabling: (addon, needsRestart) => handler("onDisabling", addon.id, needsRestart), + onDisabled: (addon) => handler("onDisabled", addon.id, false), + onInstalling: (addon, needsRestart) => handler("onInstalling", addon.id, needsRestart), + onInstalled: (addon) => handler("onInstalled", addon.id, false), + onUninstalling: (addon, needsRestart) => handler("onUninstalling", addon.id, needsRestart), + onUninstalled: (addon) => handler("onUninstalled", addon.id, false), + onOperationCancelled: (addon) => handler("onOperationCancelled", addon.id, false), + }; + this.addonListeners.set(target, listener); + AddonManager.addAddonListener(listener); + } + }, + + _removeAddonListener(target) { + if (this.addonListeners.has(target)) { + AddonManager.removeAddonListener(this.addonListeners.get(target)); + this.addonListeners.delete(target); + } + }, + + /** + * messageManager callback function. + * + * Listens to requests from child processes for InstallTrigger + * activity, and sends back callbacks. + */ + receiveMessage: function(aMessage) { + let payload = aMessage.data; + + switch (aMessage.name) { + case MSG_INSTALL_ENABLED: + return AddonManager.isInstallEnabled(payload.mimetype); + + case MSG_INSTALL_ADDONS: { + let callback = null; + if (payload.callbackID != -1) { + let mm = aMessage.target.messageManager; + callback = { + onInstallEnded: function(url, status) { + mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, { + callbackID: payload.callbackID, + url: url, + status: status + }); + }, + }; + } + + return this.installAddonsFromWebpage(payload.mimetype, + aMessage.target, payload.triggeringPrincipal, payload.uris, + payload.hashes, payload.names, payload.icons, callback); + } + + case MSG_PROMISE_REQUEST: { + let mm = aMessage.target.messageManager; + let resolve = (value) => { + mm.sendAsyncMessage(MSG_PROMISE_RESULT, { + callbackID: payload.callbackID, + resolve: value + }); + } + let reject = (value) => { + mm.sendAsyncMessage(MSG_PROMISE_RESULT, { + callbackID: payload.callbackID, + reject: value + }); + } + + let API = AddonManager.webAPI; + if (payload.type in API) { + API[payload.type](aMessage.target, ...payload.args).then(resolve, reject); + } + else { + reject("Unknown Add-on API request."); + } + break; + } + + case MSG_INSTALL_CLEANUP: { + AddonManager.webAPI.clearInstalls(payload.ids); + break; + } + + case MSG_ADDON_EVENT_REQ: { + let target = aMessage.target.messageManager; + if (payload.enabled) { + this._addAddonListener(target); + } else { + this._removeAddonListener(target); + } + } + } + return undefined; + }, + + childClosed(target) { + AddonManager.webAPI.clearInstallsFrom(target); + this._removeAddonListener(target); + }, + + sendEvent(mm, data) { + mm.sendAsyncMessage(MSG_INSTALL_EVENT, data); + }, + + classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"), + _xpcom_factory: { + createInstance: function(aOuter, aIid) { + if (aOuter != null) + throw Components.Exception("Component does not support aggregation", + Cr.NS_ERROR_NO_AGGREGATION); + + if (!gSingleton) + gSingleton = new amManager(); + return gSingleton.QueryInterface(aIid); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager, + Ci.amIWebInstaller, + Ci.nsITimerCallback, + Ci.nsIObserver, + Ci.nsIMessageListener]) +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]); diff --git a/toolkit/mozapps/webextensions/amContentHandler.js b/toolkit/mozapps/webextensions/amContentHandler.js new file mode 100644 index 000000000..8dc4dfecd --- /dev/null +++ b/toolkit/mozapps/webextensions/amContentHandler.js @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; + +const XPI_CONTENT_TYPE = "application/x-xpinstall"; +const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +function amContentHandler() { +} + +amContentHandler.prototype = { + /** + * Handles a new request for an application/x-xpinstall file. + * + * @param aMimetype + * The mimetype of the file + * @param aContext + * The context passed to nsIChannel.asyncOpen + * @param aRequest + * The nsIRequest dealing with the content + */ + handleContent: function(aMimetype, aContext, aRequest) { + if (aMimetype != XPI_CONTENT_TYPE) + throw Cr.NS_ERROR_WONT_HANDLE_CONTENT; + + if (!(aRequest instanceof Ci.nsIChannel)) + throw Cr.NS_ERROR_WONT_HANDLE_CONTENT; + + let uri = aRequest.URI; + + let window = null; + let callbacks = aRequest.notificationCallbacks ? + aRequest.notificationCallbacks : + aRequest.loadGroup.notificationCallbacks; + if (callbacks) + window = callbacks.getInterface(Ci.nsIDOMWindow); + + aRequest.cancel(Cr.NS_BINDING_ABORTED); + + let installs = { + uris: [uri.spec], + hashes: [null], + names: [null], + icons: [null], + mimetype: XPI_CONTENT_TYPE, + triggeringPrincipal: aRequest.loadInfo.triggeringPrincipal, + callbackID: -1 + }; + + if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { + // When running in the main process this might be a frame inside an + // in-content UI page, walk up to find the first frame element in a chrome + // privileged document + let element = window.frameElement; + let ssm = Services.scriptSecurityManager; + while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal)) + element = element.ownerDocument.defaultView.frameElement; + + if (element) { + let listener = Cc["@mozilla.org/addons/integration;1"]. + getService(Ci.nsIMessageListener); + listener.wrappedJSObject.receiveMessage({ + name: MSG_INSTALL_ADDONS, + target: element, + data: installs, + }); + return; + } + } + + // Fall back to sending through the message manager + let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + + messageManager.sendAsyncMessage(MSG_INSTALL_ADDONS, installs); + }, + + classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]), + + log : function(aMsg) { + let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg); + Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService). + logStringMessage(msg); + dump(msg + "\n"); + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amContentHandler]); diff --git a/toolkit/mozapps/webextensions/amIAddonManager.idl b/toolkit/mozapps/webextensions/amIAddonManager.idl new file mode 100644 index 000000000..58a58b62d --- /dev/null +++ b/toolkit/mozapps/webextensions/amIAddonManager.idl @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * A service to make some AddonManager functionality available to C++ callers. + * Javascript callers should still use AddonManager.jsm directly. + */ +[scriptable, function, uuid(7b45d82d-7ad5-48d7-9b05-f32eb9818cd4)] +interface amIAddonManager : nsISupports +{ + /** + * Synchronously map a URI to the corresponding Addon ID. + * + * Mappable URIs are limited to in-application resources belonging to the + * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. + * but do not include URIs from meta data, such as the add-on homepage. + * + * @param aURI + * The nsIURI to map + * @return + * true if the URI has been mapped successfully to an Addon ID + */ + boolean mapURIToAddonID(in nsIURI aURI, out AUTF8String aID); +}; diff --git a/toolkit/mozapps/webextensions/amIAddonPathService.idl b/toolkit/mozapps/webextensions/amIAddonPathService.idl new file mode 100644 index 000000000..9c9197a61 --- /dev/null +++ b/toolkit/mozapps/webextensions/amIAddonPathService.idl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * This service maps file system paths where add-ons reside to the ID + * of the add-on. Paths are added by the add-on manager. They can + * looked up by anyone. + */ +[scriptable, uuid(fcd9e270-dfb1-11e3-8b68-0800200c9a66)] +interface amIAddonPathService : nsISupports +{ + /** + * Given a path to a file, return the ID of the add-on that the file belongs + * to. Returns an empty string if there is no add-on there. Note that if an + * add-on is located at /a/b/c, then looking up the path /a/b/c/d will return + * that add-on. + */ + AString findAddonId(in AString path); + + /** + * Call this function to inform the service that the given file system path is + * associated with the given add-on ID. + */ + void insertPath(in AString path, in AString addonId); + + /** + * Given a URI to a file, return the ID of the add-on that the file belongs + * to. Returns an empty string if there is no add-on there. + */ + AString mapURIToAddonId(in nsIURI aURI); +}; diff --git a/toolkit/mozapps/webextensions/amIWebInstallListener.idl b/toolkit/mozapps/webextensions/amIWebInstallListener.idl new file mode 100644 index 000000000..eed108097 --- /dev/null +++ b/toolkit/mozapps/webextensions/amIWebInstallListener.idl @@ -0,0 +1,134 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMElement; +interface nsIURI; +interface nsIVariant; + +/** + * amIWebInstallInfo is used by the default implementation of + * amIWebInstallListener to communicate with the running application and allow + * it to warn the user about blocked installs and start the installs running. + */ +[scriptable, uuid(fa0b47a3-f819-47ac-bc66-4bd1d7f67b1d)] +interface amIWebInstallInfo : nsISupports +{ + readonly attribute nsIDOMElement browser; + readonly attribute nsIURI originatingURI; + readonly attribute nsIVariant installs; + + /** + * Starts all installs. + */ + void install(); +}; + +/** + * The registered amIWebInstallListener is used to notify about new installs + * triggered by websites. The default implementation displays a confirmation + * dialog when add-ons are ready to install and uses the observer service to + * notify when installations are blocked. + */ +[scriptable, uuid(d9240d4b-6b3a-4cad-b402-de6c93337e0c)] +interface amIWebInstallListener : nsISupports +{ + /** + * Called when installation by websites is currently disabled. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were blocked + * @param aCount + * The number of AddonInstalls + */ + void onWebInstallDisabled(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); + + /** + * Called when the website is not allowed to directly prompt the user to + * install add-ons. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were blocked + * @param aCount + * The number of AddonInstalls + * @return true if the caller should start the installs + */ + boolean onWebInstallBlocked(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); + + /** + * Called when a website wants to ask the user to install add-ons. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were requested + * @param aCount + * The number of AddonInstalls + * @return true if the caller should start the installs + */ + boolean onWebInstallRequested(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); +}; + +[scriptable, uuid(a80b89ad-bb1a-4c43-9cb7-3ae656556f78)] +interface amIWebInstallListener2 : nsISupports +{ + /** + * Called when a non-same-origin resource attempted to initiate an install. + * Installs will have already been cancelled and cannot be restarted. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were blocked + * @param aCount + * The number of AddonInstalls + */ + boolean onWebInstallOriginBlocked(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); +}; + +/** + * amIWebInstallPrompt is used, if available, by the default implementation of + * amIWebInstallInfo to display a confirmation UI to the user before running + * installs. + */ +[scriptable, uuid(386906f1-4d18-45bf-bc81-5dcd68e42c3b)] +interface amIWebInstallPrompt : nsISupports +{ + /** + * Get a confirmation that the user wants to start the installs. + * + * @param aBrowser + * The browser that triggered the installs + * @param aUri + * The URI of the site that triggered the installs + * @param aInstalls + * The AddonInstalls that were requested + * @param aCount + * The number of AddonInstalls + */ + void confirm(in nsIDOMElement aBrowser, in nsIURI aUri, + [array, size_is(aCount)] in nsIVariant aInstalls, + [optional] in uint32_t aCount); +}; diff --git a/toolkit/mozapps/webextensions/amIWebInstaller.idl b/toolkit/mozapps/webextensions/amIWebInstaller.idl new file mode 100644 index 000000000..6c5ebca67 --- /dev/null +++ b/toolkit/mozapps/webextensions/amIWebInstaller.idl @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMElement; +interface nsIVariant; +interface nsIURI; + +/** + * A callback function used to notify webpages when a requested install has + * ended. + * + * NOTE: This is *not* the same as InstallListener. + */ +[scriptable, function, uuid(bb22f5c0-3ca1-48f6-873c-54e87987700f)] +interface amIInstallCallback : nsISupports +{ + /** + * Called when an install completes or fails. + * + * @param aUrl + * The url of the add-on being installed + * @param aStatus + * 0 if the install was successful or negative if not + */ + void onInstallEnded(in AString aUrl, in int32_t aStatus); +}; + + +/** + * This interface is used to allow webpages to start installing add-ons. + */ +[scriptable, uuid(658d6c09-15e0-4688-bee8-8551030472a9)] +interface amIWebInstaller : nsISupports +{ + /** + * Checks if installation is enabled for a webpage. + * + * @param aMimetype + * The mimetype for the add-on to be installed + * @param referer + * The URL of the webpage trying to install an add-on + * @return true if installation is enabled + */ + boolean isInstallEnabled(in AString aMimetype, in nsIURI aReferer); + + /** + * Installs an array of add-ons at the request of a webpage + * + * @param aMimetype + * The mimetype for the add-ons + * @param aBrowser + * The browser installing the add-ons. + * @param aReferer + * The URI for the webpage installing the add-ons + * @param aUris + * The URIs of add-ons to be installed + * @param aHashes + * The hashes for the add-ons to be installed + * @param aNames + * The names for the add-ons to be installed + * @param aIcons + * The icons for the add-ons to be installed + * @param aCallback + * An optional callback to notify about installation success and + * failure + * @param aInstallCount + * An optional argument including the number of add-ons to install + * @return true if the installation was successfully started + */ + boolean installAddonsFromWebpage(in AString aMimetype, + in nsIDOMElement aBrowser, + in nsIURI aReferer, + [array, size_is(aInstallCount)] in wstring aUris, + [array, size_is(aInstallCount)] in wstring aHashes, + [array, size_is(aInstallCount)] in wstring aNames, + [array, size_is(aInstallCount)] in wstring aIcons, + [optional] in amIInstallCallback aCallback, + [optional] in uint32_t aInstallCount); +}; diff --git a/toolkit/mozapps/webextensions/amInstallTrigger.js b/toolkit/mozapps/webextensions/amInstallTrigger.js new file mode 100644 index 000000000..382791d32 --- /dev/null +++ b/toolkit/mozapps/webextensions/amInstallTrigger.js @@ -0,0 +1,240 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/Log.jsm"); + +const XPINSTALL_MIMETYPE = "application/x-xpinstall"; + +const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; +const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; +const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; + + +var log = Log.repository.getLogger("AddonManager.InstallTrigger"); +log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"]; + +function CallbackObject(id, callback, urls, mediator) { + this.id = id; + this.callback = callback; + this.urls = new Set(urls); + this.callCallback = function(url, status) { + try { + this.callback(url, status); + } + catch (e) { + log.warn("InstallTrigger callback threw an exception: " + e); + } + + this.urls.delete(url); + if (this.urls.size == 0) + mediator._callbacks.delete(id); + }; +} + +function RemoteMediator(window) { + window.QueryInterface(Ci.nsIInterfaceRequestor); + let utils = window.getInterface(Ci.nsIDOMWindowUtils); + this._windowID = utils.currentInnerWindowID; + + this.mm = window + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this); + + this._lastCallbackID = 0; + this._callbacks = new Map(); +} + +RemoteMediator.prototype = { + receiveMessage: function(message) { + if (message.name == MSG_INSTALL_CALLBACK) { + let payload = message.data; + let callbackHandler = this._callbacks.get(payload.callbackID); + if (callbackHandler) { + callbackHandler.callCallback(payload.url, payload.status); + } + } + }, + + enabled: function(url) { + let params = { + mimetype: XPINSTALL_MIMETYPE + }; + return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0]; + }, + + install: function(installs, principal, callback, window) { + let callbackID = this._addCallback(callback, installs.uris); + + installs.mimetype = XPINSTALL_MIMETYPE; + installs.triggeringPrincipal = principal; + installs.callbackID = callbackID; + + if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { + // When running in the main process this might be a frame inside an + // in-content UI page, walk up to find the first frame element in a chrome + // privileged document + let element = window.frameElement; + let ssm = Services.scriptSecurityManager; + while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal)) + element = element.ownerDocument.defaultView.frameElement; + + if (element) { + let listener = Cc["@mozilla.org/addons/integration;1"]. + getService(Ci.nsIMessageListener); + return listener.wrappedJSObject.receiveMessage({ + name: MSG_INSTALL_ADDONS, + target: element, + data: installs, + }); + } + } + + // Fall back to sending through the message manager + let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + + return messageManager.sendSyncMessage(MSG_INSTALL_ADDONS, installs)[0]; + }, + + _addCallback: function(callback, urls) { + if (!callback || typeof callback != "function") + return -1; + + let callbackID = this._windowID + "-" + ++this._lastCallbackID; + let callbackObject = new CallbackObject(callbackID, callback, urls, this); + this._callbacks.set(callbackID, callbackObject); + return callbackID; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]) +}; + + +function InstallTrigger() { +} + +InstallTrigger.prototype = { + // Here be magic. We've declared ourselves as providing the + // nsIDOMGlobalPropertyInitializer interface, and are registered in the + // "JavaScript-global-property" category in the XPCOM category manager. This + // means that for newly created windows, XPCOM will createinstance this + // object, and then call init, passing in the window for which we need to + // provide an instance. We then initialize ourselves and return the webidl + // version of this object using the webidl-provided _create method, which + // XPCOM will then duly expose as a property value on the window. All this + // indirection is necessary because webidl does not (yet) support statics + // (bug 863952). See bug 926712 for more details about this implementation. + init: function(window) { + this._window = window; + this._principal = window.document.nodePrincipal; + this._url = window.document.documentURIObject; + + try { + this._mediator = new RemoteMediator(window); + } catch (ex) { + // If we can't set up IPC (e.g., because this is a top-level window + // or something), then don't expose InstallTrigger. + return null; + } + + return window.InstallTriggerImpl._create(window, this); + }, + + enabled: function() { + return this._mediator.enabled(this._url.spec); + }, + + updateEnabled: function() { + return this.enabled(); + }, + + install: function(installs, callback) { + let installData = { + uris: [], + hashes: [], + names: [], + icons: [], + }; + + for (let name of Object.keys(installs)) { + let item = installs[name]; + if (typeof item === "string") { + item = { URL: item }; + } + if (!item.URL) { + throw new this._window.Error("Missing URL property for '" + name + "'"); + } + + let url = this._resolveURL(item.URL); + if (!this._checkLoadURIFromScript(url)) { + throw new this._window.Error("Insufficient permissions to install: " + url.spec); + } + + let iconUrl = null; + if (item.IconURL) { + iconUrl = this._resolveURL(item.IconURL); + if (!this._checkLoadURIFromScript(iconUrl)) { + iconUrl = null; // If page can't load the icon, just ignore it + } + } + + installData.uris.push(url.spec); + installData.hashes.push(item.Hash || null); + installData.names.push(name); + installData.icons.push(iconUrl ? iconUrl.spec : null); + } + + return this._mediator.install(installData, this._principal, callback, this._window); + }, + + startSoftwareUpdate: function(url, flags) { + let filename = Services.io.newURI(url, null, null) + .QueryInterface(Ci.nsIURL) + .filename; + let args = {}; + args[filename] = { "URL": url }; + return this.install(args); + }, + + installChrome: function(type, url, skin) { + return this.startSoftwareUpdate(url); + }, + + _resolveURL: function(url) { + return Services.io.newURI(url, null, this._url); + }, + + _checkLoadURIFromScript: function(uri) { + let secman = Services.scriptSecurityManager; + try { + secman.checkLoadURIWithPrincipal(this._principal, + uri, + secman.DISALLOW_INHERIT_PRINCIPAL); + return true; + } + catch (e) { + return false; + } + }, + + classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"), + contractID: "@mozilla.org/addons/installtrigger;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer]) +}; + + + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]); diff --git a/toolkit/mozapps/webextensions/amWebAPI.js b/toolkit/mozapps/webextensions/amWebAPI.js new file mode 100644 index 000000000..5ad0d23f1 --- /dev/null +++ b/toolkit/mozapps/webextensions/amWebAPI.js @@ -0,0 +1,269 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; +const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; +const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; +const MSG_INSTALL_CLEANUP = "WebAPICleanup"; +const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; +const MSG_ADDON_EVENT = "WebAPIAddonEvent"; + +class APIBroker { + constructor(mm) { + this.mm = mm; + + this._promises = new Map(); + + // _installMap maps integer ids to DOM AddonInstall instances + this._installMap = new Map(); + + this.mm.addMessageListener(MSG_PROMISE_RESULT, this); + this.mm.addMessageListener(MSG_INSTALL_EVENT, this); + + this._eventListener = null; + } + + receiveMessage(message) { + let payload = message.data; + + switch (message.name) { + case MSG_PROMISE_RESULT: { + if (!this._promises.has(payload.callbackID)) { + return; + } + + let resolve = this._promises.get(payload.callbackID); + this._promises.delete(payload.callbackID); + resolve(payload); + break; + } + + case MSG_INSTALL_EVENT: { + let install = this._installMap.get(payload.id); + if (!install) { + let err = new Error(`Got install event for unknown install ${payload.id}`); + Cu.reportError(err); + return; + } + install._dispatch(payload); + break; + } + + case MSG_ADDON_EVENT: { + if (this._eventListener) { + this._eventListener(payload); + } + } + } + } + + sendRequest(type, ...args) { + return new Promise(resolve => { + let callbackID = APIBroker._nextID++; + + this._promises.set(callbackID, resolve); + this.mm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args }); + }); + } + + setAddonListener(callback) { + this._eventListener = callback; + if (callback) { + this.mm.addMessageListener(MSG_ADDON_EVENT, this); + this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: true}); + } else { + this.mm.removeMessageListener(MSG_ADDON_EVENT, this); + this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: false}); + } + } + + sendCleanup(ids) { + this.setAddonListener(null); + this.mm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids }); + } +} + +APIBroker._nextID = 0; + +// Base class for building classes to back content-exposed interfaces. +class APIObject { + init(window, broker, properties) { + this.window = window; + this.broker = broker; + + // Copy any provided properties onto this object, webidl bindings + // will only expose to content what should be exposed. + for (let key of Object.keys(properties)) { + this[key] = properties[key]; + } + } + + /** + * Helper to implement an asychronous method visible to content, where + * the method is implemented by sending a message to the parent process + * and then wrapping the returned object or error in an appropriate object. + * This helper method ensures that: + * - Returned Promise objects are from the content window + * - Rejected Promises have Error objects from the content window + * - Only non-internal errors are exposed to the caller + * + * @param {string} apiRequest The command to invoke in the parent process. + * @param {array} apiArgs The arguments to include with the + * request to the parent process. + * @param {function} resultConvert If provided, a function called with the + * result from the parent process as an + * argument. Used to convert the result + * into something appropriate for content. + * @returns {Promise} A Promise suitable for passing directly to content. + */ + _apiTask(apiRequest, apiArgs, resultConverter) { + let win = this.window; + let broker = this.broker; + return new win.Promise((resolve, reject) => { + Task.spawn(function*() { + let result = yield broker.sendRequest(apiRequest, ...apiArgs); + if ("reject" in result) { + let err = new win.Error(result.reject.message); + // We don't currently put any other properties onto Errors + // generated by mozAddonManager. If/when we do, they will + // need to get copied here. + reject(err); + return; + } + + let obj = result.resolve; + if (resultConverter) { + obj = resultConverter(obj); + } + resolve(obj); + }).catch(err => { + Cu.reportError(err); + reject(new win.Error("Unexpected internal error")); + }); + }); + } +} + +class Addon extends APIObject { + constructor(...args) { + super(); + this.init(...args); + } + + uninstall() { + return this._apiTask("addonUninstall", [this.id]); + } + + setEnabled(value) { + return this._apiTask("addonSetEnabled", [this.id, value]); + } +} + +class AddonInstall extends APIObject { + constructor(window, broker, properties) { + super(); + this.init(window, broker, properties); + + broker._installMap.set(properties.id, this); + } + + _dispatch(data) { + // The message for the event includes updated copies of all install + // properties. Use the usual "let webidl filter visible properties" trick. + for (let key of Object.keys(data)) { + this[key] = data[key]; + } + + let event = new this.window.Event(data.event); + this.__DOM_IMPL__.dispatchEvent(event); + } + + install() { + return this._apiTask("addonInstallDoInstall", [this.id]); + } + + cancel() { + return this._apiTask("addonInstallCancel", [this.id]); + } +} + +class WebAPI extends APIObject { + constructor() { + super(); + this.allInstalls = []; + this.listenerCount = 0; + } + + init(window) { + let mm = window + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + let broker = new APIBroker(mm); + + super.init(window, broker, {}); + + window.addEventListener("unload", event => { + this.broker.sendCleanup(this.allInstalls); + }); + } + + getAddonByID(id) { + return this._apiTask("getAddonByID", [id], addonInfo => { + if (!addonInfo) { + return null; + } + let addon = new Addon(this.window, this.broker, addonInfo); + return this.window.Addon._create(this.window, addon); + }); + } + + createInstall(options) { + return this._apiTask("createInstall", [options], installInfo => { + if (!installInfo) { + return null; + } + let install = new AddonInstall(this.window, this.broker, installInfo); + this.allInstalls.push(installInfo.id); + return this.window.AddonInstall._create(this.window, install); + }); + } + + eventListenerWasAdded(type) { + if (this.listenerCount == 0) { + this.broker.setAddonListener(data => { + let event = new this.window.AddonEvent(data.event, data); + this.__DOM_IMPL__.dispatchEvent(event); + }); + } + this.listenerCount++; + } + + eventListenerWasRemoved(type) { + this.listenerCount--; + if (this.listenerCount == 0) { + this.broker.setAddonListener(null); + } + } + + QueryInterface(iid) { + if (iid.equals(WebAPI.classID) || iid.equals(Ci.nsISupports) + || iid.equals(Ci.nsIDOMGlobalPropertyInitializer)) { + return this; + } + return Cr.NS_ERROR_NO_INTERFACE; + } +} + +WebAPI.prototype.classID = Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"); +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebAPI]); diff --git a/toolkit/mozapps/webextensions/amWebInstallListener.js b/toolkit/mozapps/webextensions/amWebInstallListener.js new file mode 100644 index 000000000..0bcc345e8 --- /dev/null +++ b/toolkit/mozapps/webextensions/amWebInstallListener.js @@ -0,0 +1,348 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This is a default implementation of amIWebInstallListener that should work + * for most applications but can be overriden. It notifies the observer service + * about blocked installs. For normal installs it pops up an install + * confirmation when all the add-ons have been downloaded. + */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/AddonManager.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Preferences.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils", "resource://gre/modules/SharedPromptUtils.jsm"); + +const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; + +// Installation can begin from any of these states +const READY_STATES = [ + AddonManager.STATE_AVAILABLE, + AddonManager.STATE_DOWNLOAD_FAILED, + AddonManager.STATE_INSTALL_FAILED, + AddonManager.STATE_CANCELLED +]; + +Cu.import("resource://gre/modules/Log.jsm"); +const LOGGER_ID = "addons.weblistener"; + +// Create a new logger for use by the Addons Web Listener +// (Requires AddonManager.jsm) +var logger = Log.repository.getLogger(LOGGER_ID); + +function notifyObservers(aTopic, aBrowser, aUri, aInstalls) { + let info = { + browser: aBrowser, + originatingURI: aUri, + installs: aInstalls, + + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) + }; + Services.obs.notifyObservers(info, aTopic, null); +} + +/** + * Creates a new installer to monitor downloads and prompt to install when + * ready + * + * @param aBrowser + * The browser that started the installations + * @param aUrl + * The URL that started the installations + * @param aInstalls + * An array of AddonInstalls + */ +function Installer(aBrowser, aUrl, aInstalls) { + this.browser = aBrowser; + this.url = aUrl; + this.downloads = aInstalls; + this.installed = []; + + notifyObservers("addon-install-started", aBrowser, aUrl, aInstalls); + + for (let install of aInstalls) { + install.addListener(this); + + // Start downloading if it hasn't already begun + if (READY_STATES.indexOf(install.state) != -1) + install.install(); + } + + this.checkAllDownloaded(); +} + +Installer.prototype = { + browser: null, + downloads: null, + installed: null, + isDownloading: true, + + /** + * Checks if all downloads are now complete and if so prompts to install. + */ + checkAllDownloaded: function() { + // Prevent re-entrancy caused by the confirmation dialog cancelling unwanted + // installs. + if (!this.isDownloading) + return; + + var failed = []; + var installs = []; + + for (let install of this.downloads) { + switch (install.state) { + case AddonManager.STATE_AVAILABLE: + case AddonManager.STATE_DOWNLOADING: + // Exit early if any add-ons haven't started downloading yet or are + // still downloading + return; + case AddonManager.STATE_DOWNLOAD_FAILED: + failed.push(install); + break; + case AddonManager.STATE_DOWNLOADED: + // App disabled items are not compatible and so fail to install + if (install.addon.appDisabled) + failed.push(install); + else + installs.push(install); + + if (install.linkedInstalls) { + for (let linkedInstall of install.linkedInstalls) { + linkedInstall.addListener(this); + // Corrupt or incompatible items fail to install + if (linkedInstall.state == AddonManager.STATE_DOWNLOAD_FAILED || linkedInstall.addon.appDisabled) + failed.push(linkedInstall); + else + installs.push(linkedInstall); + } + } + break; + case AddonManager.STATE_CANCELLED: + // Just ignore cancelled downloads + break; + default: + logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " + + install.state); + } + } + + this.isDownloading = false; + this.downloads = installs; + + if (failed.length > 0) { + // Stop listening and cancel any installs that are failed because of + // compatibility reasons. + for (let install of failed) { + if (install.state == AddonManager.STATE_DOWNLOADED) { + install.removeListener(this); + install.cancel(); + } + } + notifyObservers("addon-install-failed", this.browser, this.url, failed); + } + + // If none of the downloads were successful then exit early + if (this.downloads.length == 0) + return; + + // Check for a custom installation prompt that may be provided by the + // applicaton + if ("@mozilla.org/addons/web-install-prompt;1" in Cc) { + try { + let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"]. + getService(Ci.amIWebInstallPrompt); + prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length); + return; + } + catch (e) {} + } + + if (Preferences.get("xpinstall.customConfirmationUI", false)) { + notifyObservers("addon-install-confirmation", this.browser, this.url, this.downloads); + return; + } + + let args = {}; + args.url = this.url; + args.installs = this.downloads; + args.wrappedJSObject = args; + + try { + Cc["@mozilla.org/base/telemetry;1"]. + getService(Ci.nsITelemetry). + getHistogramById("SECURITY_UI"). + add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL); + let parentWindow = null; + if (this.browser) { + parentWindow = this.browser.ownerDocument.defaultView; + PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser); + } + Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG, + null, "chrome,modal,centerscreen", args); + } catch (e) { + logger.warn("Exception showing install confirmation dialog", e); + for (let install of this.downloads) { + install.removeListener(this); + // Cancel the installs, as currently there is no way to make them fail + // from here. + install.cancel(); + } + notifyObservers("addon-install-cancelled", this.browser, this.url, + this.downloads); + } + }, + + /** + * Checks if all installs are now complete and if so notifies observers. + */ + checkAllInstalled: function() { + var failed = []; + + for (let install of this.downloads) { + switch (install.state) { + case AddonManager.STATE_DOWNLOADED: + case AddonManager.STATE_INSTALLING: + // Exit early if any add-ons haven't started installing yet or are + // still installing + return; + case AddonManager.STATE_INSTALL_FAILED: + failed.push(install); + break; + } + } + + this.downloads = null; + + if (failed.length > 0) + notifyObservers("addon-install-failed", this.browser, this.url, failed); + + if (this.installed.length > 0) + notifyObservers("addon-install-complete", this.browser, this.url, this.installed); + this.installed = null; + }, + + onDownloadCancelled: function(aInstall) { + aInstall.removeListener(this); + this.checkAllDownloaded(); + }, + + onDownloadFailed: function(aInstall) { + aInstall.removeListener(this); + this.checkAllDownloaded(); + }, + + onDownloadEnded: function(aInstall) { + this.checkAllDownloaded(); + return false; + }, + + onInstallCancelled: function(aInstall) { + aInstall.removeListener(this); + this.checkAllInstalled(); + }, + + onInstallFailed: function(aInstall) { + aInstall.removeListener(this); + this.checkAllInstalled(); + }, + + onInstallEnded: function(aInstall) { + aInstall.removeListener(this); + this.installed.push(aInstall); + + // If installing a theme that is disabled and can be enabled then enable it + if (aInstall.addon.type == "theme" && + aInstall.addon.userDisabled == true && + aInstall.addon.appDisabled == false) { + aInstall.addon.userDisabled = false; + } + + this.checkAllInstalled(); + } +}; + +function extWebInstallListener() { +} + +extWebInstallListener.prototype = { + /** + * @see amIWebInstallListener.idl + */ + onWebInstallDisabled: function(aBrowser, aUri, aInstalls) { + let info = { + browser: aBrowser, + originatingURI: aUri, + installs: aInstalls, + + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) + }; + Services.obs.notifyObservers(info, "addon-install-disabled", null); + }, + + /** + * @see amIWebInstallListener.idl + */ + onWebInstallOriginBlocked: function(aBrowser, aUri, aInstalls) { + let info = { + browser: aBrowser, + originatingURI: aUri, + installs: aInstalls, + + install: function() { + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) + }; + Services.obs.notifyObservers(info, "addon-install-origin-blocked", null); + + return false; + }, + + /** + * @see amIWebInstallListener.idl + */ + onWebInstallBlocked: function(aBrowser, aUri, aInstalls) { + let info = { + browser: aBrowser, + originatingURI: aUri, + installs: aInstalls, + + install: function() { + new Installer(this.browser, this.originatingURI, this.installs); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) + }; + Services.obs.notifyObservers(info, "addon-install-blocked", null); + + return false; + }, + + /** + * @see amIWebInstallListener.idl + */ + onWebInstallRequested: function(aBrowser, aUri, aInstalls) { + new Installer(aBrowser, aUri, aInstalls); + + // We start the installs ourself + return false; + }, + + classDescription: "XPI Install Handler", + contractID: "@mozilla.org/addons/web-install-listener;1", + classID: Components.ID("{0f38e086-89a3-40a5-8ffc-9b694de1d04a}"), + QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallListener, + Ci.amIWebInstallListener2]) +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([extWebInstallListener]); diff --git a/toolkit/mozapps/webextensions/content/OpenH264-license.txt b/toolkit/mozapps/webextensions/content/OpenH264-license.txt new file mode 100644 index 000000000..ad37989b8 --- /dev/null +++ b/toolkit/mozapps/webextensions/content/OpenH264-license.txt @@ -0,0 +1,59 @@ +------------------------------------------------------- +About The Cisco-Provided Binary of OpenH264 Video Codec +------------------------------------------------------- + +Cisco provides this program under the terms of the BSD license. + +Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met. + +As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk. Your rights from Cisco under the BSD license are not affected by this choice. + +For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary + +A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org + +----------- +BSD License +----------- + +Copyright © 2014 Cisco Systems, Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS†AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------- +AVC/H.264 Patent Portfolio License Notice +----------------------------------------- + +The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software: + +THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEOâ€) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO. NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM + +Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla + +--------------------------------------------- +AVC/H.264 Patent Portfolio License Conditions +--------------------------------------------- + +In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met: + +1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device; + +2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary; + +3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text: + + "OpenH264 Video Codec provided by Cisco Systems, Inc." + +4. Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user. + + + + v1.0 diff --git a/toolkit/mozapps/webextensions/content/about.js b/toolkit/mozapps/webextensions/content/about.js new file mode 100644 index 000000000..4f8fb353e --- /dev/null +++ b/toolkit/mozapps/webextensions/content/about.js @@ -0,0 +1,103 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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"; + +/* import-globals-from ../../../content/contentAreaUtils.js */ + +var Cu = Components.utils; +Cu.import("resource://gre/modules/AddonManager.jsm"); + +function init() { + var addon = window.arguments[0]; + var extensionsStrings = document.getElementById("extensionsStrings"); + + document.documentElement.setAttribute("addontype", addon.type); + + var iconURL = AddonManager.getPreferredIconURL(addon, 48, window); + if (iconURL) { + var extensionIcon = document.getElementById("extensionIcon"); + extensionIcon.src = iconURL; + } + + document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [addon.name]); + var extensionName = document.getElementById("extensionName"); + extensionName.textContent = addon.name; + + var extensionVersion = document.getElementById("extensionVersion"); + if (addon.version) + extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [addon.version])); + else + extensionVersion.hidden = true; + + var extensionDescription = document.getElementById("extensionDescription"); + if (addon.description) + extensionDescription.textContent = addon.description; + else + extensionDescription.hidden = true; + + var numDetails = 0; + + var extensionCreator = document.getElementById("extensionCreator"); + if (addon.creator) { + extensionCreator.setAttribute("value", addon.creator); + numDetails++; + } else { + extensionCreator.hidden = true; + var extensionCreatorLabel = document.getElementById("extensionCreatorLabel"); + extensionCreatorLabel.hidden = true; + } + + var extensionHomepage = document.getElementById("extensionHomepage"); + var homepageURL = addon.homepageURL; + if (homepageURL) { + extensionHomepage.setAttribute("homepageURL", homepageURL); + extensionHomepage.setAttribute("tooltiptext", homepageURL); + numDetails++; + } else { + extensionHomepage.hidden = true; + } + + numDetails += appendToList("extensionDevelopers", "developersBox", addon.developers); + numDetails += appendToList("extensionTranslators", "translatorsBox", addon.translators); + numDetails += appendToList("extensionContributors", "contributorsBox", addon.contributors); + + if (numDetails == 0) { + var groove = document.getElementById("groove"); + groove.hidden = true; + var extensionDetailsBox = document.getElementById("extensionDetailsBox"); + extensionDetailsBox.hidden = true; + } + + var acceptButton = document.documentElement.getButton("accept"); + acceptButton.label = extensionsStrings.getString("aboutWindowCloseButton"); + + setTimeout(sizeToContent, 0); +} + +function appendToList(aHeaderId, aNodeId, aItems) { + var header = document.getElementById(aHeaderId); + var node = document.getElementById(aNodeId); + + if (!aItems || aItems.length == 0) { + header.hidden = true; + return 0; + } + + for (let currentItem of aItems) { + var label = document.createElement("label"); + label.textContent = currentItem; + label.setAttribute("class", "contributor"); + node.appendChild(label); + } + + return aItems.length; +} + +function loadHomepage(aEvent) { + window.close(); + openURL(aEvent.target.getAttribute("homepageURL")); +} diff --git a/toolkit/mozapps/webextensions/content/about.xul b/toolkit/mozapps/webextensions/content/about.xul new file mode 100644 index 000000000..6effcf37a --- /dev/null +++ b/toolkit/mozapps/webextensions/content/about.xul @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + +

Test page for the discovery pane

+

Direct install

+

JS install

+ + diff --git a/toolkit/mozapps/webextensions/test/browser/head.js b/toolkit/mozapps/webextensions/test/browser/head.js new file mode 100644 index 000000000..5a749099d --- /dev/null +++ b/toolkit/mozapps/webextensions/test/browser/head.js @@ -0,0 +1,1468 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +/* globals end_test*/ + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +var tmp = {}; +Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp); +Components.utils.import("resource://gre/modules/Log.jsm", tmp); +var AddonManager = tmp.AddonManager; +var AddonManagerPrivate = tmp.AddonManagerPrivate; +var Log = tmp.Log; + +var pathParts = gTestPath.split("/"); +// Drop the test filename +pathParts.splice(pathParts.length - 1, pathParts.length); + +var gTestInWindow = /-window$/.test(pathParts[pathParts.length - 1]); + +// Drop the UI type +if (gTestInWindow) { + pathParts.splice(pathParts.length - 1, pathParts.length); +} + +const RELATIVE_DIR = pathParts.slice(4).join("/") + "/"; + +const TESTROOT = "http://example.com/" + RELATIVE_DIR; +const SECURE_TESTROOT = "https://example.com/" + RELATIVE_DIR; +const TESTROOT2 = "http://example.org/" + RELATIVE_DIR; +const SECURE_TESTROOT2 = "https://example.org/" + RELATIVE_DIR; +const CHROMEROOT = pathParts.join("/") + "/"; +const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; +const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; +const PREF_XPI_ENABLED = "xpinstall.enabled"; +const PREF_UPDATEURL = "extensions.update.url"; +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; +const PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI = "xpinstall.customConfirmationUI"; +const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory"; + +const MANAGER_URI = "about:addons"; +const INSTALL_URI = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const PREF_SEARCH_MAXRESULTS = "extensions.getAddons.maxResults"; +const PREF_STRICT_COMPAT = "extensions.strictCompatibility"; + +var PREF_CHECK_COMPATIBILITY; +(function() { + var channel = "default"; + try { + channel = Services.prefs.getCharPref("app.update.channel"); + } catch (e) { } + if (channel != "aurora" && + channel != "beta" && + channel != "release" && + channel != "esr") { + var version = "nightly"; + } else { + version = Services.appinfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1"); + } + PREF_CHECK_COMPATIBILITY = "extensions.checkCompatibility." + version; +})(); + +var gPendingTests = []; +var gTestsRun = 0; +var gTestStart = null; + +var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window); + +var gRestorePrefs = [{name: PREF_LOGGING_ENABLED}, + {name: PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI}, + {name: "extensions.webservice.discoverURL"}, + {name: "extensions.update.url"}, + {name: "extensions.update.background.url"}, + {name: "extensions.update.enabled"}, + {name: "extensions.update.autoUpdateDefault"}, + {name: "extensions.getAddons.get.url"}, + {name: "extensions.getAddons.getWithPerformance.url"}, + {name: "extensions.getAddons.search.browseURL"}, + {name: "extensions.getAddons.search.url"}, + {name: "extensions.getAddons.cache.enabled"}, + {name: "devtools.chrome.enabled"}, + {name: PREF_SEARCH_MAXRESULTS}, + {name: PREF_STRICT_COMPAT}, + {name: PREF_CHECK_COMPATIBILITY}]; + +for (let pref of gRestorePrefs) { + if (!Services.prefs.prefHasUserValue(pref.name)) { + pref.type = "clear"; + continue; + } + pref.type = Services.prefs.getPrefType(pref.name); + if (pref.type == Services.prefs.PREF_BOOL) + pref.value = Services.prefs.getBoolPref(pref.name); + else if (pref.type == Services.prefs.PREF_INT) + pref.value = Services.prefs.getIntPref(pref.name); + else if (pref.type == Services.prefs.PREF_STRING) + pref.value = Services.prefs.getCharPref(pref.name); +} + +// Turn logging on for all tests +Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); + +Services.prefs.setBoolPref(PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI, false); + +// Helper to register test failures and close windows if any are left open +function checkOpenWindows(aWindowID) { + let windows = Services.wm.getEnumerator(aWindowID); + let found = false; + while (windows.hasMoreElements()) { + let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow); + if (!win.closed) { + found = true; + win.close(); + } + } + if (found) + ok(false, "Found unexpected " + aWindowID + " window still open"); +} + +// Tools to disable and re-enable the background update and blocklist timers +// so that tests can protect themselves from unwanted timer events. +var gCatMan = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); +// Default values from toolkit/mozapps/extensions/extensions.manifest, but disable*UpdateTimer() +// records the actual value so we can put it back in enable*UpdateTimer() +var backgroundUpdateConfig = "@mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400"; +var blocklistUpdateConfig = "@mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400"; + +var UTIMER = "update-timer"; +var AMANAGER = "addonManager"; +var BLOCKLIST = "nsBlocklistService"; + +function disableBackgroundUpdateTimer() { + info("Disabling " + UTIMER + " " + AMANAGER); + backgroundUpdateConfig = gCatMan.getCategoryEntry(UTIMER, AMANAGER); + gCatMan.deleteCategoryEntry(UTIMER, AMANAGER, true); +} + +function enableBackgroundUpdateTimer() { + info("Enabling " + UTIMER + " " + AMANAGER); + gCatMan.addCategoryEntry(UTIMER, AMANAGER, backgroundUpdateConfig, false, true); +} + +function disableBlocklistUpdateTimer() { + info("Disabling " + UTIMER + " " + BLOCKLIST); + blocklistUpdateConfig = gCatMan.getCategoryEntry(UTIMER, BLOCKLIST); + gCatMan.deleteCategoryEntry(UTIMER, BLOCKLIST, true); +} + +function enableBlocklistUpdateTimer() { + info("Enabling " + UTIMER + " " + BLOCKLIST); + gCatMan.addCategoryEntry(UTIMER, BLOCKLIST, blocklistUpdateConfig, false, true); +} + +registerCleanupFunction(function() { + // Restore prefs + for (let pref of gRestorePrefs) { + if (pref.type == "clear") + Services.prefs.clearUserPref(pref.name); + else if (pref.type == Services.prefs.PREF_BOOL) + Services.prefs.setBoolPref(pref.name, pref.value); + else if (pref.type == Services.prefs.PREF_INT) + Services.prefs.setIntPref(pref.name, pref.value); + else if (pref.type == Services.prefs.PREF_STRING) + Services.prefs.setCharPref(pref.name, pref.value); + } + + // Throw an error if the add-ons manager window is open anywhere + checkOpenWindows("Addons:Manager"); + checkOpenWindows("Addons:Compatibility"); + checkOpenWindows("Addons:Install"); + + return new Promise((resolve, reject) => AddonManager.getAllInstalls(resolve)) + .then(aInstalls => { + for (let install of aInstalls) { + if (install instanceof MockInstall) + continue; + + ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state); + install.cancel(); + } + }); +}); + +function log_exceptions(aCallback, ...aArgs) { + try { + return aCallback.apply(null, aArgs); + } + catch (e) { + info("Exception thrown: " + e); + throw e; + } +} + +function log_callback(aPromise, aCallback) { + aPromise.then(aCallback) + .then(null, e => info("Exception thrown: " + e)); + return aPromise; +} + +function add_test(test) { + gPendingTests.push(test); +} + +function run_next_test() { + // Make sure we're not calling run_next_test from inside an add_task() test + // We're inside the browser_test.js 'testScope' here + if (this.__tasks) { + throw new Error("run_next_test() called from an add_task() test function. " + + "run_next_test() should not be called from inside add_task() " + + "under any circumstances!"); + } + if (gTestsRun > 0) + info("Test " + gTestsRun + " took " + (Date.now() - gTestStart) + "ms"); + + if (gPendingTests.length == 0) { + executeSoon(end_test); + return; + } + + gTestsRun++; + var test = gPendingTests.shift(); + if (test.name) + info("Running test " + gTestsRun + " (" + test.name + ")"); + else + info("Running test " + gTestsRun); + + gTestStart = Date.now(); + executeSoon(() => log_exceptions(test)); +} + +var get_tooltip_info = Task.async(function*(addon) { + let managerWindow = addon.ownerDocument.defaultView; + + // The popup code uses a triggering event's target to set the + // document.tooltipNode property. + let nameNode = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "name"); + let event = new managerWindow.CustomEvent("TriggerEvent"); + nameNode.dispatchEvent(event); + + let tooltip = managerWindow.document.getElementById("addonitem-tooltip"); + + let promise = BrowserTestUtils.waitForEvent(tooltip, "popupshown"); + tooltip.openPopup(nameNode, "after_start", 0, 0, false, false, event); + yield promise; + + let tiptext = tooltip.label; + + promise = BrowserTestUtils.waitForEvent(tooltip, "popuphidden"); + tooltip.hidePopup(); + yield promise; + + let expectedName = addon.getAttribute("name"); + ok(tiptext.substring(0, expectedName.length), expectedName, + "Tooltip should always start with the expected name"); + + if (expectedName.length == tiptext.length) { + return { + name: tiptext, + version: undefined + }; + } + return { + name: tiptext.substring(0, expectedName.length), + version: tiptext.substring(expectedName.length + 1) + }; +}); + +function get_addon_file_url(aFilename) { + try { + var cr = Cc["@mozilla.org/chrome/chrome-registry;1"]. + getService(Ci.nsIChromeRegistry); + var fileurl = cr.convertChromeURL(makeURI(CHROMEROOT + "addons/" + aFilename)); + return fileurl.QueryInterface(Ci.nsIFileURL); + } catch (ex) { + var jar = getJar(CHROMEROOT + "addons/" + aFilename); + var tmpDir = extractJarToTmp(jar); + tmpDir.append(aFilename); + + return Services.io.newFileURI(tmpDir).QueryInterface(Ci.nsIFileURL); + } +} + +function get_current_view(aManager) { + let view = aManager.document.getElementById("view-port").selectedPanel; + if (view.id == "headered-views") { + view = aManager.document.getElementById("headered-views-content").selectedPanel; + } + is(view, aManager.gViewController.displayedView, "view controller is tracking the displayed view correctly"); + return view; +} + +function get_test_items_in_list(aManager) { + var tests = "@tests.mozilla.org"; + + let view = get_current_view(aManager); + let listid = view.id == "search-view" ? "search-list" : "addon-list"; + let item = aManager.document.getElementById(listid).firstChild; + let items = []; + + while (item) { + if (item.localName != "richlistitem") { + item = item.nextSibling; + continue; + } + + if (!item.mAddon || item.mAddon.id.substring(item.mAddon.id.length - tests.length) == tests) + items.push(item); + item = item.nextSibling; + } + + return items; +} + +function check_all_in_list(aManager, aIds, aIgnoreExtras) { + var doc = aManager.document; + var view = get_current_view(aManager); + var listid = view.id == "search-view" ? "search-list" : "addon-list"; + var list = doc.getElementById(listid); + + var inlist = []; + var node = list.firstChild; + while (node) { + if (node.value) + inlist.push(node.value); + node = node.nextSibling; + } + + for (let id of aIds) { + if (inlist.indexOf(id) == -1) + ok(false, "Should find " + id + " in the list"); + } + + if (aIgnoreExtras) + return; + + for (let inlistItem of inlist) { + if (aIds.indexOf(inlistItem) == -1) + ok(false, "Shouldn't have seen " + inlistItem + " in the list"); + } +} + +function get_addon_element(aManager, aId) { + var doc = aManager.document; + var view = get_current_view(aManager); + var listid = "addon-list"; + if (view.id == "search-view") + listid = "search-list"; + else if (view.id == "updates-view") + listid = "updates-list"; + var list = doc.getElementById(listid); + + var node = list.firstChild; + while (node) { + if (node.value == aId) + return node; + node = node.nextSibling; + } + return null; +} + +function wait_for_view_load(aManagerWindow, aCallback, aForceWait, aLongerTimeout) { + requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); + + if (!aForceWait && !aManagerWindow.gViewController.isLoading) { + log_exceptions(aCallback, aManagerWindow); + return; + } + + aManagerWindow.document.addEventListener("ViewChanged", function() { + aManagerWindow.document.removeEventListener("ViewChanged", arguments.callee, false); + log_exceptions(aCallback, aManagerWindow); + }, false); +} + +function wait_for_manager_load(aManagerWindow, aCallback) { + if (!aManagerWindow.gIsInitializing) { + log_exceptions(aCallback, aManagerWindow); + return; + } + + info("Waiting for initialization"); + aManagerWindow.document.addEventListener("Initialized", function() { + aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false); + log_exceptions(aCallback, aManagerWindow); + }, false); +} + +function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) { + let p = new Promise((resolve, reject) => { + + function setup_manager(aManagerWindow) { + if (aLoadCallback) + log_exceptions(aLoadCallback, aManagerWindow); + + if (aView) + aManagerWindow.loadView(aView); + + ok(aManagerWindow != null, "Should have an add-ons manager window"); + is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI"); + + waitForFocus(function() { + info("window has focus, waiting for manager load"); + wait_for_manager_load(aManagerWindow, function() { + info("Manager waiting for view load"); + wait_for_view_load(aManagerWindow, function() { + resolve(aManagerWindow); + }, null, aLongerTimeout); + }); + }, aManagerWindow); + } + + if (gUseInContentUI) { + info("Loading manager window in tab"); + Services.obs.addObserver(function (aSubject, aTopic, aData) { + Services.obs.removeObserver(arguments.callee, aTopic); + if (aSubject.location.href != MANAGER_URI) { + info("Ignoring load event for " + aSubject.location.href); + return; + } + setup_manager(aSubject); + }, "EM-loaded", false); + + gBrowser.selectedTab = gBrowser.addTab(); + switchToTabHavingURI(MANAGER_URI, true); + } else { + info("Loading manager window in dialog"); + Services.obs.addObserver(function (aSubject, aTopic, aData) { + Services.obs.removeObserver(arguments.callee, aTopic); + setup_manager(aSubject); + }, "EM-loaded", false); + + openDialog(MANAGER_URI); + } + }); + + // The promise resolves with the manager window, so it is passed to the callback + return log_callback(p, aCallback); +} + +function close_manager(aManagerWindow, aCallback, aLongerTimeout) { + let p = new Promise((resolve, reject) => { + requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); + + ok(aManagerWindow != null, "Should have an add-ons manager window to close"); + is(aManagerWindow.location, MANAGER_URI, "Should be closing window with correct URI"); + + aManagerWindow.addEventListener("unload", function() { + try { + dump("Manager window unload handler\n"); + this.removeEventListener("unload", arguments.callee, false); + resolve(); + } catch (e) { + reject(e); + } + }, false); + }); + + info("Telling manager window to close"); + aManagerWindow.close(); + info("Manager window close() call returned"); + + return log_callback(p, aCallback); +} + +function restart_manager(aManagerWindow, aView, aCallback, aLoadCallback) { + if (!aManagerWindow) { + return open_manager(aView, aCallback, aLoadCallback); + } + + return close_manager(aManagerWindow) + .then(() => open_manager(aView, aCallback, aLoadCallback)); +} + +function wait_for_window_open(aCallback) { + Services.wm.addListener({ + onOpenWindow: function(aWindow) { + Services.wm.removeListener(this); + + let domwindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + domwindow.addEventListener("load", function() { + domwindow.removeEventListener("load", arguments.callee, false); + executeSoon(function() { + aCallback(domwindow); + }); + }, false); + }, + + onCloseWindow: function(aWindow) { + }, + + onWindowTitleChange: function(aWindow, aTitle) { + } + }); +} + +function get_string(aName, ...aArgs) { + var bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/extensions.properties"); + if (aArgs.length == 0) + return bundle.GetStringFromName(aName); + return bundle.formatStringFromName(aName, aArgs, aArgs.length); +} + +function formatDate(aDate) { + const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIXULChromeRegistry) + .getSelectedLocale("global", true); + const dtOptions = { year: 'numeric', month: 'long', day: 'numeric' }; + return aDate.toLocaleDateString(locale, dtOptions); +} + +function is_hidden(aElement) { + var style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, ""); + if (style.display == "none") + return true; + if (style.visibility != "visible") + return true; + + // Hiding a parent element will hide all its children + if (aElement.parentNode != aElement.ownerDocument) + return is_hidden(aElement.parentNode); + + return false; +} + +function is_element_visible(aElement, aMsg) { + isnot(aElement, null, "Element should not be null, when checking visibility"); + ok(!is_hidden(aElement), aMsg || (aElement + " should be visible")); +} + +function is_element_hidden(aElement, aMsg) { + isnot(aElement, null, "Element should not be null, when checking visibility"); + ok(is_hidden(aElement), aMsg || (aElement + " should be hidden")); +} + +function promiseAddonByID(aId) { + return new Promise(resolve => { + AddonManager.getAddonByID(aId, resolve); + }); +} + +function promiseAddonsByIDs(aIDs) { + return new Promise(resolve => { + AddonManager.getAddonsByIDs(aIDs, resolve); + }); +} +/** + * Install an add-on and call a callback when complete. + * + * The callback will receive the Addon for the installed add-on. + */ +function install_addon(path, cb, pathPrefix=TESTROOT) { + let p = new Promise((resolve, reject) => { + AddonManager.getInstallForURL(pathPrefix + path, (install) => { + install.addListener({ + onInstallEnded: () => resolve(install.addon), + }); + + install.install(); + }, "application/x-xpinstall"); + }); + + return log_callback(p, cb); +} + +function CategoryUtilities(aManagerWindow) { + this.window = aManagerWindow; + + var self = this; + this.window.addEventListener("unload", function() { + self.window.removeEventListener("unload", arguments.callee, false); + self.window = null; + }, false); +} + +CategoryUtilities.prototype = { + window: null, + + get selectedCategory() { + isnot(this.window, null, "Should not get selected category when manager window is not loaded"); + var selectedItem = this.window.document.getElementById("categories").selectedItem; + isnot(selectedItem, null, "A category should be selected"); + var view = this.window.gViewController.parseViewId(selectedItem.value); + return (view.type == "list") ? view.param : view.type; + }, + + get: function(aCategoryType, aAllowMissing) { + isnot(this.window, null, "Should not get category when manager window is not loaded"); + var categories = this.window.document.getElementById("categories"); + + var viewId = "addons://list/" + aCategoryType; + var items = categories.getElementsByAttribute("value", viewId); + if (items.length) + return items[0]; + + viewId = "addons://" + aCategoryType + "/"; + items = categories.getElementsByAttribute("value", viewId); + if (items.length) + return items[0]; + + if (!aAllowMissing) + ok(false, "Should have found a category with type " + aCategoryType); + return null; + }, + + getViewId: function(aCategoryType) { + isnot(this.window, null, "Should not get view id when manager window is not loaded"); + return this.get(aCategoryType).value; + }, + + isVisible: function(aCategory) { + isnot(this.window, null, "Should not check visible state when manager window is not loaded"); + if (aCategory.hasAttribute("disabled") && + aCategory.getAttribute("disabled") == "true") + return false; + + return !is_hidden(aCategory); + }, + + isTypeVisible: function(aCategoryType) { + return this.isVisible(this.get(aCategoryType)); + }, + + open: function(aCategory, aCallback) { + + isnot(this.window, null, "Should not open category when manager window is not loaded"); + ok(this.isVisible(aCategory), "Category should be visible if attempting to open it"); + + EventUtils.synthesizeMouse(aCategory, 2, 2, { }, this.window); + let p = new Promise((resolve, reject) => wait_for_view_load(this.window, resolve)); + + return log_callback(p, aCallback); + }, + + openType: function(aCategoryType, aCallback) { + return this.open(this.get(aCategoryType), aCallback); + } +} + +function CertOverrideListener(host, bits) { + this.host = host; + this.bits = bits; +} + +CertOverrideListener.prototype = { + host: null, + bits: null, + + getInterface: function (aIID) { + return this.QueryInterface(aIID); + }, + + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIBadCertListener2) || + aIID.equals(Ci.nsIInterfaceRequestor) || + aIID.equals(Ci.nsISupports)) + return this; + + throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE); + }, + + notifyCertProblem: function (socketInfo, sslStatus, targetHost) { + var cert = sslStatus.QueryInterface(Components.interfaces.nsISSLStatus) + .serverCert; + var cos = Cc["@mozilla.org/security/certoverride;1"]. + getService(Ci.nsICertOverrideService); + cos.rememberValidityOverride(this.host, -1, cert, this.bits, false); + return true; + } +} + +// Add overrides for the bad certificates +function addCertOverride(host, bits) { + var req = new XMLHttpRequest(); + try { + req.open("GET", "https://" + host + "/", false); + req.channel.notificationCallbacks = new CertOverrideListener(host, bits); + req.send(null); + } + catch (e) { + // This request will fail since the SSL server is not trusted yet + } +} + +/** *** Mock Provider *****/ + +function MockProvider(aUseAsyncCallbacks, aTypes) { + this.addons = []; + this.installs = []; + this.callbackTimers = []; + this.timerLocations = new Map(); + this.useAsyncCallbacks = (aUseAsyncCallbacks === undefined) ? true : aUseAsyncCallbacks; + this.types = (aTypes === undefined) ? [{ + id: "extension", + name: "Extensions", + uiPriority: 4000, + flags: AddonManager.TYPE_UI_VIEW_LIST | + AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL, + }] : aTypes; + + var self = this; + registerCleanupFunction(function() { + if (self.started) + self.unregister(); + }); + + this.register(); +} + +MockProvider.prototype = { + addons: null, + installs: null, + started: null, + apiDelay: 10, + callbackTimers: null, + timerLocations: null, + useAsyncCallbacks: null, + types: null, + + /** *** Utility functions *****/ + + /** + * Register this provider with the AddonManager + */ + register: function MP_register() { + info("Registering mock add-on provider"); + AddonManagerPrivate.registerProvider(this, this.types); + }, + + /** + * Unregister this provider with the AddonManager + */ + unregister: function MP_unregister() { + info("Unregistering mock add-on provider"); + AddonManagerPrivate.unregisterProvider(this); + }, + + /** + * Adds an add-on to the list of add-ons that this provider exposes to the + * AddonManager, dispatching appropriate events in the process. + * + * @param aAddon + * The add-on to add + */ + addAddon: function MP_addAddon(aAddon) { + var oldAddons = this.addons.filter(aOldAddon => aOldAddon.id == aAddon.id); + var oldAddon = oldAddons.length > 0 ? oldAddons[0] : null; + + this.addons = this.addons.filter(aOldAddon => aOldAddon.id != aAddon.id); + + this.addons.push(aAddon); + aAddon._provider = this; + + if (!this.started) + return; + + let requiresRestart = (aAddon.operationsRequiringRestart & + AddonManager.OP_NEEDS_RESTART_INSTALL) != 0; + AddonManagerPrivate.callInstallListeners("onExternalInstall", null, aAddon, + oldAddon, requiresRestart) + }, + + /** + * Removes an add-on from the list of add-ons that this provider exposes to + * the AddonManager, dispatching the onUninstalled event in the process. + * + * @param aAddon + * The add-on to add + */ + removeAddon: function MP_removeAddon(aAddon) { + var pos = this.addons.indexOf(aAddon); + if (pos == -1) { + ok(false, "Tried to remove an add-on that wasn't registered with the mock provider"); + return; + } + + this.addons.splice(pos, 1); + + if (!this.started) + return; + + AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon); + }, + + /** + * Adds an add-on install to the list of installs that this provider exposes + * to the AddonManager, dispatching appropriate events in the process. + * + * @param aInstall + * The add-on install to add + */ + addInstall: function MP_addInstall(aInstall) { + this.installs.push(aInstall); + aInstall._provider = this; + + if (!this.started) + return; + + aInstall.callListeners("onNewInstall"); + }, + + removeInstall: function MP_removeInstall(aInstall) { + var pos = this.installs.indexOf(aInstall); + if (pos == -1) { + ok(false, "Tried to remove an install that wasn't registered with the mock provider"); + return; + } + + this.installs.splice(pos, 1); + }, + + /** + * Creates a set of mock add-on objects and adds them to the list of add-ons + * managed by this provider. + * + * @param aAddonProperties + * An array of objects containing properties describing the add-ons + * @return Array of the new MockAddons + */ + createAddons: function MP_createAddons(aAddonProperties) { + var newAddons = []; + for (let addonProp of aAddonProperties) { + let addon = new MockAddon(addonProp.id); + for (let prop in addonProp) { + if (prop == "id") + continue; + if (prop == "applyBackgroundUpdates") { + addon._applyBackgroundUpdates = addonProp[prop]; + continue; + } + if (prop == "appDisabled") { + addon._appDisabled = addonProp[prop]; + continue; + } + addon[prop] = addonProp[prop]; + } + if (!addon.optionsType && !!addon.optionsURL) + addon.optionsType = AddonManager.OPTIONS_TYPE_DIALOG; + + // Make sure the active state matches the passed in properties + addon.isActive = addon.shouldBeActive; + + this.addAddon(addon); + newAddons.push(addon); + } + + return newAddons; + }, + + /** + * Creates a set of mock add-on install objects and adds them to the list + * of installs managed by this provider. + * + * @param aInstallProperties + * An array of objects containing properties describing the installs + * @return Array of the new MockInstalls + */ + createInstalls: function MP_createInstalls(aInstallProperties) { + var newInstalls = []; + for (let installProp of aInstallProperties) { + let install = new MockInstall(installProp.name || null, + installProp.type || null, + null); + for (let prop in installProp) { + switch (prop) { + case "name": + case "type": + break; + case "sourceURI": + install[prop] = NetUtil.newURI(installProp[prop]); + break; + default: + install[prop] = installProp[prop]; + } + } + this.addInstall(install); + newInstalls.push(install); + } + + return newInstalls; + }, + + /** *** AddonProvider implementation *****/ + + /** + * Called to initialize the provider. + */ + startup: function MP_startup() { + this.started = true; + }, + + /** + * Called when the provider should shutdown. + */ + shutdown: function MP_shutdown() { + if (this.callbackTimers.length) { + info("MockProvider: pending callbacks at shutdown(): calling immediately"); + } + while (this.callbackTimers.length > 0) { + // When we notify the callback timer, it removes itself from our array + let timer = this.callbackTimers[0]; + try { + let setAt = this.timerLocations.get(timer); + info("Notifying timer set at " + (setAt || "unknown location")); + timer.callback.notify(timer); + timer.cancel(); + } catch (e) { + info("Timer notify failed: " + e); + } + } + this.callbackTimers = []; + this.timerLocations = null; + + this.started = false; + }, + + /** + * Called to get an Addon with a particular ID. + * + * @param aId + * The ID of the add-on to retrieve + * @param aCallback + * A callback to pass the Addon to + */ + getAddonByID: function MP_getAddon(aId, aCallback) { + for (let addon of this.addons) { + if (addon.id == aId) { + this._delayCallback(aCallback, addon); + return; + } + } + + aCallback(null); + }, + + /** + * Called to get Addons of a particular type. + * + * @param aTypes + * An array of types to fetch. Can be null to get all types. + * @param callback + * A callback to pass an array of Addons to + */ + getAddonsByTypes: function MP_getAddonsByTypes(aTypes, aCallback) { + var addons = this.addons.filter(function(aAddon) { + if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1) + return false; + return true; + }); + this._delayCallback(aCallback, addons); + }, + + /** + * Called to get Addons that have pending operations. + * + * @param aTypes + * An array of types to fetch. Can be null to get all types + * @param aCallback + * A callback to pass an array of Addons to + */ + getAddonsWithOperationsByTypes: function MP_getAddonsWithOperationsByTypes(aTypes, aCallback) { + var addons = this.addons.filter(function(aAddon) { + if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1) + return false; + return aAddon.pendingOperations != 0; + }); + this._delayCallback(aCallback, addons); + }, + + /** + * Called to get the current AddonInstalls, optionally restricting by type. + * + * @param aTypes + * An array of types or null to get all types + * @param aCallback + * A callback to pass the array of AddonInstalls to + */ + getInstallsByTypes: function MP_getInstallsByTypes(aTypes, aCallback) { + var installs = this.installs.filter(function(aInstall) { + // Appear to have actually removed cancelled installs from the provider + if (aInstall.state == AddonManager.STATE_CANCELLED) + return false; + + if (aTypes && aTypes.length > 0 && aTypes.indexOf(aInstall.type) == -1) + return false; + + return true; + }); + this._delayCallback(aCallback, installs); + }, + + /** + * Called when a new add-on has been enabled when only one add-on of that type + * can be enabled. + * + * @param aId + * The ID of the newly enabled add-on + * @param aType + * The type of the newly enabled add-on + * @param aPendingRestart + * true if the newly enabled add-on will only become enabled after a + * restart + */ + addonChanged: function MP_addonChanged(aId, aType, aPendingRestart) { + // Not implemented + }, + + /** + * Update the appDisabled property for all add-ons. + */ + updateAddonAppDisabledStates: function MP_updateAddonAppDisabledStates() { + // Not needed + }, + + /** + * Called to get an AddonInstall to download and install an add-on from a URL. + * + * @param aUrl + * The URL to be installed + * @param aHash + * A hash for the install + * @param aName + * A name for the install + * @param aIconURL + * An icon URL for the install + * @param aVersion + * A version for the install + * @param aLoadGroup + * An nsILoadGroup to associate requests with + * @param aCallback + * A callback to pass the AddonInstall to + */ + getInstallForURL: function MP_getInstallForURL(aUrl, aHash, aName, aIconURL, + aVersion, aLoadGroup, aCallback) { + // Not yet implemented + }, + + /** + * Called to get an AddonInstall to install an add-on from a local file. + * + * @param aFile + * The file to be installed + * @param aCallback + * A callback to pass the AddonInstall to + */ + getInstallForFile: function MP_getInstallForFile(aFile, aCallback) { + // Not yet implemented + }, + + /** + * Called to test whether installing add-ons is enabled. + * + * @return true if installing is enabled + */ + isInstallEnabled: function MP_isInstallEnabled() { + return false; + }, + + /** + * Called to test whether this provider supports installing a particular + * mimetype. + * + * @param aMimetype + * The mimetype to check for + * @return true if the mimetype is supported + */ + supportsMimetype: function MP_supportsMimetype(aMimetype) { + return false; + }, + + /** + * Called to test whether installing add-ons from a URI is allowed. + * + * @param aUri + * The URI being installed from + * @return true if installing is allowed + */ + isInstallAllowed: function MP_isInstallAllowed(aUri) { + return false; + }, + + + /** *** Internal functions *****/ + + /** + * Delay calling a callback to fake a time-consuming async operation. + * The delay is specified by the apiDelay property, in milliseconds. + * Parameters to send to the callback should be specified as arguments after + * the aCallback argument. + * + * @param aCallback Callback to eventually call + */ + _delayCallback: function MP_delayCallback(aCallback, ...aArgs) { + if (!this.useAsyncCallbacks) { + aCallback(...aArgs); + return; + } + + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + // Need to keep a reference to the timer, so it doesn't get GC'ed + this.callbackTimers.push(timer); + // Capture a stack trace where the timer was set + // needs the 'new Error' hack until bug 1007656 + this.timerLocations.set(timer, Log.stackTrace(new Error("dummy"))); + timer.initWithCallback(() => { + let idx = this.callbackTimers.indexOf(timer); + if (idx == -1) { + dump("MockProvider._delayCallback lost track of timer set at " + + (this.timerLocations.get(timer) || "unknown location") + "\n"); + } else { + this.callbackTimers.splice(idx, 1); + } + this.timerLocations.delete(timer); + aCallback(...aArgs); + }, this.apiDelay, timer.TYPE_ONE_SHOT); + } +}; + +/** *** Mock Addon object for the Mock Provider *****/ + +function MockAddon(aId, aName, aType, aOperationsRequiringRestart) { + // Only set required attributes. + this.id = aId || ""; + this.name = aName || ""; + this.type = aType || "extension"; + this.version = ""; + this.isCompatible = true; + this.providesUpdatesSecurely = true; + this.blocklistState = 0; + this._appDisabled = false; + this._userDisabled = false; + this._applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE; + this.scope = AddonManager.SCOPE_PROFILE; + this.isActive = true; + this.creator = ""; + this.pendingOperations = 0; + this._permissions = AddonManager.PERM_CAN_UNINSTALL | + AddonManager.PERM_CAN_ENABLE | + AddonManager.PERM_CAN_DISABLE | + AddonManager.PERM_CAN_UPGRADE; + this.operationsRequiringRestart = (aOperationsRequiringRestart != undefined) ? + aOperationsRequiringRestart : + (AddonManager.OP_NEEDS_RESTART_INSTALL | + AddonManager.OP_NEEDS_RESTART_UNINSTALL | + AddonManager.OP_NEEDS_RESTART_ENABLE | + AddonManager.OP_NEEDS_RESTART_DISABLE); +} + +MockAddon.prototype = { + get isCorrectlySigned() { + if (this.signedState === AddonManager.SIGNEDSTATE_NOT_REQUIRED) + return true; + return this.signedState > AddonManager.SIGNEDSTATE_MISSING; + }, + + get shouldBeActive() { + return !this.appDisabled && !this._userDisabled && + !(this.pendingOperations & AddonManager.PENDING_UNINSTALL); + }, + + get appDisabled() { + return this._appDisabled; + }, + + set appDisabled(val) { + if (val == this._appDisabled) + return val; + + AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["appDisabled"]); + + var currentActive = this.shouldBeActive; + this._appDisabled = val; + var newActive = this.shouldBeActive; + this._updateActiveState(currentActive, newActive); + + return val; + }, + + get userDisabled() { + return this._userDisabled; + }, + + set userDisabled(val) { + if (val == this._userDisabled) + return val; + + var currentActive = this.shouldBeActive; + this._userDisabled = val; + var newActive = this.shouldBeActive; + this._updateActiveState(currentActive, newActive); + + return val; + }, + + get permissions() { + let permissions = this._permissions; + if (this.appDisabled || !this._userDisabled) + permissions &= ~AddonManager.PERM_CAN_ENABLE; + if (this.appDisabled || this._userDisabled) + permissions &= ~AddonManager.PERM_CAN_DISABLE; + return permissions; + }, + + set permissions(val) { + return this._permissions = val; + }, + + get applyBackgroundUpdates() { + return this._applyBackgroundUpdates; + }, + + set applyBackgroundUpdates(val) { + if (val != AddonManager.AUTOUPDATE_DEFAULT && + val != AddonManager.AUTOUPDATE_DISABLE && + val != AddonManager.AUTOUPDATE_ENABLE) { + ok(false, "addon.applyBackgroundUpdates set to an invalid value: " + val); + } + this._applyBackgroundUpdates = val; + AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]); + }, + + isCompatibleWith: function(aAppVersion, aPlatformVersion) { + return true; + }, + + findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) { + // Tests can implement this if they need to + }, + + uninstall: function(aAlwaysAllowUndo = false) { + if ((this.operationsRequiringRestart & AddonManager.OP_NEED_RESTART_UNINSTALL) + && this.pendingOperations & AddonManager.PENDING_UNINSTALL) + throw Components.Exception("Add-on is already pending uninstall"); + + var needsRestart = aAlwaysAllowUndo || !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL); + this.pendingOperations |= AddonManager.PENDING_UNINSTALL; + AddonManagerPrivate.callAddonListeners("onUninstalling", this, needsRestart); + if (!needsRestart) { + this.pendingOperations -= AddonManager.PENDING_UNINSTALL; + this._provider.removeAddon(this); + } else if (!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)) { + this.isActive = false; + } + }, + + cancelUninstall: function() { + if (!(this.pendingOperations & AddonManager.PENDING_UNINSTALL)) + throw Components.Exception("Add-on is not pending uninstall"); + + this.pendingOperations -= AddonManager.PENDING_UNINSTALL; + this.isActive = this.shouldBeActive; + AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); + }, + + markAsSeen: function() { + this.seen = true; + }, + + _updateActiveState: function(currentActive, newActive) { + if (currentActive == newActive) + return; + + if (newActive == this.isActive) { + this.pendingOperations -= (newActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE); + AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); + } + else if (newActive) { + let needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE); + this.pendingOperations |= AddonManager.PENDING_ENABLE; + AddonManagerPrivate.callAddonListeners("onEnabling", this, needsRestart); + if (!needsRestart) { + this.isActive = newActive; + this.pendingOperations -= AddonManager.PENDING_ENABLE; + AddonManagerPrivate.callAddonListeners("onEnabled", this); + } + } + else { + let needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE); + this.pendingOperations |= AddonManager.PENDING_DISABLE; + AddonManagerPrivate.callAddonListeners("onDisabling", this, needsRestart); + if (!needsRestart) { + this.isActive = newActive; + this.pendingOperations -= AddonManager.PENDING_DISABLE; + AddonManagerPrivate.callAddonListeners("onDisabled", this); + } + } + } +}; + +/** *** Mock AddonInstall object for the Mock Provider *****/ + +function MockInstall(aName, aType, aAddonToInstall) { + this.name = aName || ""; + // Don't expose type until download completed + this._type = aType || "extension"; + this.type = null; + this.version = "1.0"; + this.iconURL = ""; + this.infoURL = ""; + this.state = AddonManager.STATE_AVAILABLE; + this.error = 0; + this.sourceURI = null; + this.file = null; + this.progress = 0; + this.maxProgress = -1; + this.certificate = null; + this.certName = ""; + this.existingAddon = null; + this.addon = null; + this._addonToInstall = aAddonToInstall; + this.listeners = []; + + // Another type of install listener for tests that want to check the results + // of code run from standard install listeners + this.testListeners = []; +} + +MockInstall.prototype = { + install: function() { + switch (this.state) { + case AddonManager.STATE_AVAILABLE: + this.state = AddonManager.STATE_DOWNLOADING; + if (!this.callListeners("onDownloadStarted")) { + this.state = AddonManager.STATE_CANCELLED; + this.callListeners("onDownloadCancelled"); + return; + } + + this.type = this._type; + + // Adding addon to MockProvider to be implemented when needed + if (this._addonToInstall) + this.addon = this._addonToInstall; + else { + this.addon = new MockAddon("", this.name, this.type); + this.addon.version = this.version; + this.addon.pendingOperations = AddonManager.PENDING_INSTALL; + } + this.addon.install = this; + if (this.existingAddon) { + if (!this.addon.id) + this.addon.id = this.existingAddon.id; + this.existingAddon.pendingUpgrade = this.addon; + this.existingAddon.pendingOperations |= AddonManager.PENDING_UPGRADE; + } + + this.state = AddonManager.STATE_DOWNLOADED; + this.callListeners("onDownloadEnded"); + + case AddonManager.STATE_DOWNLOADED: + this.state = AddonManager.STATE_INSTALLING; + if (!this.callListeners("onInstallStarted")) { + this.state = AddonManager.STATE_CANCELLED; + this.callListeners("onInstallCancelled"); + return; + } + + let needsRestart = (this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_INSTALL); + AddonManagerPrivate.callAddonListeners("onInstalling", this.addon, needsRestart); + if (!needsRestart) { + AddonManagerPrivate.callAddonListeners("onInstalled", this.addon); + } + + this.state = AddonManager.STATE_INSTALLED; + this.callListeners("onInstallEnded"); + break; + case AddonManager.STATE_DOWNLOADING: + case AddonManager.STATE_CHECKING: + case AddonManager.STATE_INSTALLING: + // Installation is already running + return; + default: + ok(false, "Cannot start installing when state = " + this.state); + } + }, + + cancel: function() { + switch (this.state) { + case AddonManager.STATE_AVAILABLE: + this.state = AddonManager.STATE_CANCELLED; + break; + case AddonManager.STATE_INSTALLED: + this.state = AddonManager.STATE_CANCELLED; + this._provider.removeInstall(this); + this.callListeners("onInstallCancelled"); + break; + default: + // Handling cancelling when downloading to be implemented when needed + ok(false, "Cannot cancel when state = " + this.state); + } + }, + + + addListener: function(aListener) { + if (!this.listeners.some(i => i == aListener)) + this.listeners.push(aListener); + }, + + removeListener: function(aListener) { + this.listeners = this.listeners.filter(i => i != aListener); + }, + + addTestListener: function(aListener) { + if (!this.testListeners.some(i => i == aListener)) + this.testListeners.push(aListener); + }, + + removeTestListener: function(aListener) { + this.testListeners = this.testListeners.filter(i => i != aListener); + }, + + callListeners: function(aMethod) { + var result = AddonManagerPrivate.callInstallListeners(aMethod, this.listeners, + this, this.addon); + + // Call test listeners after standard listeners to remove race condition + // between standard and test listeners + for (let listener of this.testListeners) { + try { + if (aMethod in listener) + if (listener[aMethod].call(listener, this, this.addon) === false) + result = false; + } + catch (e) { + ok(false, "Test listener threw exception: " + e); + } + } + + return result; + } +}; + +function waitForCondition(condition, nextTest, errorMsg) { + let tries = 0; + let interval = setInterval(function() { + if (tries >= 30) { + ok(false, errorMsg); + moveOn(); + } + var conditionPassed; + try { + conditionPassed = condition(); + } catch (e) { + ok(false, e + "\n" + e.stack); + conditionPassed = false; + } + if (conditionPassed) { + moveOn(); + } + tries++; + }, 100); + let moveOn = function() { clearInterval(interval); nextTest(); }; +} + +function getTestPluginTag() { + let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let tags = ph.getPluginTags(); + + // Find the test plugin + for (let i = 0; i < tags.length; i++) { + if (tags[i].name == "Test Plug-in") + return tags[i]; + } + ok(false, "Unable to find plugin"); + return null; +} diff --git a/toolkit/mozapps/webextensions/test/browser/more_options.xul b/toolkit/mozapps/webextensions/test/browser/more_options.xul new file mode 100644 index 000000000..28dbb0a2e --- /dev/null +++ b/toolkit/mozapps/webextensions/test/browser/more_options.xul @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/mozapps/webextensions/test/browser/moz.build b/toolkit/mozapps/webextensions/test/browser/moz.build new file mode 100644 index 000000000..af04aaeef --- /dev/null +++ b/toolkit/mozapps/webextensions/test/browser/moz.build @@ -0,0 +1,10 @@ +# -*- 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/. + +BROWSER_CHROME_MANIFESTS += [ + 'browser-window.ini', + 'browser.ini', +] diff --git a/toolkit/mozapps/webextensions/test/browser/options.xul b/toolkit/mozapps/webextensions/test/browser/options.xul new file mode 100644 index 000000000..1b6827915 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/browser/options.xul @@ -0,0 +1,12 @@ + + + + + Description Text Node + + This is a test, +

+

+ + diff --git a/toolkit/mozapps/webextensions/test/xpinstall/cookieRedirect.sjs b/toolkit/mozapps/webextensions/test/xpinstall/cookieRedirect.sjs new file mode 100644 index 000000000..92bccd9ec --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/cookieRedirect.sjs @@ -0,0 +1,24 @@ +// Simple script redirects to the query part of the uri if the cookie "xpinstall" +// has the value "true", otherwise gives a 500 error. + +function handleRequest(request, response) +{ + let cookie = null; + if (request.hasHeader("Cookie")) { + let cookies = request.getHeader("Cookie").split(";"); + for (let i = 0; i < cookies.length; i++) { + if (cookies[i].substring(0, 10) == "xpinstall=") + cookie = cookies[i].substring(10); + } + } + + if (cookie == "true") { + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", request.queryString); + response.write("See " + request.queryString); + } + else { + response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); + response.write("Invalid request"); + } +} diff --git a/toolkit/mozapps/webextensions/test/xpinstall/corrupt.xpi b/toolkit/mozapps/webextensions/test/xpinstall/corrupt.xpi new file mode 100644 index 000000000..35d7bd5e5 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/corrupt.xpi @@ -0,0 +1 @@ +This is a corrupt zip file diff --git a/toolkit/mozapps/webextensions/test/xpinstall/empty.xpi b/toolkit/mozapps/webextensions/test/xpinstall/empty.xpi new file mode 100644 index 000000000..74ed2b817 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/empty.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/enabled.html b/toolkit/mozapps/webextensions/test/xpinstall/enabled.html new file mode 100644 index 000000000..370cde8fb --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/enabled.html @@ -0,0 +1,24 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+

+ + diff --git a/toolkit/mozapps/webextensions/test/xpinstall/hashRedirect.sjs b/toolkit/mozapps/webextensions/test/xpinstall/hashRedirect.sjs new file mode 100644 index 000000000..324a092a3 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/hashRedirect.sjs @@ -0,0 +1,15 @@ +// Simple script redirects takes the query part of te request and splits it on +// the | character. Anything before is included as the X-Target-Digest header +// the latter part is used as the url to redirect to + +function handleRequest(request, response) +{ + let pos = request.queryString.indexOf("|"); + let header = request.queryString.substring(0, pos); + let url = request.queryString.substring(pos + 1); + + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("X-Target-Digest", header); + response.setHeader("Location", url); + response.write("See " + url); +} diff --git a/toolkit/mozapps/webextensions/test/xpinstall/head.js b/toolkit/mozapps/webextensions/test/xpinstall/head.js new file mode 100644 index 000000000..197fe3fac --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/head.js @@ -0,0 +1,434 @@ +const RELATIVE_DIR = "toolkit/mozapps/extensions/test/xpinstall/"; + +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; +const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR; +const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; +const PROMPT_URL = "chrome://global/content/commonDialog.xul"; +const ADDONS_URL = "chrome://mozapps/content/extensions/extensions.xul"; +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; +const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; +const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin"; +const PREF_CUSTOM_CONFIRMATION_UI = "xpinstall.customConfirmationUI"; +const CHROME_NAME = "mochikit"; + +function getChromeRoot(path) { + if (path === undefined) { + return "chrome://" + CHROME_NAME + "/content/browser/" + RELATIVE_DIR + } + return getRootDirectory(path); +} + +function extractChromeRoot(path) { + var chromeRootPath = getChromeRoot(path); + var jar = getJar(chromeRootPath); + if (jar) { + var tmpdir = extractJarToTmp(jar); + return "file://" + tmpdir.path + "/"; + } + return chromeRootPath; +} + +Services.prefs.setBoolPref(PREF_CUSTOM_CONFIRMATION_UI, false); +registerCleanupFunction(() => { + Services.prefs.clearUserPref(PREF_CUSTOM_CONFIRMATION_UI); +}); + +/** + * This is a test harness designed to handle responding to UI during the process + * of installing an XPI. A test can set callbacks to hear about specific parts + * of the sequence. + * Before use setup must be called and finish must be called afterwards. + */ +var Harness = { + // If set then the callback is called when an install is attempted and + // software installation is disabled. + installDisabledCallback: null, + // If set then the callback is called when an install is attempted and + // then canceled. + installCancelledCallback: null, + // If set then the callback will be called when an install's origin is blocked. + installOriginBlockedCallback: null, + // If set then the callback will be called when an install is blocked by the + // whitelist. The callback should return true to continue with the install + // anyway. + installBlockedCallback: null, + // If set will be called in the event of authentication being needed to get + // the xpi. Should return a 2 element array of username and password, or + // null to not authenticate. + authenticationCallback: null, + // If set this will be called to allow checking the contents of the xpinstall + // confirmation dialog. The callback should return true to continue the install. + installConfirmCallback: null, + // If set will be called when downloading of an item has begun. + downloadStartedCallback: null, + // If set will be called during the download of an item. + downloadProgressCallback: null, + // If set will be called when an xpi fails to download. + downloadFailedCallback: null, + // If set will be called when an xpi download is cancelled. + downloadCancelledCallback: null, + // If set will be called when downloading of an item has ended. + downloadEndedCallback: null, + // If set will be called when installation by the extension manager of an xpi + // item starts + installStartedCallback: null, + // If set will be called when an xpi fails to install. + installFailedCallback: null, + // If set will be called when each xpi item to be installed completes + // installation. + installEndedCallback: null, + // If set will be called when all triggered items are installed or the install + // is canceled. + installsCompletedCallback: null, + // If set the harness will wait for this DOM event before calling + // installsCompletedCallback + finalContentEvent: null, + + waitingForEvent: false, + pendingCount: null, + installCount: null, + runningInstalls: null, + + waitingForFinish: false, + + // A unique value to return from the installConfirmCallback to indicate that + // the install UI shouldn't be closed automatically + leaveOpen: {}, + + // Setup and tear down functions + setup: function() { + if (!this.waitingForFinish) { + waitForExplicitFinish(); + this.waitingForFinish = true; + + Services.prefs.setBoolPref(PREF_INSTALL_REQUIRESECUREORIGIN, false); + + Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); + Services.obs.addObserver(this, "addon-install-started", false); + Services.obs.addObserver(this, "addon-install-disabled", false); + Services.obs.addObserver(this, "addon-install-origin-blocked", false); + Services.obs.addObserver(this, "addon-install-blocked", false); + Services.obs.addObserver(this, "addon-install-failed", false); + Services.obs.addObserver(this, "addon-install-complete", false); + + AddonManager.addInstallListener(this); + + Services.wm.addListener(this); + + var self = this; + registerCleanupFunction(function() { + Services.prefs.clearUserPref(PREF_LOGGING_ENABLED); + Services.prefs.clearUserPref(PREF_INSTALL_REQUIRESECUREORIGIN); + Services.obs.removeObserver(self, "addon-install-started"); + Services.obs.removeObserver(self, "addon-install-disabled"); + Services.obs.removeObserver(self, "addon-install-origin-blocked"); + Services.obs.removeObserver(self, "addon-install-blocked"); + Services.obs.removeObserver(self, "addon-install-failed"); + Services.obs.removeObserver(self, "addon-install-complete"); + + AddonManager.removeInstallListener(self); + + Services.wm.removeListener(self); + + AddonManager.getAllInstalls(function(aInstalls) { + is(aInstalls.length, 0, "Should be no active installs at the end of the test"); + aInstalls.forEach(function(aInstall) { + info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state); + aInstall.cancel(); + }); + }); + }); + } + + this.installCount = 0; + this.pendingCount = 0; + this.runningInstalls = []; + }, + + finish: function() { + finish(); + }, + + endTest: function() { + let callback = this.installsCompletedCallback; + let count = this.installCount; + + is(this.runningInstalls.length, 0, "Should be no running installs left"); + this.runningInstalls.forEach(function(aInstall) { + info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state); + }); + + this.installOriginBlockedCallback = null; + this.installBlockedCallback = null; + this.authenticationCallback = null; + this.installConfirmCallback = null; + this.downloadStartedCallback = null; + this.downloadProgressCallback = null; + this.downloadCancelledCallback = null; + this.downloadFailedCallback = null; + this.downloadEndedCallback = null; + this.installStartedCallback = null; + this.installFailedCallback = null; + this.installEndedCallback = null; + this.installsCompletedCallback = null; + this.runningInstalls = null; + + if (callback) + executeSoon(() => callback(count)); + }, + + // Window open handling + windowReady: function(window) { + if (window.document.location.href == XPINSTALL_URL) { + if (this.installBlockedCallback) + ok(false, "Should have been blocked by the whitelist"); + this.pendingCount = window.document.getElementById("itemList").childNodes.length; + + // If there is a confirm callback then its return status determines whether + // to install the items or not. If not the test is over. + let result = true; + if (this.installConfirmCallback) { + result = this.installConfirmCallback(window); + if (result === this.leaveOpen) + return; + } + + if (!result) { + window.document.documentElement.cancelDialog(); + } + else { + // Initially the accept button is disabled on a countdown timer + var button = window.document.documentElement.getButton("accept"); + button.disabled = false; + window.document.documentElement.acceptDialog(); + } + } + else if (window.document.location.href == PROMPT_URL) { + var promptType = window.args.promptType; + switch (promptType) { + case "alert": + case "alertCheck": + case "confirmCheck": + case "confirm": + case "confirmEx": + window.document.documentElement.acceptDialog(); + break; + case "promptUserAndPass": + // This is a login dialog, hopefully an authentication prompt + // for the xpi. + if (this.authenticationCallback) { + var auth = this.authenticationCallback(); + if (auth && auth.length == 2) { + window.document.getElementById("loginTextbox").value = auth[0]; + window.document.getElementById("password1Textbox").value = auth[1]; + window.document.documentElement.acceptDialog(); + } + else { + window.document.documentElement.cancelDialog(); + } + } + else { + window.document.documentElement.cancelDialog(); + } + break; + default: + ok(false, "prompt type " + promptType + " not handled in test."); + break; + } + } + }, + + // Install blocked handling + + installDisabled: function(installInfo) { + ok(!!this.installDisabledCallback, "Installation shouldn't have been disabled"); + if (this.installDisabledCallback) + this.installDisabledCallback(installInfo); + this.expectingCancelled = true; + this.expectingCancelled = false; + this.endTest(); + }, + + installCancelled: function(installInfo) { + if (this.expectingCancelled) + return; + + ok(!!this.installCancelledCallback, "Installation shouldn't have been cancelled"); + if (this.installCancelledCallback) + this.installCancelledCallback(installInfo); + this.endTest(); + }, + + installOriginBlocked: function(installInfo) { + ok(!!this.installOriginBlockedCallback, "Shouldn't have been blocked"); + if (this.installOriginBlockedCallback) + this.installOriginBlockedCallback(installInfo); + this.endTest(); + }, + + installBlocked: function(installInfo) { + ok(!!this.installBlockedCallback, "Shouldn't have been blocked by the whitelist"); + if (this.installBlockedCallback && this.installBlockedCallback(installInfo)) { + this.installBlockedCallback = null; + installInfo.install(); + } + else { + this.expectingCancelled = true; + installInfo.installs.forEach(function(install) { + install.cancel(); + }); + this.expectingCancelled = false; + this.endTest(); + } + }, + + // nsIWindowMediatorListener + + onWindowTitleChange: function(window, title) { + }, + + onOpenWindow: function(window) { + var domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindow); + var self = this; + waitForFocus(function() { + self.windowReady(domwindow); + }, domwindow); + }, + + onCloseWindow: function(window) { + }, + + // Addon Install Listener + + onNewInstall: function(install) { + this.runningInstalls.push(install); + + if (this.finalContentEvent && !this.waitingForEvent) { + this.waitingForEvent = true; + info("Waiting for " + this.finalContentEvent); + let mm = gBrowser.selectedBrowser.messageManager; + mm.loadFrameScript(`data:,content.addEventListener("${this.finalContentEvent}", () => { sendAsyncMessage("Test:GotNewInstallEvent"); });`, false); + let win = gBrowser.contentWindow; + let listener = () => { + info("Saw " + this.finalContentEvent); + mm.removeMessageListener("Test:GotNewInstallEvent", listener); + this.waitingForEvent = false; + if (this.pendingCount == 0) + this.endTest(); + } + mm.addMessageListener("Test:GotNewInstallEvent", listener); + } + }, + + onDownloadStarted: function(install) { + this.pendingCount++; + if (this.downloadStartedCallback) + this.downloadStartedCallback(install); + }, + + onDownloadProgress: function(install) { + if (this.downloadProgressCallback) + this.downloadProgressCallback(install); + }, + + onDownloadEnded: function(install) { + if (this.downloadEndedCallback) + this.downloadEndedCallback(install); + }, + + onDownloadCancelled: function(install) { + isnot(this.runningInstalls.indexOf(install), -1, + "Should only see cancelations for started installs"); + this.runningInstalls.splice(this.runningInstalls.indexOf(install), 1); + + if (this.downloadCancelledCallback) + this.downloadCancelledCallback(install); + this.checkTestEnded(); + }, + + onDownloadFailed: function(install) { + if (this.downloadFailedCallback) + this.downloadFailedCallback(install); + this.checkTestEnded(); + }, + + onInstallStarted: function(install) { + if (this.installStartedCallback) + this.installStartedCallback(install); + }, + + onInstallEnded: function(install, addon) { + if (this.installEndedCallback) + this.installEndedCallback(install, addon); + this.installCount++; + this.checkTestEnded(); + }, + + onInstallFailed: function(install) { + if (this.installFailedCallback) + this.installFailedCallback(install); + this.checkTestEnded(); + }, + + checkTestEnded: function() { + if (--this.pendingCount == 0 && !this.waitingForEvent) + this.endTest(); + }, + + // nsIObserver + + observe: function(subject, topic, data) { + var installInfo = subject.QueryInterface(Components.interfaces.amIWebInstallInfo); + switch (topic) { + case "addon-install-started": + is(this.runningInstalls.length, installInfo.installs.length, + "Should have seen the expected number of installs started"); + break; + case "addon-install-disabled": + this.installDisabled(installInfo); + break; + case "addon-install-cancelled": + this.installCancelled(installInfo); + break; + case "addon-install-origin-blocked": + this.installOriginBlocked(installInfo); + break; + case "addon-install-blocked": + this.installBlocked(installInfo); + break; + case "addon-install-failed": + installInfo.installs.forEach(function(aInstall) { + isnot(this.runningInstalls.indexOf(aInstall), -1, + "Should only see failures for started installs"); + + ok(aInstall.error != 0 || aInstall.addon.appDisabled, + "Failed installs should have an error or be appDisabled"); + + this.runningInstalls.splice(this.runningInstalls.indexOf(aInstall), 1); + }, this); + break; + case "addon-install-complete": + installInfo.installs.forEach(function(aInstall) { + isnot(this.runningInstalls.indexOf(aInstall), -1, + "Should only see completed events for started installs"); + + is(aInstall.error, 0, "Completed installs should have no error"); + ok(!aInstall.appDisabled, "Completed installs should not be appDisabled"); + + // Complete installs are either in the INSTALLED or CANCELLED state + // since the test may cancel installs the moment they complete. + ok(aInstall.state == AddonManager.STATE_INSTALLED || + aInstall.state == AddonManager.STATE_CANCELLED, + "Completed installs should be in the right state"); + + this.runningInstalls.splice(this.runningInstalls.indexOf(aInstall), 1); + }, this); + break; + } + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsIWindowMediatorListener, + Ci.nsISupports]) +} diff --git a/toolkit/mozapps/webextensions/test/xpinstall/incompatible.xpi b/toolkit/mozapps/webextensions/test/xpinstall/incompatible.xpi new file mode 100644 index 000000000..262ed38a7 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/incompatible.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/installchrome.html b/toolkit/mozapps/webextensions/test/xpinstall/installchrome.html new file mode 100644 index 000000000..6abee2ef3 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/installchrome.html @@ -0,0 +1,22 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+ + diff --git a/toolkit/mozapps/webextensions/test/xpinstall/installtrigger.html b/toolkit/mozapps/webextensions/test/xpinstall/installtrigger.html new file mode 100644 index 000000000..65cab1ef1 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/installtrigger.html @@ -0,0 +1,44 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+

+

+ + diff --git a/toolkit/mozapps/webextensions/test/xpinstall/installtrigger_frame.html b/toolkit/mozapps/webextensions/test/xpinstall/installtrigger_frame.html new file mode 100644 index 000000000..2b302642e --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/installtrigger_frame.html @@ -0,0 +1,29 @@ + + + + + + + +InstallTrigger frame tests + + + + + + +

InstallTrigger tests

+

+

+ + diff --git a/toolkit/mozapps/webextensions/test/xpinstall/multipackage.xpi b/toolkit/mozapps/webextensions/test/xpinstall/multipackage.xpi new file mode 100644 index 000000000..d52f28c28 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/multipackage.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/navigate.html b/toolkit/mozapps/webextensions/test/xpinstall/navigate.html new file mode 100644 index 000000000..5a6903eb9 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/navigate.html @@ -0,0 +1,26 @@ + + + + + + + +Navigation tests + + + + +

Test Link

+ + diff --git a/toolkit/mozapps/webextensions/test/xpinstall/redirect.sjs b/toolkit/mozapps/webextensions/test/xpinstall/redirect.sjs new file mode 100644 index 000000000..d248bfbc7 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/redirect.sjs @@ -0,0 +1,45 @@ +// Script has two modes based on the query string. If the mode is "setup" then +// parameters from the query string configure the redirection. If the mode is +// "redirect" then a redirect is returned + +function handleRequest(request, response) +{ + let parts = request.queryString.split("&"); + let settings = {}; + + parts.forEach(function(aString) { + let [k, v] = aString.split("="); + settings[k] = decodeURIComponent(v); + }) + + if (settings.mode == "setup") { + delete settings.mode; + + // Object states must be an nsISupports + var state = { + settings: settings, + QueryInterface: function(aIid) { + if (aIid.equals(Components.interfaces.nsISupports)) + return settings; + throw Components.results.NS_ERROR_NO_INTERFACE; + } + } + state.wrappedJSObject = state; + + setObjectState("xpinstall-redirect-settings", state); + response.setStatusLine(request.httpVersion, 200, "Ok"); + response.setHeader("Content-Type", "text/plain"); + response.write("Setup complete"); + } + else if (settings.mode == "redirect") { + getObjectState("xpinstall-redirect-settings", function(aObject) { + settings = aObject.wrappedJSObject.settings; + }); + + response.setStatusLine(request.httpVersion, 302, "Found"); + for (var name in settings) { + response.setHeader(name, settings[name]); + } + response.write("Done"); + } +} diff --git a/toolkit/mozapps/webextensions/test/xpinstall/restartless-unsigned.xpi b/toolkit/mozapps/webextensions/test/xpinstall/restartless-unsigned.xpi new file mode 100644 index 000000000..8e76bd052 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/restartless-unsigned.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/restartless.xpi b/toolkit/mozapps/webextensions/test/xpinstall/restartless.xpi new file mode 100644 index 000000000..9fee8f60b Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/restartless.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/signed-multipackage.xpi b/toolkit/mozapps/webextensions/test/xpinstall/signed-multipackage.xpi new file mode 100644 index 000000000..11fbe1861 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/signed-multipackage.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/signed-no-cn.xpi b/toolkit/mozapps/webextensions/test/xpinstall/signed-no-cn.xpi new file mode 100644 index 000000000..90d3a3ce6 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/signed-no-cn.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/signed-no-o.xpi b/toolkit/mozapps/webextensions/test/xpinstall/signed-no-o.xpi new file mode 100644 index 000000000..19b754038 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/signed-no-o.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/signed-tampered.xpi b/toolkit/mozapps/webextensions/test/xpinstall/signed-tampered.xpi new file mode 100644 index 000000000..8c951881e Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/signed-tampered.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/signed-untrusted.xpi b/toolkit/mozapps/webextensions/test/xpinstall/signed-untrusted.xpi new file mode 100644 index 000000000..09789d189 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/signed-untrusted.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/signed.xpi b/toolkit/mozapps/webextensions/test/xpinstall/signed.xpi new file mode 100644 index 000000000..bd7f78b7c Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/signed.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/signed2.xpi b/toolkit/mozapps/webextensions/test/xpinstall/signed2.xpi new file mode 100644 index 000000000..085efbbf7 Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/signed2.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/slowinstall.sjs b/toolkit/mozapps/webextensions/test/xpinstall/slowinstall.sjs new file mode 100644 index 000000000..5f767a8f4 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/slowinstall.sjs @@ -0,0 +1,101 @@ +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +const RELATIVE_PATH = "browser/toolkit/mozapps/extensions/test/xpinstall" +const NOTIFICATION_TOPIC = "slowinstall-complete"; + +/** + * Helper function to create a JS object representing the url parameters from + * the request's queryString. + * + * @param aQueryString + * The request's query string. + * @return A JS object representing the url parameters from the request's + * queryString. + */ +function parseQueryString(aQueryString) { + var paramArray = aQueryString.split("&"); + var regex = /^([^=]+)=(.*)$/; + var params = {}; + for (var i = 0, sz = paramArray.length; i < sz; i++) { + var match = regex.exec(paramArray[i]); + if (!match) + throw "Bad parameter in queryString! '" + paramArray[i] + "'"; + params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]); + } + + return params; +} + +function handleRequest(aRequest, aResponse) { + let id = +getState("ID"); + setState("ID", "" + (id + 1)); + + function LOG(str) { + dump("slowinstall.sjs[" + id + "]: " + str + "\n"); + } + + aResponse.setStatusLine(aRequest.httpVersion, 200, "OK"); + + var params = { }; + if (aRequest.queryString) + params = parseQueryString(aRequest.queryString); + + if (params.file) { + let xpiFile = ""; + + function complete_download() { + LOG("Completing download"); + downloadPaused = false; + + try { + // Doesn't seem to be a sane way to read using OS.File and write to an + // nsIOutputStream so here we are. + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(xpiFile); + let stream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + stream.init(file, -1, -1, stream.DEFER_OPEN + stream.CLOSE_ON_EOF); + + NetUtil.asyncCopy(stream, aResponse.bodyOutputStream, () => { + LOG("Download complete"); + aResponse.finish(); + }); + } + catch (e) { + LOG("Exception " + e); + } + } + + let waitForComplete = new Promise(resolve => { + function complete() { + Services.obs.removeObserver(complete, NOTIFICATION_TOPIC); + resolve(); + } + + Services.obs.addObserver(complete, NOTIFICATION_TOPIC, false); + }); + + aResponse.processAsync(); + + OS.File.getCurrentDirectory().then(dir => { + xpiFile = OS.Path.join(dir, ...RELATIVE_PATH.split("/"), params.file); + LOG("Starting slow download of " + xpiFile); + + OS.File.stat(xpiFile).then(info => { + aResponse.setHeader("Content-Type", "binary/octet-stream"); + aResponse.setHeader("Content-Length", info.size.toString()); + + LOG("Download paused"); + waitForComplete.then(complete_download); + }); + }); + } + else if (params.continue) { + dump("slowinstall.sjs: Received signal to complete all current downloads.\n"); + Services.obs.notifyObservers(null, NOTIFICATION_TOPIC, null); + } +} diff --git a/toolkit/mozapps/webextensions/test/xpinstall/startsoftwareupdate.html b/toolkit/mozapps/webextensions/test/xpinstall/startsoftwareupdate.html new file mode 100644 index 000000000..50083ca90 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/startsoftwareupdate.html @@ -0,0 +1,20 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+ + diff --git a/toolkit/mozapps/webextensions/test/xpinstall/theme.xpi b/toolkit/mozapps/webextensions/test/xpinstall/theme.xpi new file mode 100644 index 000000000..74e650b4a Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/theme.xpi differ diff --git a/toolkit/mozapps/webextensions/test/xpinstall/triggerredirect.html b/toolkit/mozapps/webextensions/test/xpinstall/triggerredirect.html new file mode 100644 index 000000000..42e0e1cd0 --- /dev/null +++ b/toolkit/mozapps/webextensions/test/xpinstall/triggerredirect.html @@ -0,0 +1,36 @@ + + + + + + + +InstallTrigger tests + + + +

InstallTrigger tests

+

+

+ + diff --git a/toolkit/mozapps/webextensions/test/xpinstall/unsigned.xpi b/toolkit/mozapps/webextensions/test/xpinstall/unsigned.xpi new file mode 100644 index 000000000..51b00475a Binary files /dev/null and b/toolkit/mozapps/webextensions/test/xpinstall/unsigned.xpi differ diff --git a/toolkit/themes/linux/mozapps/extensions/category-available.png b/toolkit/themes/linux/mozapps/extensions/category-available.png deleted file mode 100644 index 689d526c9..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/category-available.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/category-discover.png b/toolkit/themes/linux/mozapps/extensions/category-discover.png deleted file mode 100644 index ccea27524..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/category-discover.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/category-plugins.png b/toolkit/themes/linux/mozapps/extensions/category-plugins.png deleted file mode 100644 index b253dd08f..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/category-plugins.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/category-recent.png b/toolkit/themes/linux/mozapps/extensions/category-recent.png deleted file mode 100644 index 9039b27aa..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/category-recent.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/category-search.png b/toolkit/themes/linux/mozapps/extensions/category-search.png deleted file mode 100644 index 52e91a7ce..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/category-search.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/category-service.png b/toolkit/themes/linux/mozapps/extensions/category-service.png deleted file mode 100644 index 997c8541c..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/category-service.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/dictionaryGeneric-16.png b/toolkit/themes/linux/mozapps/extensions/dictionaryGeneric-16.png deleted file mode 100644 index 08a0447a4..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/dictionaryGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/dictionaryGeneric.png b/toolkit/themes/linux/mozapps/extensions/dictionaryGeneric.png deleted file mode 100644 index a1e0d5359..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/dictionaryGeneric.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/extensionGeneric-16.png b/toolkit/themes/linux/mozapps/extensions/extensionGeneric-16.png deleted file mode 100644 index b1a2f3652..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/extensionGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/extensions.css b/toolkit/themes/linux/mozapps/extensions/extensions.css deleted file mode 100644 index a75669937..000000000 --- a/toolkit/themes/linux/mozapps/extensions/extensions.css +++ /dev/null @@ -1,42 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -%include ../../../shared/extensions/extensions.inc.css - -#header-utils-btn .toolbarbutton-icon { - list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities-native"); -} - -.sorter[checkState="1"] .button-icon { - display: -moz-box; - list-style-image: url("moz-icon://stock/gtk-sort-descending?size=16"); -} - -.sorter[checkState="2"] .button-icon { - display: -moz-box; - list-style-image: url("moz-icon://stock/gtk-sort-ascending?size=16"); -} - -.addon .relnotes-toggle { - list-style-image: url("moz-icon://stock/gtk-go-down?size=16"); -} - -.addon .relnotes-toggle > .button-box > .button-icon { - display: -moz-box; -} - -.addon[show-relnotes] .relnotes-toggle { - list-style-image: url("moz-icon://stock/gtk-go-up?size=16"); -} - -.meta-rating[showrating="average"] > .star { - list-style-image: url("chrome://mozapps/skin/extensions/rating-not-won.png"); - padding: 0 1px; -} - -.meta-rating > .star[on="true"], -.meta-rating[showrating="user"] > .star[hover] { - list-style-image: url("chrome://mozapps/skin/extensions/rating-won.png"); - padding: 0 1px; -} diff --git a/toolkit/themes/linux/mozapps/extensions/heart.png b/toolkit/themes/linux/mozapps/extensions/heart.png deleted file mode 100644 index 655f4c4be..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/heart.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/localeGeneric.png b/toolkit/themes/linux/mozapps/extensions/localeGeneric.png deleted file mode 100644 index c72115906..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/localeGeneric.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/newaddon.css b/toolkit/themes/linux/mozapps/extensions/newaddon.css deleted file mode 100644 index edfba0ef5..000000000 --- a/toolkit/themes/linux/mozapps/extensions/newaddon.css +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -%include ../../../shared/extensions/newaddon.inc.css diff --git a/toolkit/themes/linux/mozapps/extensions/themeGeneric-16.png b/toolkit/themes/linux/mozapps/extensions/themeGeneric-16.png deleted file mode 100644 index 019886fea..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/themeGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/extensions/themeGeneric.png b/toolkit/themes/linux/mozapps/extensions/themeGeneric.png deleted file mode 100644 index cde1c7834..000000000 Binary files a/toolkit/themes/linux/mozapps/extensions/themeGeneric.png and /dev/null differ diff --git a/toolkit/themes/linux/mozapps/jar.mn b/toolkit/themes/linux/mozapps/jar.mn index 8d1c12305..0965662bd 100644 --- a/toolkit/themes/linux/mozapps/jar.mn +++ b/toolkit/themes/linux/mozapps/jar.mn @@ -6,21 +6,23 @@ toolkit.jar: #include ../../shared/mozapps.inc.mn skin/classic/mozapps/downloads/downloadIcon.png (downloads/downloadIcon.png) skin/classic/mozapps/downloads/downloads.css (downloads/downloads.css) -* skin/classic/mozapps/extensions/extensions.css (extensions/extensions.css) - skin/classic/mozapps/extensions/category-search.png (extensions/category-search.png) - skin/classic/mozapps/extensions/category-discover.png (extensions/category-discover.png) - skin/classic/mozapps/extensions/category-plugins.png (extensions/category-plugins.png) - skin/classic/mozapps/extensions/category-service.png (extensions/category-service.png) - skin/classic/mozapps/extensions/category-recent.png (extensions/category-recent.png) - skin/classic/mozapps/extensions/category-available.png (extensions/category-available.png) - skin/classic/mozapps/extensions/extensionGeneric-16.png (extensions/extensionGeneric-16.png) - skin/classic/mozapps/extensions/dictionaryGeneric.png (extensions/dictionaryGeneric.png) - skin/classic/mozapps/extensions/dictionaryGeneric-16.png (extensions/dictionaryGeneric-16.png) - skin/classic/mozapps/extensions/themeGeneric.png (extensions/themeGeneric.png) - skin/classic/mozapps/extensions/themeGeneric-16.png (extensions/themeGeneric-16.png) - skin/classic/mozapps/extensions/localeGeneric.png (extensions/localeGeneric.png) -* skin/classic/mozapps/extensions/newaddon.css (extensions/newaddon.css) - skin/classic/mozapps/extensions/heart.png (extensions/heart.png) +#ifdef MOZ_WEBEXTENSIONS +* skin/classic/mozapps/extensions/extensions.css (webextensions/extensions.css) + skin/classic/mozapps/extensions/category-search.png (webextensions/category-search.png) + skin/classic/mozapps/extensions/category-discover.png (webextensions/category-discover.png) + skin/classic/mozapps/extensions/category-plugins.png (webextensions/category-plugins.png) + skin/classic/mozapps/extensions/category-service.png (webextensions/category-service.png) + skin/classic/mozapps/extensions/category-recent.png (webextensions/category-recent.png) + skin/classic/mozapps/extensions/category-available.png (webextensions/category-available.png) + skin/classic/mozapps/extensions/extensionGeneric-16.png (webextensions/extensionGeneric-16.png) + skin/classic/mozapps/extensions/dictionaryGeneric.png (webextensions/dictionaryGeneric.png) + skin/classic/mozapps/extensions/dictionaryGeneric-16.png (webextensions/dictionaryGeneric-16.png) + skin/classic/mozapps/extensions/themeGeneric.png (webextensions/themeGeneric.png) + skin/classic/mozapps/extensions/themeGeneric-16.png (webextensions/themeGeneric-16.png) + skin/classic/mozapps/extensions/localeGeneric.png (webextensions/localeGeneric.png) +* skin/classic/mozapps/extensions/newaddon.css (webextensions/newaddon.css) + skin/classic/mozapps/extensions/heart.png (webextensions/heart.png) +#endif skin/classic/mozapps/plugins/pluginGeneric.png (plugins/pluginGeneric.png) skin/classic/mozapps/plugins/pluginBlocked.png (plugins/pluginBlocked.png) skin/classic/mozapps/plugins/pluginGeneric-16.png (plugins/pluginGeneric-16.png) diff --git a/toolkit/themes/linux/mozapps/webextensions/category-available.png b/toolkit/themes/linux/mozapps/webextensions/category-available.png new file mode 100644 index 000000000..689d526c9 Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/category-available.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/category-discover.png b/toolkit/themes/linux/mozapps/webextensions/category-discover.png new file mode 100644 index 000000000..ccea27524 Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/category-discover.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/category-plugins.png b/toolkit/themes/linux/mozapps/webextensions/category-plugins.png new file mode 100644 index 000000000..b253dd08f Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/category-plugins.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/category-recent.png b/toolkit/themes/linux/mozapps/webextensions/category-recent.png new file mode 100644 index 000000000..9039b27aa Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/category-recent.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/category-search.png b/toolkit/themes/linux/mozapps/webextensions/category-search.png new file mode 100644 index 000000000..52e91a7ce Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/category-search.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/category-service.png b/toolkit/themes/linux/mozapps/webextensions/category-service.png new file mode 100644 index 000000000..997c8541c Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/category-service.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/dictionaryGeneric-16.png b/toolkit/themes/linux/mozapps/webextensions/dictionaryGeneric-16.png new file mode 100644 index 000000000..08a0447a4 Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/dictionaryGeneric-16.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/dictionaryGeneric.png b/toolkit/themes/linux/mozapps/webextensions/dictionaryGeneric.png new file mode 100644 index 000000000..a1e0d5359 Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/dictionaryGeneric.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/extensionGeneric-16.png b/toolkit/themes/linux/mozapps/webextensions/extensionGeneric-16.png new file mode 100644 index 000000000..b1a2f3652 Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/extensionGeneric-16.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/extensions.css b/toolkit/themes/linux/mozapps/webextensions/extensions.css new file mode 100644 index 000000000..5a11df119 --- /dev/null +++ b/toolkit/themes/linux/mozapps/webextensions/extensions.css @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +%include ../../../shared/webextensions/extensions.inc.css + +#header-utils-btn .toolbarbutton-icon { + list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities-native"); +} + +.sorter[checkState="1"] .button-icon { + display: -moz-box; + list-style-image: url("moz-icon://stock/gtk-sort-descending?size=16"); +} + +.sorter[checkState="2"] .button-icon { + display: -moz-box; + list-style-image: url("moz-icon://stock/gtk-sort-ascending?size=16"); +} + +.addon .relnotes-toggle { + list-style-image: url("moz-icon://stock/gtk-go-down?size=16"); +} + +.addon .relnotes-toggle > .button-box > .button-icon { + display: -moz-box; +} + +.addon[show-relnotes] .relnotes-toggle { + list-style-image: url("moz-icon://stock/gtk-go-up?size=16"); +} + +.meta-rating[showrating="average"] > .star { + list-style-image: url("chrome://mozapps/skin/extensions/rating-not-won.png"); + padding: 0 1px; +} + +.meta-rating > .star[on="true"], +.meta-rating[showrating="user"] > .star[hover] { + list-style-image: url("chrome://mozapps/skin/extensions/rating-won.png"); + padding: 0 1px; +} diff --git a/toolkit/themes/linux/mozapps/webextensions/heart.png b/toolkit/themes/linux/mozapps/webextensions/heart.png new file mode 100644 index 000000000..655f4c4be Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/heart.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/localeGeneric.png b/toolkit/themes/linux/mozapps/webextensions/localeGeneric.png new file mode 100644 index 000000000..c72115906 Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/localeGeneric.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/newaddon.css b/toolkit/themes/linux/mozapps/webextensions/newaddon.css new file mode 100644 index 000000000..5856c08b5 --- /dev/null +++ b/toolkit/themes/linux/mozapps/webextensions/newaddon.css @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +%include ../../../shared/webextensions/newaddon.inc.css diff --git a/toolkit/themes/linux/mozapps/webextensions/themeGeneric-16.png b/toolkit/themes/linux/mozapps/webextensions/themeGeneric-16.png new file mode 100644 index 000000000..019886fea Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/themeGeneric-16.png differ diff --git a/toolkit/themes/linux/mozapps/webextensions/themeGeneric.png b/toolkit/themes/linux/mozapps/webextensions/themeGeneric.png new file mode 100644 index 000000000..cde1c7834 Binary files /dev/null and b/toolkit/themes/linux/mozapps/webextensions/themeGeneric.png differ diff --git a/toolkit/themes/osx/mozapps/extensions/about.css b/toolkit/themes/osx/mozapps/extensions/about.css deleted file mode 100644 index c13ce5f59..000000000 --- a/toolkit/themes/osx/mozapps/extensions/about.css +++ /dev/null @@ -1,78 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#genericAbout { - padding: 0px; - min-height: 200px; - max-height: 400px; - width: 30em; -} - -#clientBox { - background-color: -moz-Dialog; - color: -moz-DialogText; -} - -.basic-info { - padding: 10px; -} - -#extensionIcon { - list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); - max-width: 64px; - max-height: 64px; - margin-inline-end: 6px; -} - -#genericAbout[addontype="theme"] #extensionIcon { - list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); -} - -#genericAbout[addontype="locale"] #extensionIcon { - list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); -} - -#genericAbout[addontype="plugin"] #extensionIcon { - list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); -} - -#genericAbout[addontype="dictionary"] #extensionIcon { - list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); -} - -#extensionName { - font-size: 200%; - font-weight: bolder; -} - -#extensionVersion { - font-weight: bold; -} - -#extensionDescription { - margin-top: 4px; -} - -#groove { - margin-top: 8px; -} - -#extensionDetailsBox { - overflow: auto; - min-height: 100px; -} - -.boxIndent { - margin-inline-start: 18px; -} - -#extensionCreator, .contributor { - margin: 0px; -} - -.sectionTitle { - padding: 2px 0px 3px 0px; - margin-top: 3px; - font-weight: bold; -} diff --git a/toolkit/themes/osx/mozapps/extensions/blocklist.css b/toolkit/themes/osx/mozapps/extensions/blocklist.css deleted file mode 100644 index 02f6e1d49..000000000 --- a/toolkit/themes/osx/mozapps/extensions/blocklist.css +++ /dev/null @@ -1,20 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -richlistitem { - padding-top: 6px; - padding-bottom: 6px; - padding-inline-start: 7px; - padding-inline-end: 7px; - border-bottom: 1px solid #C0C0C0; -} - -.addon-name-version { - font-size: 110%; -} - -.blockedLabel { - font-weight: bold; - font-style: italic; -} diff --git a/toolkit/themes/osx/mozapps/extensions/cancel.png b/toolkit/themes/osx/mozapps/extensions/cancel.png deleted file mode 100644 index 0d98ab235..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/cancel.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/category-available.png b/toolkit/themes/osx/mozapps/extensions/category-available.png deleted file mode 100644 index d1b737ab0..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/category-available.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/category-dictionaries.png b/toolkit/themes/osx/mozapps/extensions/category-dictionaries.png deleted file mode 100644 index 54ae4f93f..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/category-dictionaries.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/category-discover.png b/toolkit/themes/osx/mozapps/extensions/category-discover.png deleted file mode 100644 index a6f5b49b3..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/category-discover.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/category-experiments.png b/toolkit/themes/osx/mozapps/extensions/category-experiments.png deleted file mode 100644 index a9d00545e..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/category-experiments.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/category-plugins.png b/toolkit/themes/osx/mozapps/extensions/category-plugins.png deleted file mode 100644 index 5c4d8bf47..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/category-plugins.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/category-recent.png b/toolkit/themes/osx/mozapps/extensions/category-recent.png deleted file mode 100644 index 7ecfc7d4c..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/category-recent.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/category-search.png b/toolkit/themes/osx/mozapps/extensions/category-search.png deleted file mode 100644 index 52e91a7ce..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/category-search.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/category-service.png b/toolkit/themes/osx/mozapps/extensions/category-service.png deleted file mode 100644 index 997c8541c..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/category-service.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png b/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png deleted file mode 100644 index 4ad1a1a82..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png b/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png deleted file mode 100644 index 54ae4f93f..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/discover-logo.png b/toolkit/themes/osx/mozapps/extensions/discover-logo.png deleted file mode 100644 index cd50735a8..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/discover-logo.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/eula.css b/toolkit/themes/osx/mozapps/extensions/eula.css deleted file mode 100644 index 05aeb3c1c..000000000 --- a/toolkit/themes/osx/mozapps/extensions/eula.css +++ /dev/null @@ -1,47 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#icon { - list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); - max-width: 48px; - max-height: 48px; - margin-inline-end: 6px; -} - -#eula-dialog[addontype="theme"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); -} - -#eula-dialog[addontype="locale"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); -} - -#eula-dialog[addontype="plugin"] #icon { - list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); -} - -#eula-dialog[addontype="dictionary"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); -} - -#heading-container { - -moz-box-align: center; -} - -#heading { - font-size: 120%; -} - -#eula { - -moz-appearance: none; - color: -moz-FieldText; - background-color: -moz-Field; - margin: 1em; - border: 1px solid; - -moz-border-top-colors: ActiveBorder; - -moz-border-right-colors: ActiveBorder; - -moz-border-bottom-colors: ActiveBorder; - -moz-border-left-colors: ActiveBorder; -} - diff --git a/toolkit/themes/osx/mozapps/extensions/experimentGeneric.png b/toolkit/themes/osx/mozapps/extensions/experimentGeneric.png deleted file mode 100644 index a9d00545e..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/experimentGeneric.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.png b/toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.png deleted file mode 100644 index fc6c8a258..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/extensions.css b/toolkit/themes/osx/mozapps/extensions/extensions.css deleted file mode 100644 index d4dac2f01..000000000 --- a/toolkit/themes/osx/mozapps/extensions/extensions.css +++ /dev/null @@ -1,51 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -%include ../../../shared/extensions/extensions.inc.css - -.no-auto-hide > .menulist-dropmarker { - padding-inline-start: 0px !important; -} - -#header-utils-btn { - list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities"); - margin-inline-end: 18px; -} - -#header-utils-btn > .toolbarbutton-menu-dropmarker { - list-style-image: url("chrome://mozapps/skin/extensions/toolbarbutton-dropmarker.png"); - padding: 0; - margin-inline-start: 2px; -} - -.sorter[checkState="1"] { - list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif"); -} - -.sorter[checkState="2"] { - list-style-image: url("chrome://global/skin/arrow/arrow-up.gif"); -} - -.addon .relnotes-toggle { - -moz-box-direction: reverse; - list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif"); -} - -.addon[show-relnotes] .relnotes-toggle { - list-style-image: url("chrome://global/skin/arrow/arrow-up.gif"); -} - -.download-progress { - margin-top: 3px; - margin-bottom: 3px; -} - -.meta-rating > .star { - list-style-image: url("chrome://mozapps/skin/extensions/rating-not-won.png"); - padding: 0 1px; -} - -.meta-rating > .star[on="true"] { - list-style-image: url("chrome://mozapps/skin/extensions/rating-won.png"); -} diff --git a/toolkit/themes/osx/mozapps/extensions/heart.png b/toolkit/themes/osx/mozapps/extensions/heart.png deleted file mode 100644 index 655f4c4be..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/heart.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/localeGeneric.png b/toolkit/themes/osx/mozapps/extensions/localeGeneric.png deleted file mode 100644 index 4d9ac5ad8..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/localeGeneric.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/newaddon.css b/toolkit/themes/osx/mozapps/extensions/newaddon.css deleted file mode 100644 index edfba0ef5..000000000 --- a/toolkit/themes/osx/mozapps/extensions/newaddon.css +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -%include ../../../shared/extensions/newaddon.inc.css diff --git a/toolkit/themes/osx/mozapps/extensions/rating-not-won.png b/toolkit/themes/osx/mozapps/extensions/rating-not-won.png deleted file mode 100644 index 2761f1925..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/rating-not-won.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/rating-won.png b/toolkit/themes/osx/mozapps/extensions/rating-won.png deleted file mode 100644 index 336dd8f6e..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/rating-won.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/search.png b/toolkit/themes/osx/mozapps/extensions/search.png deleted file mode 100644 index 93196dbbf..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/search.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png b/toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png deleted file mode 100644 index 190bb30d7..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/themeGeneric.png b/toolkit/themes/osx/mozapps/extensions/themeGeneric.png deleted file mode 100644 index be645f76d..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/themeGeneric.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.png b/toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.png deleted file mode 100644 index e7674c62a..000000000 Binary files a/toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.png and /dev/null differ diff --git a/toolkit/themes/osx/mozapps/extensions/update.css b/toolkit/themes/osx/mozapps/extensions/update.css deleted file mode 100644 index a0a016868..000000000 --- a/toolkit/themes/osx/mozapps/extensions/update.css +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -.throbber { - list-style-image: url("chrome://global/skin/icons/loading.png"); - width: 16px; - height: 16px; - margin-top: 5px; - margin-bottom: 5px; - margin-inline-start: 5px; - margin-inline-end: 2px; -} - -@media (min-resolution: 2dppx) { - .throbber { - list-style-image: url("chrome://global/skin/icons/loading@2x.png"); - } -} - -.alertBox { - background-color: InfoBackground; - color: InfoText; - border: 1px outset InfoBackground; - margin-left: 3px; - margin-right: 3px; - padding: 5px; -} diff --git a/toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css b/toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css deleted file mode 100644 index 48af7c1a9..000000000 --- a/toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css +++ /dev/null @@ -1,90 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#xpinstallheader { - margin-bottom: 2em; -} - -.alert-icon { - width: 48px; - height: 48px; - list-style-image: url("chrome://global/skin/icons/warning-large.png"); - margin-top: 0 !important; - margin-bottom: 6px !important; - margin-inline-start: 6px !important; - margin-inline-end: 20px !important; -} - -#itemList { - -moz-appearance: listbox; - margin: 3px 4px 10px 4px; - height: 14em; -} - -#itemWarningIntro { - margin-inline-start: 8px; -} - -#dialogContentBox { - padding: 5px; -} - -installitem { - padding: 5px 0 5px 5px; - border-bottom: 1px dotted #C0C0C0; - margin-bottom: 5px; -} - -.warning { - font-weight: bold; - font-size: 1.25em; - margin-bottom: 1em; -} - -.xpinstallIconContainer { - width: 32px; - height: 32px; - margin-inline-end: 5px; -} - -.xpinstallItemName { - font-weight: bold; -} - -.xpinstallItemSigned { - font-style: italic; - font-size: 0.9em; -} - -.xpinstallItemURL { - -moz-appearance: none; - border: none; - background-color: Window; - margin-top: 2px; - margin-bottom: 1px; - margin-inline-start: 6px; - margin-inline-end: 5px; -} - -.xpinstallItemIcon { - max-width: 32px; - max-height: 32px; - list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); -} - -installitem[type="theme"] .xpinstallItemIcon { - list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); -} - -installitem[type="locale"] .xpinstallItemIcon { - list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); -} - -installitem[type="plugin"] .xpinstallItemIcon { - list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); -} - -installitem[type="dictionary"] .xpinstallItemIcon { - list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); -} diff --git a/toolkit/themes/osx/mozapps/jar.mn b/toolkit/themes/osx/mozapps/jar.mn index 3f5717073..8da83da16 100644 --- a/toolkit/themes/osx/mozapps/jar.mn +++ b/toolkit/themes/osx/mozapps/jar.mn @@ -8,34 +8,36 @@ toolkit.jar: skin/classic/mozapps/downloads/downloadIcon.png (downloads/downloadIcon.png) * skin/classic/mozapps/downloads/downloads.css (downloads/downloads.css) skin/classic/mozapps/downloads/unknownContentType.css (downloads/unknownContentType.css) - skin/classic/mozapps/extensions/category-search.png (extensions/category-search.png) - skin/classic/mozapps/extensions/category-discover.png (extensions/category-discover.png) - skin/classic/mozapps/extensions/category-plugins.png (extensions/category-plugins.png) - skin/classic/mozapps/extensions/category-service.png (extensions/category-service.png) - skin/classic/mozapps/extensions/category-dictionaries.png (extensions/category-dictionaries.png) - skin/classic/mozapps/extensions/category-experiments.png (extensions/category-experiments.png) - skin/classic/mozapps/extensions/category-recent.png (extensions/category-recent.png) - skin/classic/mozapps/extensions/category-available.png (extensions/category-available.png) - skin/classic/mozapps/extensions/discover-logo.png (extensions/discover-logo.png) - skin/classic/mozapps/extensions/extensionGeneric-16.png (extensions/extensionGeneric-16.png) - skin/classic/mozapps/extensions/themeGeneric.png (extensions/themeGeneric.png) - skin/classic/mozapps/extensions/themeGeneric-16.png (extensions/themeGeneric-16.png) - skin/classic/mozapps/extensions/dictionaryGeneric.png (extensions/dictionaryGeneric.png) - skin/classic/mozapps/extensions/dictionaryGeneric-16.png (extensions/dictionaryGeneric-16.png) - skin/classic/mozapps/extensions/experimentGeneric.png (extensions/experimentGeneric.png) - skin/classic/mozapps/extensions/localeGeneric.png (extensions/localeGeneric.png) - skin/classic/mozapps/extensions/rating-won.png (extensions/rating-won.png) - skin/classic/mozapps/extensions/rating-not-won.png (extensions/rating-not-won.png) - skin/classic/mozapps/extensions/cancel.png (extensions/cancel.png) - skin/classic/mozapps/extensions/toolbarbutton-dropmarker.png (extensions/toolbarbutton-dropmarker.png) - skin/classic/mozapps/extensions/heart.png (extensions/heart.png) - skin/classic/mozapps/extensions/search.png (extensions/search.png) - skin/classic/mozapps/extensions/about.css (extensions/about.css) -* skin/classic/mozapps/extensions/extensions.css (extensions/extensions.css) - skin/classic/mozapps/extensions/update.css (extensions/update.css) - skin/classic/mozapps/extensions/eula.css (extensions/eula.css) - skin/classic/mozapps/extensions/blocklist.css (extensions/blocklist.css) -* skin/classic/mozapps/extensions/newaddon.css (extensions/newaddon.css) +#ifdef MOZ_WEBEXTENSIONS + skin/classic/mozapps/extensions/category-search.png (webextensions/category-search.png) + skin/classic/mozapps/extensions/category-discover.png (webextensions/category-discover.png) + skin/classic/mozapps/extensions/category-plugins.png (webextensions/category-plugins.png) + skin/classic/mozapps/extensions/category-service.png (webextensions/category-service.png) + skin/classic/mozapps/extensions/category-dictionaries.png (webextensions/category-dictionaries.png) + skin/classic/mozapps/extensions/category-experiments.png (webextensions/category-experiments.png) + skin/classic/mozapps/extensions/category-recent.png (webextensions/category-recent.png) + skin/classic/mozapps/extensions/category-available.png (webextensions/category-available.png) + skin/classic/mozapps/extensions/discover-logo.png (webextensions/discover-logo.png) + skin/classic/mozapps/extensions/extensionGeneric-16.png (webextensions/extensionGeneric-16.png) + skin/classic/mozapps/extensions/themeGeneric.png (webextensions/themeGeneric.png) + skin/classic/mozapps/extensions/themeGeneric-16.png (webextensions/themeGeneric-16.png) + skin/classic/mozapps/extensions/dictionaryGeneric.png (webextensions/dictionaryGeneric.png) + skin/classic/mozapps/extensions/dictionaryGeneric-16.png (webextensions/dictionaryGeneric-16.png) + skin/classic/mozapps/extensions/experimentGeneric.png (webextensions/experimentGeneric.png) + skin/classic/mozapps/extensions/localeGeneric.png (webextensions/localeGeneric.png) + skin/classic/mozapps/extensions/rating-won.png (webextensions/rating-won.png) + skin/classic/mozapps/extensions/rating-not-won.png (webextensions/rating-not-won.png) + skin/classic/mozapps/extensions/cancel.png (webextensions/cancel.png) + skin/classic/mozapps/extensions/toolbarbutton-dropmarker.png (webextensions/toolbarbutton-dropmarker.png) + skin/classic/mozapps/extensions/heart.png (webextensions/heart.png) + skin/classic/mozapps/extensions/search.png (webextensions/search.png) + skin/classic/mozapps/extensions/about.css (webextensions/about.css) +* skin/classic/mozapps/extensions/extensions.css (webextensions/extensions.css) + skin/classic/mozapps/extensions/update.css (webextensions/update.css) + skin/classic/mozapps/extensions/eula.css (webextensions/eula.css) + skin/classic/mozapps/extensions/blocklist.css (webextensions/blocklist.css) +* skin/classic/mozapps/extensions/newaddon.css (webextensions/newaddon.css) +#endif skin/classic/mozapps/plugins/notifyPluginGeneric.png (plugins/notifyPluginGeneric.png) skin/classic/mozapps/plugins/pluginGeneric.png (plugins/pluginGeneric.png) skin/classic/mozapps/plugins/pluginBlocked.png (plugins/pluginBlocked.png) @@ -48,7 +50,9 @@ toolkit.jar: skin/classic/mozapps/update/buttons.png (update/buttons.png) * skin/classic/mozapps/update/updates.css (update/updates.css) skin/classic/mozapps/viewsource/viewsource.css (viewsource/viewsource.css) - skin/classic/mozapps/xpinstall/xpinstallConfirm.css (extensions/xpinstallConfirm.css) +#ifdef MOZ_WEBEXTENSIONS + skin/classic/mozapps/xpinstall/xpinstallConfirm.css (webextensions/xpinstallConfirm.css) +#endif skin/classic/mozapps/handling/handling.css (handling/handling.css) #ifdef MOZ_PHOENIX @@ -56,7 +60,9 @@ toolkit.jar: #elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: #endif +#ifdef MOZ_WEBEXTENSIONS % override chrome://mozapps/skin/extensions/category-extensions.svg chrome://mozapps/skin/extensions/extensionGeneric.svg % override chrome://mozapps/skin/extensions/category-languages.png chrome://mozapps/skin/extensions/localeGeneric.png % override chrome://mozapps/skin/extensions/category-themes.png chrome://mozapps/skin/extensions/themeGeneric.png +#endif % override chrome://mozapps/skin/plugins/notifyPluginCrashed.png chrome://mozapps/skin/plugins/notifyPluginGeneric.png diff --git a/toolkit/themes/osx/mozapps/webextensions/about.css b/toolkit/themes/osx/mozapps/webextensions/about.css new file mode 100644 index 000000000..c13ce5f59 --- /dev/null +++ b/toolkit/themes/osx/mozapps/webextensions/about.css @@ -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/. */ + +#genericAbout { + padding: 0px; + min-height: 200px; + max-height: 400px; + width: 30em; +} + +#clientBox { + background-color: -moz-Dialog; + color: -moz-DialogText; +} + +.basic-info { + padding: 10px; +} + +#extensionIcon { + list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); + max-width: 64px; + max-height: 64px; + margin-inline-end: 6px; +} + +#genericAbout[addontype="theme"] #extensionIcon { + list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); +} + +#genericAbout[addontype="locale"] #extensionIcon { + list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); +} + +#genericAbout[addontype="plugin"] #extensionIcon { + list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); +} + +#genericAbout[addontype="dictionary"] #extensionIcon { + list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); +} + +#extensionName { + font-size: 200%; + font-weight: bolder; +} + +#extensionVersion { + font-weight: bold; +} + +#extensionDescription { + margin-top: 4px; +} + +#groove { + margin-top: 8px; +} + +#extensionDetailsBox { + overflow: auto; + min-height: 100px; +} + +.boxIndent { + margin-inline-start: 18px; +} + +#extensionCreator, .contributor { + margin: 0px; +} + +.sectionTitle { + padding: 2px 0px 3px 0px; + margin-top: 3px; + font-weight: bold; +} diff --git a/toolkit/themes/osx/mozapps/webextensions/blocklist.css b/toolkit/themes/osx/mozapps/webextensions/blocklist.css new file mode 100644 index 000000000..02f6e1d49 --- /dev/null +++ b/toolkit/themes/osx/mozapps/webextensions/blocklist.css @@ -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/. */ + +richlistitem { + padding-top: 6px; + padding-bottom: 6px; + padding-inline-start: 7px; + padding-inline-end: 7px; + border-bottom: 1px solid #C0C0C0; +} + +.addon-name-version { + font-size: 110%; +} + +.blockedLabel { + font-weight: bold; + font-style: italic; +} diff --git a/toolkit/themes/osx/mozapps/webextensions/cancel.png b/toolkit/themes/osx/mozapps/webextensions/cancel.png new file mode 100644 index 000000000..0d98ab235 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/cancel.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/category-available.png b/toolkit/themes/osx/mozapps/webextensions/category-available.png new file mode 100644 index 000000000..d1b737ab0 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/category-available.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/category-dictionaries.png b/toolkit/themes/osx/mozapps/webextensions/category-dictionaries.png new file mode 100644 index 000000000..54ae4f93f Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/category-dictionaries.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/category-discover.png b/toolkit/themes/osx/mozapps/webextensions/category-discover.png new file mode 100644 index 000000000..a6f5b49b3 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/category-discover.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/category-experiments.png b/toolkit/themes/osx/mozapps/webextensions/category-experiments.png new file mode 100644 index 000000000..a9d00545e Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/category-experiments.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/category-plugins.png b/toolkit/themes/osx/mozapps/webextensions/category-plugins.png new file mode 100644 index 000000000..5c4d8bf47 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/category-plugins.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/category-recent.png b/toolkit/themes/osx/mozapps/webextensions/category-recent.png new file mode 100644 index 000000000..7ecfc7d4c Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/category-recent.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/category-search.png b/toolkit/themes/osx/mozapps/webextensions/category-search.png new file mode 100644 index 000000000..52e91a7ce Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/category-search.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/category-service.png b/toolkit/themes/osx/mozapps/webextensions/category-service.png new file mode 100644 index 000000000..997c8541c Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/category-service.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/dictionaryGeneric-16.png b/toolkit/themes/osx/mozapps/webextensions/dictionaryGeneric-16.png new file mode 100644 index 000000000..4ad1a1a82 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/dictionaryGeneric-16.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/dictionaryGeneric.png b/toolkit/themes/osx/mozapps/webextensions/dictionaryGeneric.png new file mode 100644 index 000000000..54ae4f93f Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/dictionaryGeneric.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/discover-logo.png b/toolkit/themes/osx/mozapps/webextensions/discover-logo.png new file mode 100644 index 000000000..cd50735a8 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/discover-logo.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/eula.css b/toolkit/themes/osx/mozapps/webextensions/eula.css new file mode 100644 index 000000000..05aeb3c1c --- /dev/null +++ b/toolkit/themes/osx/mozapps/webextensions/eula.css @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#icon { + list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); + max-width: 48px; + max-height: 48px; + margin-inline-end: 6px; +} + +#eula-dialog[addontype="theme"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); +} + +#eula-dialog[addontype="locale"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); +} + +#eula-dialog[addontype="plugin"] #icon { + list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); +} + +#eula-dialog[addontype="dictionary"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); +} + +#heading-container { + -moz-box-align: center; +} + +#heading { + font-size: 120%; +} + +#eula { + -moz-appearance: none; + color: -moz-FieldText; + background-color: -moz-Field; + margin: 1em; + border: 1px solid; + -moz-border-top-colors: ActiveBorder; + -moz-border-right-colors: ActiveBorder; + -moz-border-bottom-colors: ActiveBorder; + -moz-border-left-colors: ActiveBorder; +} + diff --git a/toolkit/themes/osx/mozapps/webextensions/experimentGeneric.png b/toolkit/themes/osx/mozapps/webextensions/experimentGeneric.png new file mode 100644 index 000000000..a9d00545e Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/experimentGeneric.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/extensionGeneric-16.png b/toolkit/themes/osx/mozapps/webextensions/extensionGeneric-16.png new file mode 100644 index 000000000..fc6c8a258 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/extensionGeneric-16.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/extensions.css b/toolkit/themes/osx/mozapps/webextensions/extensions.css new file mode 100644 index 000000000..cbdd9a5b0 --- /dev/null +++ b/toolkit/themes/osx/mozapps/webextensions/extensions.css @@ -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/. */ + +%include ../../../shared/webextensions/extensions.inc.css + +.no-auto-hide > .menulist-dropmarker { + padding-inline-start: 0px !important; +} + +#header-utils-btn { + list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities"); + margin-inline-end: 18px; +} + +#header-utils-btn > .toolbarbutton-menu-dropmarker { + list-style-image: url("chrome://mozapps/skin/extensions/toolbarbutton-dropmarker.png"); + padding: 0; + margin-inline-start: 2px; +} + +.sorter[checkState="1"] { + list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif"); +} + +.sorter[checkState="2"] { + list-style-image: url("chrome://global/skin/arrow/arrow-up.gif"); +} + +.addon .relnotes-toggle { + -moz-box-direction: reverse; + list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif"); +} + +.addon[show-relnotes] .relnotes-toggle { + list-style-image: url("chrome://global/skin/arrow/arrow-up.gif"); +} + +.download-progress { + margin-top: 3px; + margin-bottom: 3px; +} + +.meta-rating > .star { + list-style-image: url("chrome://mozapps/skin/extensions/rating-not-won.png"); + padding: 0 1px; +} + +.meta-rating > .star[on="true"] { + list-style-image: url("chrome://mozapps/skin/extensions/rating-won.png"); +} diff --git a/toolkit/themes/osx/mozapps/webextensions/heart.png b/toolkit/themes/osx/mozapps/webextensions/heart.png new file mode 100644 index 000000000..655f4c4be Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/heart.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/localeGeneric.png b/toolkit/themes/osx/mozapps/webextensions/localeGeneric.png new file mode 100644 index 000000000..4d9ac5ad8 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/localeGeneric.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/newaddon.css b/toolkit/themes/osx/mozapps/webextensions/newaddon.css new file mode 100644 index 000000000..5856c08b5 --- /dev/null +++ b/toolkit/themes/osx/mozapps/webextensions/newaddon.css @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +%include ../../../shared/webextensions/newaddon.inc.css diff --git a/toolkit/themes/osx/mozapps/webextensions/rating-not-won.png b/toolkit/themes/osx/mozapps/webextensions/rating-not-won.png new file mode 100644 index 000000000..2761f1925 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/rating-not-won.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/rating-won.png b/toolkit/themes/osx/mozapps/webextensions/rating-won.png new file mode 100644 index 000000000..336dd8f6e Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/rating-won.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/search.png b/toolkit/themes/osx/mozapps/webextensions/search.png new file mode 100644 index 000000000..93196dbbf Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/search.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/themeGeneric-16.png b/toolkit/themes/osx/mozapps/webextensions/themeGeneric-16.png new file mode 100644 index 000000000..190bb30d7 Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/themeGeneric-16.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/themeGeneric.png b/toolkit/themes/osx/mozapps/webextensions/themeGeneric.png new file mode 100644 index 000000000..be645f76d Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/themeGeneric.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/toolbarbutton-dropmarker.png b/toolkit/themes/osx/mozapps/webextensions/toolbarbutton-dropmarker.png new file mode 100644 index 000000000..e7674c62a Binary files /dev/null and b/toolkit/themes/osx/mozapps/webextensions/toolbarbutton-dropmarker.png differ diff --git a/toolkit/themes/osx/mozapps/webextensions/update.css b/toolkit/themes/osx/mozapps/webextensions/update.css new file mode 100644 index 000000000..a0a016868 --- /dev/null +++ b/toolkit/themes/osx/mozapps/webextensions/update.css @@ -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/. */ + +.throbber { + list-style-image: url("chrome://global/skin/icons/loading.png"); + width: 16px; + height: 16px; + margin-top: 5px; + margin-bottom: 5px; + margin-inline-start: 5px; + margin-inline-end: 2px; +} + +@media (min-resolution: 2dppx) { + .throbber { + list-style-image: url("chrome://global/skin/icons/loading@2x.png"); + } +} + +.alertBox { + background-color: InfoBackground; + color: InfoText; + border: 1px outset InfoBackground; + margin-left: 3px; + margin-right: 3px; + padding: 5px; +} diff --git a/toolkit/themes/osx/mozapps/webextensions/xpinstallConfirm.css b/toolkit/themes/osx/mozapps/webextensions/xpinstallConfirm.css new file mode 100644 index 000000000..48af7c1a9 --- /dev/null +++ b/toolkit/themes/osx/mozapps/webextensions/xpinstallConfirm.css @@ -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/. */ + +#xpinstallheader { + margin-bottom: 2em; +} + +.alert-icon { + width: 48px; + height: 48px; + list-style-image: url("chrome://global/skin/icons/warning-large.png"); + margin-top: 0 !important; + margin-bottom: 6px !important; + margin-inline-start: 6px !important; + margin-inline-end: 20px !important; +} + +#itemList { + -moz-appearance: listbox; + margin: 3px 4px 10px 4px; + height: 14em; +} + +#itemWarningIntro { + margin-inline-start: 8px; +} + +#dialogContentBox { + padding: 5px; +} + +installitem { + padding: 5px 0 5px 5px; + border-bottom: 1px dotted #C0C0C0; + margin-bottom: 5px; +} + +.warning { + font-weight: bold; + font-size: 1.25em; + margin-bottom: 1em; +} + +.xpinstallIconContainer { + width: 32px; + height: 32px; + margin-inline-end: 5px; +} + +.xpinstallItemName { + font-weight: bold; +} + +.xpinstallItemSigned { + font-style: italic; + font-size: 0.9em; +} + +.xpinstallItemURL { + -moz-appearance: none; + border: none; + background-color: Window; + margin-top: 2px; + margin-bottom: 1px; + margin-inline-start: 6px; + margin-inline-end: 5px; +} + +.xpinstallItemIcon { + max-width: 32px; + max-height: 32px; + list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); +} + +installitem[type="theme"] .xpinstallItemIcon { + list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); +} + +installitem[type="locale"] .xpinstallItemIcon { + list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); +} + +installitem[type="plugin"] .xpinstallItemIcon { + list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); +} + +installitem[type="dictionary"] .xpinstallItemIcon { + list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); +} diff --git a/toolkit/themes/shared/extensions/alerticon-error.svg b/toolkit/themes/shared/extensions/alerticon-error.svg deleted file mode 100644 index cb883e16e..000000000 --- a/toolkit/themes/shared/extensions/alerticon-error.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/toolkit/themes/shared/extensions/alerticon-info-negative.svg b/toolkit/themes/shared/extensions/alerticon-info-negative.svg deleted file mode 100644 index 733f8571a..000000000 --- a/toolkit/themes/shared/extensions/alerticon-info-negative.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/toolkit/themes/shared/extensions/alerticon-info-positive.svg b/toolkit/themes/shared/extensions/alerticon-info-positive.svg deleted file mode 100644 index 031190bce..000000000 --- a/toolkit/themes/shared/extensions/alerticon-info-positive.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/toolkit/themes/shared/extensions/alerticon-warning.svg b/toolkit/themes/shared/extensions/alerticon-warning.svg deleted file mode 100644 index 2b403220e..000000000 --- a/toolkit/themes/shared/extensions/alerticon-warning.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/toolkit/themes/shared/extensions/extensionGeneric.svg b/toolkit/themes/shared/extensions/extensionGeneric.svg deleted file mode 100644 index 28c2f7ba3..000000000 --- a/toolkit/themes/shared/extensions/extensionGeneric.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/toolkit/themes/shared/extensions/extensions.inc.css b/toolkit/themes/shared/extensions/extensions.inc.css deleted file mode 100644 index c4523bbe6..000000000 --- a/toolkit/themes/shared/extensions/extensions.inc.css +++ /dev/null @@ -1,1082 +0,0 @@ -%if 0 -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -%endif -@import url("chrome://global/skin/in-content/common.css"); - -.main-content { - padding: 0; -} - -#nav-header { - min-height: 39px; - background-color: #424f5a; -} - -.view-pane > .list > scrollbox { - padding-right: 48px; - padding-left: 48px; -} - - -/*** global warnings ***/ - -.global-warning-container { - overflow-x: hidden; -} - -.global-warning { - -moz-box-align: center; - padding: 0 8px; - color: #c8a91e; - font-weight: bold; -} - -#addons-page[warning] .global-warning-container { - background-image: linear-gradient(transparent, rgba(255, 255, 0, 0.1)); -} - -#detail-view .global-warning { - padding: 4px 12px; - border-bottom: 1px solid #c1c1c1; -} - -@media (max-width: 600px) { - .global-warning-text { - display: none; - } - - .global-warning .warning-icon { - background-color: #fff; - box-shadow: 0 0 2px 5px #fff; - border-radius: 10px; - } -} - -/*** global informations ***/ - -/* Plugins aren't yet disabled by safemode (bug 342333), - so don't show that warning when viewing plugins. */ -#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container, -#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning-container { - background-color: inherit; - background-image: none; -} - - -/*** notification icons ***/ - -.warning-icon, -.error-icon, -.pending-icon, -.info-icon { - width: 16px; - height: 16px; - margin: 3px 0; -} - -.warning-icon { - list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg"); -} - -.error-icon { - list-style-image: url("chrome://mozapps/skin/extensions/alerticon-error.svg"); -} - -.pending-icon, -.info-icon { - list-style-image: url("chrome://mozapps/skin/extensions/alerticon-info-positive.svg"); -} - -.addon-view[pending="disable"] .pending-icon, -.addon-view[pending="uninstall"] .pending-icon { - list-style-image: url("chrome://mozapps/skin/extensions/alerticon-info-negative.svg"); -} - -/*** view alert boxes ***/ - -.alert-container { - -moz-box-align: center; - margin-right: 48px; - margin-left: 48px; -} - -.alert-spacer-before { - -moz-box-flex: 1; -} - -.alert-spacer-after { - -moz-box-flex: 3; -} - -.alert { - -moz-box-align: center; - padding: 10px; - color: #333; - border: 1px solid #c1c1c1; - border-radius: 2px; - background-color: #ebebeb; -} - -.alert .alert-title { - font-weight: bold; - font-size: 200%; - margin-bottom: 15px; -} - -.alert button { - margin: 1em 2em; -} - -.loading { - list-style-image: url("chrome://global/skin/icons/loading.png"); - padding-left: 20px; - padding-right: 20px; -} - -@media (min-resolution: 1.1dppx) { - .loading > image { - width: 16px; - list-style-image: url("chrome://global/skin/icons/loading@2x.png"); - } -} - -button.warning { - list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg"); -} - - -/*** category selector ***/ - -#categories { - padding-top: 0; -} - -.category[selected="true"]:hover { - background-color:#1A2533; -} - -.category[disabled] { - overflow: hidden; - height: 0; - min-height: 0; - opacity: 0; - transition-property: min-height, opacity; - transition-duration: 1s, 0.8s; -} - -.category:not([disabled]) { - min-height: 40px; - transition-property: min-height, opacity; - transition-duration: 1s, 0.8s; -} - -/* Maximize the size of the viewport when the window is small */ -@media (max-width: 800px) { - .category-name { - display: none; - } -} - -.category-badge { - background-color: #55D4FF; - padding: 2px 8px; - margin: 6px 0; - margin-inline-start: 6px; - border-radius: 100%; - color: #FFF; - font-weight: bold; - text-align: center; -} - -.category-badge[value="0"] { - display: none; -} - -#category-search > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-search.png"); -} -#category-discover > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-discover.png"); -} -#category-locale > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-languages.png"); -} -#category-extension > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-extensions.svg"); -} -#category-service > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-service.png"); -} -#category-theme > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-themes.png"); -} -#category-plugin > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-plugins.png"); -} -#category-dictionary > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-dictionaries.png"); -} -#category-experiment > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-experiments.png"); -} -#category-availableUpdates > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-available.png"); -} -#category-recentUpdates > .category-icon { - list-style-image: url("chrome://mozapps/skin/extensions/category-recent.png"); -} - - -/*** header ***/ - -#header { - margin-top: 20px; - margin-bottom: 20px; - margin-right: 48px; - margin-left: 48px; -} - -@media (max-width: 600px) { - #header-search { - width: 12em; - } -} - -.view-header { - margin: 0 48px; - border-bottom: 1px solid #c1c1c1; -} - -#header-utils-btn { - height: 30px; - line-height: 20px; - border-color: #c1c1c1; - background-color: #fbfbfb; - padding-right: 10px; - padding-left: 10px; -} - -#header-utils-btn:not([disabled="true"]):active:hover, -#header-utils-btn[open="true"] { - background-color: #dadada; -} - -.header-button { - -moz-appearance: none; - border: 1px solid; - border-radius: 2px; -} - -.header-button[disabled="true"] > .toolbarbutton-icon { - opacity: 0.4; -} - -.header-button:not([disabled="true"]):hover, -#header-utils-btn:not([disabled="true"]):hover { - background-color: #ebebeb; - cursor: pointer; -} - -.header-button > .toolbarbutton-text { - display: none; -} - -.nav-button { - list-style-image: url(chrome://mozapps/skin/extensions/navigation.png); - margin-top: 15px; - margin-bottom: 15px; - border-color: transparent; -} - -.nav-button:not([disabled="true"]):hover { - border-color: #ebebeb; -} - -#back-btn:-moz-locale-dir(ltr), -#forward-btn:-moz-locale-dir(rtl) { - -moz-image-region: rect(0, 18px, 18px, 0); -} - -#back-btn:-moz-locale-dir(rtl), -#forward-btn:-moz-locale-dir(ltr) { - -moz-image-region: rect(0, 36px, 18px, 18px); -} - - -/*** sorters ***/ - -.sort-controls { - -moz-appearance: none; -} - -.sorter { - height: 35px; - border: none; - border-radius: 0; - background-color: transparent; - color: #536680; - margin: 0; - min-width: 12px !important; - -moz-box-direction: reverse; -} - -.sorter .button-box { - padding-top: 0; - padding-bottom: 0; -} - -.sorter[checkState="1"], -.sorter[checkState="2"] { - background-color: #ebebeb; - box-shadow: 0 -4px 0 0 #ff9500 inset; -} - -.sorter .button-icon { - margin-inline-start: 6px; -} - - -/*** discover view ***/ - -.discover-spacer-before, -.discover-spacer-after { - -moz-box-flex: 1; -} - -#discover-error .alert { - max-width: 45em; - -moz-box-flex: 1; -} - -.discover-logo { - list-style-image: url("chrome://mozapps/skin/extensions/discover-logo.png"); - margin-inline-end: 15px; -} - -.discover-title { - font-weight: bold; - font-size: 24px; - font-family: MetaWebPro-Book, "Trebuchet MS", sans-serif; - margin: 0 0 15px 0; -} - -.discover-description { - text-align: justify; - margin: 0 0 15px 0; -} - -.discover-footer { - text-align: justify; -} - - -/*** list ***/ - -.list { - -moz-appearance: none; - margin: 0; - border-width: 0 !important; - background-color: transparent; -} - -.list > scrollbox > .scrollbox-innerbox { - border: 1px dotted transparent; -} - -.list:-moz-focusring > scrollbox > .scrollbox-innerbox { - border-color: #0095dd; -} - -.addon { - color: #444; - border-bottom: 1px solid #c1c1c1; - padding: 5px; - background-origin: border-box; -} - -.addon:not(:only-child):last-child { - border-bottom-width: 0; -} - -.details { - cursor: pointer; - margin: 0; - margin-inline-start: 10px; -} - -.icon-container { - width: 48px; - height: 48px; - margin: 3px 7px; - -moz-box-align: center; - -moz-box-pack: center; -} - -.icon { - list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); - max-width: 32px; - max-height: 32px; -} - -.content-inner-container { - margin-inline-end: 5px; -} - -.addon[active="false"] .icon { - filter: grayscale(1); -} - -.addon-view[type="theme"] .icon { - list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); -} - -.addon-view[type="locale"] .icon { - list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); -} - -.addon-view[type="plugin"] .icon { - list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); -} - -.addon-view[type="dictionary"] .icon { - list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); -} - -.addon-view[type="experiment"] .icon { - list-style-image: url("chrome://mozapps/skin/extensions/experimentGeneric.png"); -} - -.name-container { - font-size: 1.3rem; - font-weight: bold; - -moz-box-align: end; - -moz-box-flex: 1; -} - -.creator { - font-weight: bold; -} - -.description-container { - margin-inline-start: 6px; - -moz-box-align: center; - font-size: 1.25rem; -} - -.description { - margin: 0; -} - -.warning, -.pending, -.error { - margin-inline-start: 48px; - font-weight: bold; - -moz-box-align: center; -} - -.content-container, -.basicinfo-container { - -moz-box-align: start; -} - -.addon[status="installing"] > .content-container { - -moz-box-align: stretch; -} - -.update-info-container { - -moz-box-align: center; -} - -.update-available { - -moz-box-align: end; -} - -.install-status-container { - -moz-box-pack: end; - -moz-box-align: end; -} - -.name-outer-container { - -moz-box-pack: center; -} - -.relnotes-toggle-container, -.icon-outer-container { - -moz-box-pack: start; -} - -.status-container, -.control-container { - -moz-box-pack: end; -} - -.addon-view .warning { - color: #d8b826; -} - -.addon-view .error { - color: #e62117; -} - -.addon-view .pending { - color: #62c44e; -} - -.addon-view[pending="disable"] .pending, -.addon-view[pending="uninstall"] .pending { - color: #898989; -} - -.addon .relnotes-container { - -moz-box-align: start; - margin-inline-start: 6px; - height: 0; - overflow: hidden; - opacity: 0; - transition-property: height, opacity; - transition-duration: 0.5s, 0.5s; -} - -.addon[show-relnotes] .relnotes-container { - opacity: 1; - transition-property: height, opacity; - transition-duration: 0.5s, 0.5s; -} - -.addon .relnotes-header { - font-weight: bold; - margin: 10px 0; -} - -.addon .relnotes-toggle { - -moz-appearance: none; - border: none; - background: transparent; - font-weight: bold; - cursor: pointer; -} - -.addon .relnotes-toggle > .button-box > .button-icon { - padding-inline-start: 4px; -} - -.addon-view[notification], -.addon-view[pending] { - --view-highlight-color: transparent; - background-image: radial-gradient(at 50% 0%, - var(--view-highlight-color) 0%, - transparent 75%); -} -.addon-view[notification="warning"] { - --view-highlight-color: #F9F5E5; -} - -.addon-view[notification="error"] { - --view-highlight-color: #FFE8E9; -} - -.addon-view[pending="enable"], -.addon-view[pending="upgrade"], -.addon-view[pending="install"] { - --view-highlight-color: #EFFAF2; -} - -.addon-view[pending="disable"], -.addon-view[pending="uninstall"] { - --view-highlight-color: #F2F2F2; -} - -.addon[selected] { - background-color: #fafafa; - color: #333; - padding-inline-start: 1px; /* compensate the 4px border */ - border-inline-start: solid 4px #ff9500; -} - -.addon[active="false"] > .content-container > .content-inner-container { - color: #999; -} - -.addon[active="false"][selected] > .content-container > .content-inner-container { - color: #777; -} - - -/*** item - uninstalled ***/ - -.addon[status="uninstalled"] { - border: none; -} - -.addon[status="uninstalled"] > .container { - -moz-box-align: center; - padding: 4px 20px; - background-color: #FDFFA8; - border-radius: 8px; - font-size: 120%; -} - -.addon[status="uninstalled"][selected] { - background-color: transparent; -} - - -/*** search view ***/ - -#search-filter { - padding: 5px 20px; - margin-right: 48px; - margin-left: 48px; - font-size: 120%; - border-bottom: 1px solid #c1c1c1; - overflow-x: hidden; -} - -#search-filter-label { - font-weight: bold; - color: grey; - margin-inline-end: 10px; -} - -#search-allresults-link { - margin-top: 1em; - margin-bottom: 2em; -} - - -/*** detail view ***/ - -#detail-view > .box-inherit { - margin-right: 48px; - margin-left: 48px; -} - -#detail-view .loading { - opacity: 0; -} - -#detail-view[loading-extended] .loading { - opacity: 1; - transition-property: opacity; - transition-duration: 1s; -} - -.detail-view-container { - padding-inline-end: 2em; - padding-bottom: 2em; - font-size: 1.25rem; - color: #333; -} - -#detail-notifications { - margin-top: 1em; - margin-bottom: 2em; -} - -#detail-notifications .warning, -#detail-notifications .pending, -#detail-notifications .error { - margin-inline-start: 0; -} - -#detail-icon-container { - width: 64px; - margin-inline-end: 10px; - margin-top: 6px; -} - -#detail-icon { - max-width: 64px; - max-height: 64px; -} - -#detail-summary { - margin-bottom: 2em; -} - -#detail-name-container { - font-size: 2.5rem; - font-weight: normal; -} - -#detail-screenshot-box { - margin-inline-end: 2em; - background-image: linear-gradient(rgba(255,255,255,.5), transparent); - background-color: white; - box-shadow: 0 1px 2px #666; - border-radius: 2px; -} - -#detail-screenshot { - max-width: 300px; - max-height: 300px; -} - -#detail-screenshot[loading] { - background-image: url("chrome://global/skin/icons/loading.png"); - background-position: 50% 50%; - background-repeat: no-repeat; -} - -@media (min-resolution: 1.1dppx) { - #detail-screenshot[loading] { - background-image: url("chrome://global/skin/icons/loading@2x.png"); - background-size: 16px; - } -} - -#detail-screenshot[loading="error"] { - background-image: url("chrome://global/skin/media/error.png"); -} - -#detail-desc-container { - margin-bottom: 2em; -} - -#detail-desc, #detail-fulldesc { - margin-inline-start: 6px; - /* This is necessary to fix layout issues with multi-line descriptions, see - bug 592712*/ - outline: solid transparent; - white-space: pre-wrap; - min-width: 10em; -} - -#detail-fulldesc { - margin-top: 1em; -} - -#detail-contributions { - border-radius: 2px; - border: 1px solid #D2DBE8; - margin-bottom: 2em; - padding: 1em; - background-color: #F3F7FB; -} - -#detail-contrib-description { - font-style: italic; - margin-bottom: 1em; - color: #373D48; -} - -#detail-contrib-suggested { - color: grey; - font-weight: bold; -} - -#detail-contrib-btn { - color: #FFF; - text-shadow: none; - border: 1px solid #0095dd; - list-style-image: url("chrome://mozapps/skin/extensions/heart.png"); - background-color: #0095dd; -} - -#detail-contrib-btn .button-icon { - margin-inline-end: 5px; -} - -#detail-contrib-btn:not(:active):hover { - border-color: #008acb; - background-color: #008acb; -} - -#detail-contrib-btn:active:hover { - background-color: #006b9d; - border-color: #006b9d; -} - -#detail-grid { - margin-bottom: 2em; -} - -#detail-grid > columns > column:first-child { - min-width: 15em; - max-width: 25em; -} - -.detail-row[first-row="true"], -.detail-row-complex[first-row="true"], -setting[first-row="true"] { - border-top: none; -} - -.detail-row, -.detail-row-complex, -setting { - border-top: 1px solid #c1c1c1; - -moz-box-align: center; - min-height: 35px; - line-height: 20px; - text-shadow: 0 1px 1px #fefffe; -} - -#detail-controls { - margin-bottom: 1em; -} - -.inline-options-browser, -setting[first-row="true"] { - margin-top: 2em; -} - -setting { - -moz-box-align: start; -} - -.preferences-alignment { - min-height: 30px; - -moz-box-align: center; -} - -.preferences-description { - font-size: 90.9%; - color: graytext; - margin-top: -2px; - margin-inline-start: 2em; - white-space: pre-wrap; -} - -.preferences-description:empty { - display: none; -} - -setting[type="radio"] > radiogroup { - -moz-box-orient: horizontal; -} - - -/*** creator ***/ - -.creator > label { - margin-inline-start: 0; - margin-inline-end: 0; -} - -.creator > .text-link { - margin-top: 1px; - margin-bottom: 1px; -} - - -/*** rating ***/ - -.meta-rating { - margin-inline-end: 0; - padding-top: 2px; -} - - -/*** download progress ***/ - -.download-progress { - border: 1px solid #c1c1c1; - border-radius: 2px; - background-color: #fbfbfb; - width: 200px; - height: 30px; - margin: 2px 4px; -} - -.download-progress[mode="undetermined"] { - border-color: #0095dd; -} - -.download-progress .start-cap, -.download-progress[complete] .end-cap, -.download-progress[mode="undetermined"] .end-cap, -.download-progress .progress .progress-bar { - -moz-appearance: none; - background-color: #0095dd; -} - -.download-progress .progress .progress-bar { - min-height: 28px; -} - -.download-progress .progress { - -moz-appearance: none; - background-color: transparent; - padding: 0; - margin: 0; - border: none; -} - -.download-progress .start-cap, -.download-progress .end-cap { - width: 4px; -} - -.download-progress .start-cap:-moz-locale-dir(ltr), -.download-progress .end-cap:-moz-locale-dir(rtl) { - border-radius: 1px 0 0 1px; -} - -.download-progress .end-cap:-moz-locale-dir(ltr), -.download-progress .start-cap:-moz-locale-dir(rtl) { - border-radius: 0 1px 1px 0; -} - -.download-progress .cancel { - -moz-appearance: none; - padding: 3px; - min-width: 0; - width: 20px; - height: 20px; - margin: 3px; -} - -.download-progress .cancel .button-box { - /* override in-content/common.css !important rule */ - padding: 0 !important; - border: none; -} - -.download-progress .cancel .button-text { - display: none; -} - -.download-progress .cancel .button-icon { - margin: 0; -} - -.download-progress .cancel { - list-style-image: url('chrome://mozapps/skin/extensions/cancel.png'); -} - -.download-progress .status-container { - -moz-box-align: center; -} - -.download-progress .status { - color: #333; - text-shadow: #fff 0 0 2px; -} - - -/*** install status ***/ - -.install-status { - -moz-box-align: center; -} - - -/*** check for updates ***/ - -#updates-container { - -moz-box-align: center; -} - -#updates-container .button-link { - font-weight: bold; -} - -#updates-installed, -#updates-downloaded { - color: #00BB00; - font-weight: bold; -} - -#update-selected { - margin: 12px; -} - - -/*** buttons ***/ - -.addon-control[disabled="true"]:not(.no-auto-hide) { - display: none; -} - -.no-auto-hide .addon-control { - display: block !important; -} - -button.button-link { - -moz-appearance: none; - background: transparent; - border: none; - box-shadow: none; - color: #0095dd; - cursor: pointer; - min-width: 0; - min-height: 20px; - margin: 0 6px; -} - -button.button-link:not(:-moz-focusring) > .button-box { - border-width: 0; - margin: 1px; -} - -button.button-link:hover { - background-color: transparent; - color: #178ce5; - text-decoration: underline; -} - -/* Needed to override normal button style from inContent.css */ -button.button-link:not([disabled="true"]):active:hover { - background-color: transparent; - color: #ff9500; - text-decoration: none; -} - - -/*** telemetry experiments ***/ - -#detail-experiment-container { - font-size: 80%; - margin-bottom: 1em; -} - -#detail-experiment-bullet-container, -#detail-experiment-state, -#detail-experiment-time, -.experiment-bullet-container, -.experiment-state, -.experiment-time { - vertical-align: middle; - display: inline-block; -} - -.addon .experiment-bullet, -#detail-experiment-bullet { - fill: rgb(158, 158, 158); -} - -.addon[active="true"] .experiment-bullet, -#detail-view[active="true"] #detail-experiment-bullet { - fill: rgb(106, 201, 20); -} - -/*** info UI for add-ons that have been disabled for being unsigned ***/ - -#show-disabled-unsigned-extensions:not(:hover) { - background-color: #fcf8ed; -} - -#disabled-unsigned-addons-info { - margin-bottom: 2em; - margin-right: 48px; - margin-left: 48px; -} - -#disabled-unsigned-addons-heading { - font-size: 1.4em; - font-weight: bold; - margin-bottom: .5em; -} - -#signing-dev-info { - font-style: italic; -} - -#detail-findUpdates-btn[hidden] { - display: -moz-box; - visibility: hidden; -} diff --git a/toolkit/themes/shared/extensions/navigation.png b/toolkit/themes/shared/extensions/navigation.png deleted file mode 100644 index 67ff3d9a7..000000000 Binary files a/toolkit/themes/shared/extensions/navigation.png and /dev/null differ diff --git a/toolkit/themes/shared/extensions/newaddon.inc.css b/toolkit/themes/shared/extensions/newaddon.inc.css deleted file mode 100644 index 52352ccd2..000000000 --- a/toolkit/themes/shared/extensions/newaddon.inc.css +++ /dev/null @@ -1,114 +0,0 @@ -%if 0 -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -%endif -@import url("chrome://global/skin/in-content/common.css"); - -#addon-page { - font-size: 1.1em; -} - -#addon-scrollbox { - overflow: auto; - -moz-box-orient: vertical; - -moz-box-flex: 1; -} - -#spacer-start { - -moz-box-flex: 1; -} - -#spacer-end { - -moz-box-flex: 3; -} - -#addon-container { - overflow: visible; - max-width: 800px; - margin: 20px; - padding: 30px 90px; -} - -#addon-info { - -moz-box-align: start; - margin: 25px 10px; -} - -#icon { - margin-top: 8px; - margin-inline-end: 10px; - max-width: 64px; - max-height: 64px; - list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); -} - -.addon-info[type="theme"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); -} - -.addon-info[type="locale"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); -} - -.addon-info[type="plugin"] #icon { - list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); -} - -.addon-info[type="dictionary"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); -} - -#name { - font-size: 130%; -} - -#author { - color: GrayText; -} - -#location { - color: GrayText; -} - -#warning { - margin-bottom: 25px; - -moz-box-align: start; -} - -#warning-icon { - list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg"); - width: 16px; - height: 16px; - margin-top: 5px; - margin-inline-end: 5px; -} - -#allow { - margin-inline-start: 84px; - margin-bottom: 20px; -} - -#buttonDeck { - margin-top: 25px; - -moz-box-align: stretch; -} - -#continuePanel { - -moz-box-pack: end; - -moz-box-align: end; -} - -#restartPanel { - -moz-box-pack: end; - -moz-box-align: stretch; -} - -#restartPanelButtons { - margin-top: 25px; - -moz-box-pack: end; -} - -#later { - color: GrayText; -} diff --git a/toolkit/themes/shared/extensions/utilities.svg b/toolkit/themes/shared/extensions/utilities.svg deleted file mode 100644 index 8bf24458c..000000000 --- a/toolkit/themes/shared/extensions/utilities.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - diff --git a/toolkit/themes/shared/mozapps.inc.mn b/toolkit/themes/shared/mozapps.inc.mn index bb43fd4d2..fadd203b0 100644 --- a/toolkit/themes/shared/mozapps.inc.mn +++ b/toolkit/themes/shared/mozapps.inc.mn @@ -7,13 +7,15 @@ # be specified once. As a result, the source file paths are relative # to the location of the actual manifest. - skin/classic/mozapps/extensions/extensionGeneric.svg (../../shared/extensions/extensionGeneric.svg) - skin/classic/mozapps/extensions/utilities.svg (../../shared/extensions/utilities.svg) - skin/classic/mozapps/extensions/navigation.png (../../shared/extensions/navigation.png) - skin/classic/mozapps/extensions/alerticon-warning.svg (../../shared/extensions/alerticon-warning.svg) - skin/classic/mozapps/extensions/alerticon-error.svg (../../shared/extensions/alerticon-error.svg) - skin/classic/mozapps/extensions/alerticon-info-positive.svg (../../shared/extensions/alerticon-info-positive.svg) - skin/classic/mozapps/extensions/alerticon-info-negative.svg (../../shared/extensions/alerticon-info-negative.svg) +#ifdef MOZ_WEBEXTENSIONS + skin/classic/mozapps/extensions/extensionGeneric.svg (../../shared/webextensions/extensionGeneric.svg) + skin/classic/mozapps/extensions/utilities.svg (../../shared/webextensions/utilities.svg) + skin/classic/mozapps/extensions/navigation.png (../../shared/webextensions/navigation.png) + skin/classic/mozapps/extensions/alerticon-warning.svg (../../shared/webextensions/alerticon-warning.svg) + skin/classic/mozapps/extensions/alerticon-error.svg (../../shared/webextensions/alerticon-error.svg) + skin/classic/mozapps/extensions/alerticon-info-positive.svg (../../shared/webextensions/alerticon-info-positive.svg) + skin/classic/mozapps/extensions/alerticon-info-negative.svg (../../shared/webextensions/alerticon-info-negative.svg) +#endif skin/classic/mozapps/formautofill/requestAutocomplete.css (../../shared/formautofill/requestAutocomplete.css) skin/classic/mozapps/plugins/pluginProblem.css (../../shared/plugins/pluginProblem.css) skin/classic/mozapps/aboutNetworking.css (../../shared/aboutNetworking.css) diff --git a/toolkit/themes/shared/non-mac.jar.inc.mn b/toolkit/themes/shared/non-mac.jar.inc.mn index 859f1ba4a..a783bbb3c 100644 --- a/toolkit/themes/shared/non-mac.jar.inc.mn +++ b/toolkit/themes/shared/non-mac.jar.inc.mn @@ -107,27 +107,32 @@ skin/classic/mozapps/downloads/downloadButtons.png (../../windows/mozapps/downloads/downloadButtons.png) skin/classic/mozapps/downloads/unknownContentType.css (../../windows/mozapps/downloads/unknownContentType.css) - skin/classic/mozapps/extensions/about.css (../../windows/mozapps/extensions/about.css) - skin/classic/mozapps/extensions/blocklist.css (../../windows/mozapps/extensions/blocklist.css) - skin/classic/mozapps/extensions/update.css (../../windows/mozapps/extensions/update.css) - skin/classic/mozapps/extensions/discover-logo.png (../../windows/mozapps/extensions/discover-logo.png) - skin/classic/mozapps/extensions/experimentGeneric.png (../../windows/mozapps/extensions/experimentGeneric.png) - skin/classic/mozapps/extensions/rating-won.png (../../windows/mozapps/extensions/rating-won.png) - skin/classic/mozapps/extensions/rating-not-won.png (../../windows/mozapps/extensions/rating-not-won.png) - skin/classic/mozapps/extensions/cancel.png (../../windows/mozapps/extensions/cancel.png) - skin/classic/mozapps/extensions/eula.css (../../windows/mozapps/extensions/eula.css) +#ifdef MOZ_WEBEXTENSIONS + skin/classic/mozapps/extensions/about.css (../../windows/mozapps/webextensions/about.css) + skin/classic/mozapps/extensions/blocklist.css (../../windows/mozapps/webextensions/blocklist.css) + skin/classic/mozapps/extensions/update.css (../../windows/mozapps/webextensions/update.css) + skin/classic/mozapps/extensions/discover-logo.png (../../windows/mozapps/webextensions/discover-logo.png) + skin/classic/mozapps/extensions/experimentGeneric.png (../../windows/mozapps/webextensions/experimentGeneric.png) + skin/classic/mozapps/extensions/rating-won.png (../../windows/mozapps/webextensions/rating-won.png) + skin/classic/mozapps/extensions/rating-not-won.png (../../windows/mozapps/webextensions/rating-not-won.png) + skin/classic/mozapps/extensions/cancel.png (../../windows/mozapps/webextensions/cancel.png) + skin/classic/mozapps/extensions/eula.css (../../windows/mozapps/webextensions/eula.css) +#endif skin/classic/mozapps/handling/handling.css (../../windows/mozapps/handling/handling.css) skin/classic/mozapps/plugins/pluginBlocked-64.png (../../windows/mozapps/plugins/pluginBlocked-64.png) skin/classic/mozapps/plugins/pluginHelp-16.png (../../windows/mozapps/plugins/pluginHelp-16.png) skin/classic/mozapps/profile/profileSelection.css (../../windows/mozapps/profile/profileSelection.css) skin/classic/mozapps/update/downloadButtons.png (../../windows/mozapps/update/downloadButtons.png) -* skin/classic/mozapps/xpinstall/xpinstallConfirm.css (../../windows/mozapps/extensions/xpinstallConfirm.css) +#ifdef MOZ_WEBEXTENSIONS +* skin/classic/mozapps/xpinstall/xpinstallConfirm.css (../../windows/mozapps/webextensions/xpinstallConfirm.css) +#endif #ifdef MOZ_PHOENIX [browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: #elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: #endif +#ifdef MOZ_WEBEXTENSIONS % override chrome://global/skin/arrow/arrow-lft-hov.gif chrome://global/skin/arrow/arrow-lft.gif % override chrome://global/skin/arrow/arrow-rit-hov.gif chrome://global/skin/arrow/arrow-rit.gif % override chrome://mozapps/skin/extensions/category-dictionaries.png chrome://mozapps/skin/extensions/dictionaryGeneric.png @@ -138,4 +143,4 @@ % override chrome://mozapps/skin/plugins/notifyPluginCrashed.png chrome://mozapps/skin/plugins/pluginGeneric-16.png % override chrome://mozapps/skin/plugins/notifyPluginGeneric.png chrome://mozapps/skin/plugins/pluginGeneric-16.png % override chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png chrome://mozapps/skin/extensions/extensionGeneric.png - +#endif diff --git a/toolkit/themes/shared/webextensions/alerticon-error.svg b/toolkit/themes/shared/webextensions/alerticon-error.svg new file mode 100644 index 000000000..cb883e16e --- /dev/null +++ b/toolkit/themes/shared/webextensions/alerticon-error.svg @@ -0,0 +1,6 @@ + + + + diff --git a/toolkit/themes/shared/webextensions/alerticon-info-negative.svg b/toolkit/themes/shared/webextensions/alerticon-info-negative.svg new file mode 100644 index 000000000..733f8571a --- /dev/null +++ b/toolkit/themes/shared/webextensions/alerticon-info-negative.svg @@ -0,0 +1,6 @@ + + + + diff --git a/toolkit/themes/shared/webextensions/alerticon-info-positive.svg b/toolkit/themes/shared/webextensions/alerticon-info-positive.svg new file mode 100644 index 000000000..031190bce --- /dev/null +++ b/toolkit/themes/shared/webextensions/alerticon-info-positive.svg @@ -0,0 +1,6 @@ + + + + diff --git a/toolkit/themes/shared/webextensions/alerticon-warning.svg b/toolkit/themes/shared/webextensions/alerticon-warning.svg new file mode 100644 index 000000000..2b403220e --- /dev/null +++ b/toolkit/themes/shared/webextensions/alerticon-warning.svg @@ -0,0 +1,6 @@ + + + + diff --git a/toolkit/themes/shared/webextensions/extensionGeneric.svg b/toolkit/themes/shared/webextensions/extensionGeneric.svg new file mode 100644 index 000000000..28c2f7ba3 --- /dev/null +++ b/toolkit/themes/shared/webextensions/extensionGeneric.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/toolkit/themes/shared/webextensions/extensions.inc.css b/toolkit/themes/shared/webextensions/extensions.inc.css new file mode 100644 index 000000000..c4523bbe6 --- /dev/null +++ b/toolkit/themes/shared/webextensions/extensions.inc.css @@ -0,0 +1,1082 @@ +%if 0 +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +%endif +@import url("chrome://global/skin/in-content/common.css"); + +.main-content { + padding: 0; +} + +#nav-header { + min-height: 39px; + background-color: #424f5a; +} + +.view-pane > .list > scrollbox { + padding-right: 48px; + padding-left: 48px; +} + + +/*** global warnings ***/ + +.global-warning-container { + overflow-x: hidden; +} + +.global-warning { + -moz-box-align: center; + padding: 0 8px; + color: #c8a91e; + font-weight: bold; +} + +#addons-page[warning] .global-warning-container { + background-image: linear-gradient(transparent, rgba(255, 255, 0, 0.1)); +} + +#detail-view .global-warning { + padding: 4px 12px; + border-bottom: 1px solid #c1c1c1; +} + +@media (max-width: 600px) { + .global-warning-text { + display: none; + } + + .global-warning .warning-icon { + background-color: #fff; + box-shadow: 0 0 2px 5px #fff; + border-radius: 10px; + } +} + +/*** global informations ***/ + +/* Plugins aren't yet disabled by safemode (bug 342333), + so don't show that warning when viewing plugins. */ +#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container, +#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning-container { + background-color: inherit; + background-image: none; +} + + +/*** notification icons ***/ + +.warning-icon, +.error-icon, +.pending-icon, +.info-icon { + width: 16px; + height: 16px; + margin: 3px 0; +} + +.warning-icon { + list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg"); +} + +.error-icon { + list-style-image: url("chrome://mozapps/skin/extensions/alerticon-error.svg"); +} + +.pending-icon, +.info-icon { + list-style-image: url("chrome://mozapps/skin/extensions/alerticon-info-positive.svg"); +} + +.addon-view[pending="disable"] .pending-icon, +.addon-view[pending="uninstall"] .pending-icon { + list-style-image: url("chrome://mozapps/skin/extensions/alerticon-info-negative.svg"); +} + +/*** view alert boxes ***/ + +.alert-container { + -moz-box-align: center; + margin-right: 48px; + margin-left: 48px; +} + +.alert-spacer-before { + -moz-box-flex: 1; +} + +.alert-spacer-after { + -moz-box-flex: 3; +} + +.alert { + -moz-box-align: center; + padding: 10px; + color: #333; + border: 1px solid #c1c1c1; + border-radius: 2px; + background-color: #ebebeb; +} + +.alert .alert-title { + font-weight: bold; + font-size: 200%; + margin-bottom: 15px; +} + +.alert button { + margin: 1em 2em; +} + +.loading { + list-style-image: url("chrome://global/skin/icons/loading.png"); + padding-left: 20px; + padding-right: 20px; +} + +@media (min-resolution: 1.1dppx) { + .loading > image { + width: 16px; + list-style-image: url("chrome://global/skin/icons/loading@2x.png"); + } +} + +button.warning { + list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg"); +} + + +/*** category selector ***/ + +#categories { + padding-top: 0; +} + +.category[selected="true"]:hover { + background-color:#1A2533; +} + +.category[disabled] { + overflow: hidden; + height: 0; + min-height: 0; + opacity: 0; + transition-property: min-height, opacity; + transition-duration: 1s, 0.8s; +} + +.category:not([disabled]) { + min-height: 40px; + transition-property: min-height, opacity; + transition-duration: 1s, 0.8s; +} + +/* Maximize the size of the viewport when the window is small */ +@media (max-width: 800px) { + .category-name { + display: none; + } +} + +.category-badge { + background-color: #55D4FF; + padding: 2px 8px; + margin: 6px 0; + margin-inline-start: 6px; + border-radius: 100%; + color: #FFF; + font-weight: bold; + text-align: center; +} + +.category-badge[value="0"] { + display: none; +} + +#category-search > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-search.png"); +} +#category-discover > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-discover.png"); +} +#category-locale > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-languages.png"); +} +#category-extension > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-extensions.svg"); +} +#category-service > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-service.png"); +} +#category-theme > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-themes.png"); +} +#category-plugin > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-plugins.png"); +} +#category-dictionary > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-dictionaries.png"); +} +#category-experiment > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-experiments.png"); +} +#category-availableUpdates > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-available.png"); +} +#category-recentUpdates > .category-icon { + list-style-image: url("chrome://mozapps/skin/extensions/category-recent.png"); +} + + +/*** header ***/ + +#header { + margin-top: 20px; + margin-bottom: 20px; + margin-right: 48px; + margin-left: 48px; +} + +@media (max-width: 600px) { + #header-search { + width: 12em; + } +} + +.view-header { + margin: 0 48px; + border-bottom: 1px solid #c1c1c1; +} + +#header-utils-btn { + height: 30px; + line-height: 20px; + border-color: #c1c1c1; + background-color: #fbfbfb; + padding-right: 10px; + padding-left: 10px; +} + +#header-utils-btn:not([disabled="true"]):active:hover, +#header-utils-btn[open="true"] { + background-color: #dadada; +} + +.header-button { + -moz-appearance: none; + border: 1px solid; + border-radius: 2px; +} + +.header-button[disabled="true"] > .toolbarbutton-icon { + opacity: 0.4; +} + +.header-button:not([disabled="true"]):hover, +#header-utils-btn:not([disabled="true"]):hover { + background-color: #ebebeb; + cursor: pointer; +} + +.header-button > .toolbarbutton-text { + display: none; +} + +.nav-button { + list-style-image: url(chrome://mozapps/skin/extensions/navigation.png); + margin-top: 15px; + margin-bottom: 15px; + border-color: transparent; +} + +.nav-button:not([disabled="true"]):hover { + border-color: #ebebeb; +} + +#back-btn:-moz-locale-dir(ltr), +#forward-btn:-moz-locale-dir(rtl) { + -moz-image-region: rect(0, 18px, 18px, 0); +} + +#back-btn:-moz-locale-dir(rtl), +#forward-btn:-moz-locale-dir(ltr) { + -moz-image-region: rect(0, 36px, 18px, 18px); +} + + +/*** sorters ***/ + +.sort-controls { + -moz-appearance: none; +} + +.sorter { + height: 35px; + border: none; + border-radius: 0; + background-color: transparent; + color: #536680; + margin: 0; + min-width: 12px !important; + -moz-box-direction: reverse; +} + +.sorter .button-box { + padding-top: 0; + padding-bottom: 0; +} + +.sorter[checkState="1"], +.sorter[checkState="2"] { + background-color: #ebebeb; + box-shadow: 0 -4px 0 0 #ff9500 inset; +} + +.sorter .button-icon { + margin-inline-start: 6px; +} + + +/*** discover view ***/ + +.discover-spacer-before, +.discover-spacer-after { + -moz-box-flex: 1; +} + +#discover-error .alert { + max-width: 45em; + -moz-box-flex: 1; +} + +.discover-logo { + list-style-image: url("chrome://mozapps/skin/extensions/discover-logo.png"); + margin-inline-end: 15px; +} + +.discover-title { + font-weight: bold; + font-size: 24px; + font-family: MetaWebPro-Book, "Trebuchet MS", sans-serif; + margin: 0 0 15px 0; +} + +.discover-description { + text-align: justify; + margin: 0 0 15px 0; +} + +.discover-footer { + text-align: justify; +} + + +/*** list ***/ + +.list { + -moz-appearance: none; + margin: 0; + border-width: 0 !important; + background-color: transparent; +} + +.list > scrollbox > .scrollbox-innerbox { + border: 1px dotted transparent; +} + +.list:-moz-focusring > scrollbox > .scrollbox-innerbox { + border-color: #0095dd; +} + +.addon { + color: #444; + border-bottom: 1px solid #c1c1c1; + padding: 5px; + background-origin: border-box; +} + +.addon:not(:only-child):last-child { + border-bottom-width: 0; +} + +.details { + cursor: pointer; + margin: 0; + margin-inline-start: 10px; +} + +.icon-container { + width: 48px; + height: 48px; + margin: 3px 7px; + -moz-box-align: center; + -moz-box-pack: center; +} + +.icon { + list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); + max-width: 32px; + max-height: 32px; +} + +.content-inner-container { + margin-inline-end: 5px; +} + +.addon[active="false"] .icon { + filter: grayscale(1); +} + +.addon-view[type="theme"] .icon { + list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); +} + +.addon-view[type="locale"] .icon { + list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); +} + +.addon-view[type="plugin"] .icon { + list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); +} + +.addon-view[type="dictionary"] .icon { + list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); +} + +.addon-view[type="experiment"] .icon { + list-style-image: url("chrome://mozapps/skin/extensions/experimentGeneric.png"); +} + +.name-container { + font-size: 1.3rem; + font-weight: bold; + -moz-box-align: end; + -moz-box-flex: 1; +} + +.creator { + font-weight: bold; +} + +.description-container { + margin-inline-start: 6px; + -moz-box-align: center; + font-size: 1.25rem; +} + +.description { + margin: 0; +} + +.warning, +.pending, +.error { + margin-inline-start: 48px; + font-weight: bold; + -moz-box-align: center; +} + +.content-container, +.basicinfo-container { + -moz-box-align: start; +} + +.addon[status="installing"] > .content-container { + -moz-box-align: stretch; +} + +.update-info-container { + -moz-box-align: center; +} + +.update-available { + -moz-box-align: end; +} + +.install-status-container { + -moz-box-pack: end; + -moz-box-align: end; +} + +.name-outer-container { + -moz-box-pack: center; +} + +.relnotes-toggle-container, +.icon-outer-container { + -moz-box-pack: start; +} + +.status-container, +.control-container { + -moz-box-pack: end; +} + +.addon-view .warning { + color: #d8b826; +} + +.addon-view .error { + color: #e62117; +} + +.addon-view .pending { + color: #62c44e; +} + +.addon-view[pending="disable"] .pending, +.addon-view[pending="uninstall"] .pending { + color: #898989; +} + +.addon .relnotes-container { + -moz-box-align: start; + margin-inline-start: 6px; + height: 0; + overflow: hidden; + opacity: 0; + transition-property: height, opacity; + transition-duration: 0.5s, 0.5s; +} + +.addon[show-relnotes] .relnotes-container { + opacity: 1; + transition-property: height, opacity; + transition-duration: 0.5s, 0.5s; +} + +.addon .relnotes-header { + font-weight: bold; + margin: 10px 0; +} + +.addon .relnotes-toggle { + -moz-appearance: none; + border: none; + background: transparent; + font-weight: bold; + cursor: pointer; +} + +.addon .relnotes-toggle > .button-box > .button-icon { + padding-inline-start: 4px; +} + +.addon-view[notification], +.addon-view[pending] { + --view-highlight-color: transparent; + background-image: radial-gradient(at 50% 0%, + var(--view-highlight-color) 0%, + transparent 75%); +} +.addon-view[notification="warning"] { + --view-highlight-color: #F9F5E5; +} + +.addon-view[notification="error"] { + --view-highlight-color: #FFE8E9; +} + +.addon-view[pending="enable"], +.addon-view[pending="upgrade"], +.addon-view[pending="install"] { + --view-highlight-color: #EFFAF2; +} + +.addon-view[pending="disable"], +.addon-view[pending="uninstall"] { + --view-highlight-color: #F2F2F2; +} + +.addon[selected] { + background-color: #fafafa; + color: #333; + padding-inline-start: 1px; /* compensate the 4px border */ + border-inline-start: solid 4px #ff9500; +} + +.addon[active="false"] > .content-container > .content-inner-container { + color: #999; +} + +.addon[active="false"][selected] > .content-container > .content-inner-container { + color: #777; +} + + +/*** item - uninstalled ***/ + +.addon[status="uninstalled"] { + border: none; +} + +.addon[status="uninstalled"] > .container { + -moz-box-align: center; + padding: 4px 20px; + background-color: #FDFFA8; + border-radius: 8px; + font-size: 120%; +} + +.addon[status="uninstalled"][selected] { + background-color: transparent; +} + + +/*** search view ***/ + +#search-filter { + padding: 5px 20px; + margin-right: 48px; + margin-left: 48px; + font-size: 120%; + border-bottom: 1px solid #c1c1c1; + overflow-x: hidden; +} + +#search-filter-label { + font-weight: bold; + color: grey; + margin-inline-end: 10px; +} + +#search-allresults-link { + margin-top: 1em; + margin-bottom: 2em; +} + + +/*** detail view ***/ + +#detail-view > .box-inherit { + margin-right: 48px; + margin-left: 48px; +} + +#detail-view .loading { + opacity: 0; +} + +#detail-view[loading-extended] .loading { + opacity: 1; + transition-property: opacity; + transition-duration: 1s; +} + +.detail-view-container { + padding-inline-end: 2em; + padding-bottom: 2em; + font-size: 1.25rem; + color: #333; +} + +#detail-notifications { + margin-top: 1em; + margin-bottom: 2em; +} + +#detail-notifications .warning, +#detail-notifications .pending, +#detail-notifications .error { + margin-inline-start: 0; +} + +#detail-icon-container { + width: 64px; + margin-inline-end: 10px; + margin-top: 6px; +} + +#detail-icon { + max-width: 64px; + max-height: 64px; +} + +#detail-summary { + margin-bottom: 2em; +} + +#detail-name-container { + font-size: 2.5rem; + font-weight: normal; +} + +#detail-screenshot-box { + margin-inline-end: 2em; + background-image: linear-gradient(rgba(255,255,255,.5), transparent); + background-color: white; + box-shadow: 0 1px 2px #666; + border-radius: 2px; +} + +#detail-screenshot { + max-width: 300px; + max-height: 300px; +} + +#detail-screenshot[loading] { + background-image: url("chrome://global/skin/icons/loading.png"); + background-position: 50% 50%; + background-repeat: no-repeat; +} + +@media (min-resolution: 1.1dppx) { + #detail-screenshot[loading] { + background-image: url("chrome://global/skin/icons/loading@2x.png"); + background-size: 16px; + } +} + +#detail-screenshot[loading="error"] { + background-image: url("chrome://global/skin/media/error.png"); +} + +#detail-desc-container { + margin-bottom: 2em; +} + +#detail-desc, #detail-fulldesc { + margin-inline-start: 6px; + /* This is necessary to fix layout issues with multi-line descriptions, see + bug 592712*/ + outline: solid transparent; + white-space: pre-wrap; + min-width: 10em; +} + +#detail-fulldesc { + margin-top: 1em; +} + +#detail-contributions { + border-radius: 2px; + border: 1px solid #D2DBE8; + margin-bottom: 2em; + padding: 1em; + background-color: #F3F7FB; +} + +#detail-contrib-description { + font-style: italic; + margin-bottom: 1em; + color: #373D48; +} + +#detail-contrib-suggested { + color: grey; + font-weight: bold; +} + +#detail-contrib-btn { + color: #FFF; + text-shadow: none; + border: 1px solid #0095dd; + list-style-image: url("chrome://mozapps/skin/extensions/heart.png"); + background-color: #0095dd; +} + +#detail-contrib-btn .button-icon { + margin-inline-end: 5px; +} + +#detail-contrib-btn:not(:active):hover { + border-color: #008acb; + background-color: #008acb; +} + +#detail-contrib-btn:active:hover { + background-color: #006b9d; + border-color: #006b9d; +} + +#detail-grid { + margin-bottom: 2em; +} + +#detail-grid > columns > column:first-child { + min-width: 15em; + max-width: 25em; +} + +.detail-row[first-row="true"], +.detail-row-complex[first-row="true"], +setting[first-row="true"] { + border-top: none; +} + +.detail-row, +.detail-row-complex, +setting { + border-top: 1px solid #c1c1c1; + -moz-box-align: center; + min-height: 35px; + line-height: 20px; + text-shadow: 0 1px 1px #fefffe; +} + +#detail-controls { + margin-bottom: 1em; +} + +.inline-options-browser, +setting[first-row="true"] { + margin-top: 2em; +} + +setting { + -moz-box-align: start; +} + +.preferences-alignment { + min-height: 30px; + -moz-box-align: center; +} + +.preferences-description { + font-size: 90.9%; + color: graytext; + margin-top: -2px; + margin-inline-start: 2em; + white-space: pre-wrap; +} + +.preferences-description:empty { + display: none; +} + +setting[type="radio"] > radiogroup { + -moz-box-orient: horizontal; +} + + +/*** creator ***/ + +.creator > label { + margin-inline-start: 0; + margin-inline-end: 0; +} + +.creator > .text-link { + margin-top: 1px; + margin-bottom: 1px; +} + + +/*** rating ***/ + +.meta-rating { + margin-inline-end: 0; + padding-top: 2px; +} + + +/*** download progress ***/ + +.download-progress { + border: 1px solid #c1c1c1; + border-radius: 2px; + background-color: #fbfbfb; + width: 200px; + height: 30px; + margin: 2px 4px; +} + +.download-progress[mode="undetermined"] { + border-color: #0095dd; +} + +.download-progress .start-cap, +.download-progress[complete] .end-cap, +.download-progress[mode="undetermined"] .end-cap, +.download-progress .progress .progress-bar { + -moz-appearance: none; + background-color: #0095dd; +} + +.download-progress .progress .progress-bar { + min-height: 28px; +} + +.download-progress .progress { + -moz-appearance: none; + background-color: transparent; + padding: 0; + margin: 0; + border: none; +} + +.download-progress .start-cap, +.download-progress .end-cap { + width: 4px; +} + +.download-progress .start-cap:-moz-locale-dir(ltr), +.download-progress .end-cap:-moz-locale-dir(rtl) { + border-radius: 1px 0 0 1px; +} + +.download-progress .end-cap:-moz-locale-dir(ltr), +.download-progress .start-cap:-moz-locale-dir(rtl) { + border-radius: 0 1px 1px 0; +} + +.download-progress .cancel { + -moz-appearance: none; + padding: 3px; + min-width: 0; + width: 20px; + height: 20px; + margin: 3px; +} + +.download-progress .cancel .button-box { + /* override in-content/common.css !important rule */ + padding: 0 !important; + border: none; +} + +.download-progress .cancel .button-text { + display: none; +} + +.download-progress .cancel .button-icon { + margin: 0; +} + +.download-progress .cancel { + list-style-image: url('chrome://mozapps/skin/extensions/cancel.png'); +} + +.download-progress .status-container { + -moz-box-align: center; +} + +.download-progress .status { + color: #333; + text-shadow: #fff 0 0 2px; +} + + +/*** install status ***/ + +.install-status { + -moz-box-align: center; +} + + +/*** check for updates ***/ + +#updates-container { + -moz-box-align: center; +} + +#updates-container .button-link { + font-weight: bold; +} + +#updates-installed, +#updates-downloaded { + color: #00BB00; + font-weight: bold; +} + +#update-selected { + margin: 12px; +} + + +/*** buttons ***/ + +.addon-control[disabled="true"]:not(.no-auto-hide) { + display: none; +} + +.no-auto-hide .addon-control { + display: block !important; +} + +button.button-link { + -moz-appearance: none; + background: transparent; + border: none; + box-shadow: none; + color: #0095dd; + cursor: pointer; + min-width: 0; + min-height: 20px; + margin: 0 6px; +} + +button.button-link:not(:-moz-focusring) > .button-box { + border-width: 0; + margin: 1px; +} + +button.button-link:hover { + background-color: transparent; + color: #178ce5; + text-decoration: underline; +} + +/* Needed to override normal button style from inContent.css */ +button.button-link:not([disabled="true"]):active:hover { + background-color: transparent; + color: #ff9500; + text-decoration: none; +} + + +/*** telemetry experiments ***/ + +#detail-experiment-container { + font-size: 80%; + margin-bottom: 1em; +} + +#detail-experiment-bullet-container, +#detail-experiment-state, +#detail-experiment-time, +.experiment-bullet-container, +.experiment-state, +.experiment-time { + vertical-align: middle; + display: inline-block; +} + +.addon .experiment-bullet, +#detail-experiment-bullet { + fill: rgb(158, 158, 158); +} + +.addon[active="true"] .experiment-bullet, +#detail-view[active="true"] #detail-experiment-bullet { + fill: rgb(106, 201, 20); +} + +/*** info UI for add-ons that have been disabled for being unsigned ***/ + +#show-disabled-unsigned-extensions:not(:hover) { + background-color: #fcf8ed; +} + +#disabled-unsigned-addons-info { + margin-bottom: 2em; + margin-right: 48px; + margin-left: 48px; +} + +#disabled-unsigned-addons-heading { + font-size: 1.4em; + font-weight: bold; + margin-bottom: .5em; +} + +#signing-dev-info { + font-style: italic; +} + +#detail-findUpdates-btn[hidden] { + display: -moz-box; + visibility: hidden; +} diff --git a/toolkit/themes/shared/webextensions/navigation.png b/toolkit/themes/shared/webextensions/navigation.png new file mode 100644 index 000000000..67ff3d9a7 Binary files /dev/null and b/toolkit/themes/shared/webextensions/navigation.png differ diff --git a/toolkit/themes/shared/webextensions/newaddon.inc.css b/toolkit/themes/shared/webextensions/newaddon.inc.css new file mode 100644 index 000000000..52352ccd2 --- /dev/null +++ b/toolkit/themes/shared/webextensions/newaddon.inc.css @@ -0,0 +1,114 @@ +%if 0 +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +%endif +@import url("chrome://global/skin/in-content/common.css"); + +#addon-page { + font-size: 1.1em; +} + +#addon-scrollbox { + overflow: auto; + -moz-box-orient: vertical; + -moz-box-flex: 1; +} + +#spacer-start { + -moz-box-flex: 1; +} + +#spacer-end { + -moz-box-flex: 3; +} + +#addon-container { + overflow: visible; + max-width: 800px; + margin: 20px; + padding: 30px 90px; +} + +#addon-info { + -moz-box-align: start; + margin: 25px 10px; +} + +#icon { + margin-top: 8px; + margin-inline-end: 10px; + max-width: 64px; + max-height: 64px; + list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); +} + +.addon-info[type="theme"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); +} + +.addon-info[type="locale"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); +} + +.addon-info[type="plugin"] #icon { + list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); +} + +.addon-info[type="dictionary"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); +} + +#name { + font-size: 130%; +} + +#author { + color: GrayText; +} + +#location { + color: GrayText; +} + +#warning { + margin-bottom: 25px; + -moz-box-align: start; +} + +#warning-icon { + list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg"); + width: 16px; + height: 16px; + margin-top: 5px; + margin-inline-end: 5px; +} + +#allow { + margin-inline-start: 84px; + margin-bottom: 20px; +} + +#buttonDeck { + margin-top: 25px; + -moz-box-align: stretch; +} + +#continuePanel { + -moz-box-pack: end; + -moz-box-align: end; +} + +#restartPanel { + -moz-box-pack: end; + -moz-box-align: stretch; +} + +#restartPanelButtons { + margin-top: 25px; + -moz-box-pack: end; +} + +#later { + color: GrayText; +} diff --git a/toolkit/themes/shared/webextensions/utilities.svg b/toolkit/themes/shared/webextensions/utilities.svg new file mode 100644 index 000000000..8bf24458c --- /dev/null +++ b/toolkit/themes/shared/webextensions/utilities.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/toolkit/themes/windows/mozapps/extensions/about.css b/toolkit/themes/windows/mozapps/extensions/about.css deleted file mode 100644 index 19eaddca8..000000000 --- a/toolkit/themes/windows/mozapps/extensions/about.css +++ /dev/null @@ -1,91 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#genericAbout { - padding: 0px; - min-height: 200px; - max-height: 400px; - width: 30em; -} - -#clientBox { - background-color: -moz-Dialog; - color: -moz-DialogText; -} - -@media (-moz-windows-compositor) { - #genericAbout { - -moz-appearance: -moz-win-glass; - background: transparent; - } - - #clientBox { - -moz-appearance: -moz-win-exclude-glass; - } -} - - -.basic-info { - padding: 10px; -} - -#extensionIcon { - list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); - max-width: 64px; - max-height: 64px; - margin-inline-end: 6px; -} - -#genericAbout[addontype="theme"] #extensionIcon { - list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); -} - -#genericAbout[addontype="locale"] #extensionIcon { - list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); -} - -#genericAbout[addontype="plugin"] #extensionIcon { - list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); -} - -#genericAbout[addontype="dictionary"] #extensionIcon { - list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); -} - -#extensionName { - font-size: 200%; - font-weight: bolder; -} - -#extensionVersion { - font-weight: bold; -} - -#extensionDescription { - margin-top: 4px; -} - -#groove { - margin-top: 8px; -} - -#extensionDetailsBox { - overflow: auto; - min-height: 100px; -} - -.boxIndent { - margin-inline-start: 18px; -} - -#extensionCreator, .contributor { - margin: 0px; -} - -.sectionTitle { - padding: 2px 0px 3px 0px; - margin-top: 3px; - font-weight: bold; -} - diff --git a/toolkit/themes/windows/mozapps/extensions/blocklist.css b/toolkit/themes/windows/mozapps/extensions/blocklist.css deleted file mode 100644 index 1cdbb35ac..000000000 --- a/toolkit/themes/windows/mozapps/extensions/blocklist.css +++ /dev/null @@ -1,20 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -richlistitem { - padding-top: 6px; - padding-bottom: 6px; - padding-inline-start: 7px; - padding-inline-end: 7px; - border-bottom: 1px solid #C0C0C0; -} - -.addonName { - font-weight: bold; -} - -.blockedLabel { - font-weight: bold; - font-style: italic; -} diff --git a/toolkit/themes/windows/mozapps/extensions/cancel.png b/toolkit/themes/windows/mozapps/extensions/cancel.png deleted file mode 100644 index 0d98ab235..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/cancel.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-available-XP.png b/toolkit/themes/windows/mozapps/extensions/category-available-XP.png deleted file mode 100644 index d1b737ab0..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-available-XP.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-available.png b/toolkit/themes/windows/mozapps/extensions/category-available.png deleted file mode 100644 index 9341f2aa7..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-available.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-discover-XP.png b/toolkit/themes/windows/mozapps/extensions/category-discover-XP.png deleted file mode 100644 index a6f5b49b3..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-discover-XP.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-discover.png b/toolkit/themes/windows/mozapps/extensions/category-discover.png deleted file mode 100644 index af954a613..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-discover.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-plugins-XP.png b/toolkit/themes/windows/mozapps/extensions/category-plugins-XP.png deleted file mode 100644 index 5c4d8bf47..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-plugins-XP.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-plugins.png b/toolkit/themes/windows/mozapps/extensions/category-plugins.png deleted file mode 100644 index 100a90307..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-plugins.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-recent-XP.png b/toolkit/themes/windows/mozapps/extensions/category-recent-XP.png deleted file mode 100644 index 7ecfc7d4c..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-recent-XP.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-recent.png b/toolkit/themes/windows/mozapps/extensions/category-recent.png deleted file mode 100644 index d65158646..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-recent.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-search.png b/toolkit/themes/windows/mozapps/extensions/category-search.png deleted file mode 100644 index 52e91a7ce..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-search.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/category-service.png b/toolkit/themes/windows/mozapps/extensions/category-service.png deleted file mode 100644 index 997c8541c..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/category-service.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/dictionaryGeneric-16.png b/toolkit/themes/windows/mozapps/extensions/dictionaryGeneric-16.png deleted file mode 100644 index 37e2a5e4c..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/dictionaryGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/dictionaryGeneric.png b/toolkit/themes/windows/mozapps/extensions/dictionaryGeneric.png deleted file mode 100644 index b26bb7100..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/dictionaryGeneric.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/discover-logo.png b/toolkit/themes/windows/mozapps/extensions/discover-logo.png deleted file mode 100644 index cd50735a8..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/discover-logo.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/eula.css b/toolkit/themes/windows/mozapps/extensions/eula.css deleted file mode 100644 index 05aeb3c1c..000000000 --- a/toolkit/themes/windows/mozapps/extensions/eula.css +++ /dev/null @@ -1,47 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#icon { - list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); - max-width: 48px; - max-height: 48px; - margin-inline-end: 6px; -} - -#eula-dialog[addontype="theme"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); -} - -#eula-dialog[addontype="locale"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); -} - -#eula-dialog[addontype="plugin"] #icon { - list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); -} - -#eula-dialog[addontype="dictionary"] #icon { - list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); -} - -#heading-container { - -moz-box-align: center; -} - -#heading { - font-size: 120%; -} - -#eula { - -moz-appearance: none; - color: -moz-FieldText; - background-color: -moz-Field; - margin: 1em; - border: 1px solid; - -moz-border-top-colors: ActiveBorder; - -moz-border-right-colors: ActiveBorder; - -moz-border-bottom-colors: ActiveBorder; - -moz-border-left-colors: ActiveBorder; -} - diff --git a/toolkit/themes/windows/mozapps/extensions/experimentGeneric.png b/toolkit/themes/windows/mozapps/extensions/experimentGeneric.png deleted file mode 100644 index a9d00545e..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/experimentGeneric.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/extensionGeneric-16-XP.png b/toolkit/themes/windows/mozapps/extensions/extensionGeneric-16-XP.png deleted file mode 100644 index 36e7689a3..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/extensionGeneric-16-XP.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/extensionGeneric-16.png b/toolkit/themes/windows/mozapps/extensions/extensionGeneric-16.png deleted file mode 100644 index 2724b9e7c..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/extensionGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/extensions.css b/toolkit/themes/windows/mozapps/extensions/extensions.css deleted file mode 100644 index 335a31bde..000000000 --- a/toolkit/themes/windows/mozapps/extensions/extensions.css +++ /dev/null @@ -1,42 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -%include ../../../shared/extensions/extensions.inc.css - -#header-utils-btn { - list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities"); - margin-inline-end: 16px; -} - -@media not all and (-moz-windows-default-theme) { - #header-utils-btn { - list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities-native"); - } -} - -.sorter[checkState="1"] { - list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif"); -} - -.sorter[checkState="2"] { - list-style-image: url("chrome://global/skin/arrow/arrow-up.gif"); -} - -.addon .relnotes-toggle { - -moz-box-direction: reverse; - list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif"); -} - -.addon[show-relnotes] .relnotes-toggle { - list-style-image: url("chrome://global/skin/arrow/arrow-up.gif"); -} - -.meta-rating > .star { - list-style-image: url("chrome://mozapps/skin/extensions/rating-not-won.png"); - padding: 0 1px; -} - -.meta-rating > .star[on="true"] { - list-style-image: url("chrome://mozapps/skin/extensions/rating-won.png"); -} diff --git a/toolkit/themes/windows/mozapps/extensions/heart.png b/toolkit/themes/windows/mozapps/extensions/heart.png deleted file mode 100644 index 655f4c4be..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/heart.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/localeGeneric-XP.png b/toolkit/themes/windows/mozapps/extensions/localeGeneric-XP.png deleted file mode 100644 index 4d9ac5ad8..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/localeGeneric-XP.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/localeGeneric.png b/toolkit/themes/windows/mozapps/extensions/localeGeneric.png deleted file mode 100644 index 623ba3a6a..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/localeGeneric.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/newaddon.css b/toolkit/themes/windows/mozapps/extensions/newaddon.css deleted file mode 100644 index edfba0ef5..000000000 --- a/toolkit/themes/windows/mozapps/extensions/newaddon.css +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -%include ../../../shared/extensions/newaddon.inc.css diff --git a/toolkit/themes/windows/mozapps/extensions/rating-not-won.png b/toolkit/themes/windows/mozapps/extensions/rating-not-won.png deleted file mode 100644 index 2761f1925..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/rating-not-won.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/rating-won.png b/toolkit/themes/windows/mozapps/extensions/rating-won.png deleted file mode 100644 index 336dd8f6e..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/rating-won.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/themeGeneric-16-XP.png b/toolkit/themes/windows/mozapps/extensions/themeGeneric-16-XP.png deleted file mode 100644 index 16d77a4a2..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/themeGeneric-16-XP.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/themeGeneric-16.png b/toolkit/themes/windows/mozapps/extensions/themeGeneric-16.png deleted file mode 100644 index ff13ce37f..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/themeGeneric-16.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/themeGeneric-XP.png b/toolkit/themes/windows/mozapps/extensions/themeGeneric-XP.png deleted file mode 100644 index be645f76d..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/themeGeneric-XP.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/themeGeneric.png b/toolkit/themes/windows/mozapps/extensions/themeGeneric.png deleted file mode 100644 index 9cea549dd..000000000 Binary files a/toolkit/themes/windows/mozapps/extensions/themeGeneric.png and /dev/null differ diff --git a/toolkit/themes/windows/mozapps/extensions/update.css b/toolkit/themes/windows/mozapps/extensions/update.css deleted file mode 100644 index 0db179330..000000000 --- a/toolkit/themes/windows/mozapps/extensions/update.css +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -.throbber { - list-style-image: url("chrome://global/skin/icons/loading.png"); - width: 16px; - height: 16px; - margin-top: 5px; - margin-bottom: 5px; - margin-inline-start: 5px; - margin-inline-end: 2px; -} - -@media (min-resolution: 1.1dppx) { - .throbber { - list-style-image: url("chrome://global/skin/icons/loading@2x.png"); - } -} - -.alertBox { - background-color: InfoBackground; - color: InfoText; - border: 1px outset InfoBackground; - margin-left: 3px; - margin-right: 3px; - padding: 5px; -} diff --git a/toolkit/themes/windows/mozapps/extensions/xpinstallConfirm.css b/toolkit/themes/windows/mozapps/extensions/xpinstallConfirm.css deleted file mode 100644 index 42db4cd4d..000000000 --- a/toolkit/themes/windows/mozapps/extensions/xpinstallConfirm.css +++ /dev/null @@ -1,101 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#xpinstallheader { - margin-bottom: 2em; -} - -#itemList { - -moz-appearance: listbox; - margin: 3px 4px 10px 4px; - height: 14em; - border: 2px solid; - -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow; - -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow; - -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow; - -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow; - background-color: -moz-Field; - color: -moz-FieldText; -} - -#itemWarningIntro { - margin-inline-start: 8px; -} - -#dialogContentBox { - padding: 5px; -} - -installitem { - padding-top: 5px; - padding-bottom: 5px; - padding-inline-start: 5px; - padding-inline-end: 0; - border-bottom: 1px dotted #C0C0C0; - margin-bottom: 5px; -} - -.alert-icon { -%ifdef XP_WIN - list-style-image: url("chrome://global/skin/icons/warning-large.png"); - width: 48px; - height: 48px; -%endif - margin-inline-end: 20px; -} - -.warning { - font-weight: bold; - font-size: 1.25em; - margin-bottom: 1em; -} - -.xpinstallIconContainer { - width: 32px; - height: 32px; - margin-inline-end: 5px; -} - -.xpinstallItemName { - font-weight: bold; -} - -.xpinstallItemSigned { - font-style: italic; - font-size: 0.9em; -} - -.xpinstallItemURL { - -moz-appearance: none; - border: none; - padding: 0; - background-color: -moz-Field; - color: -moz-FieldText; - margin-top: 1px; - margin-bottom: 1px; - margin-inline-start: 6px; - margin-inline-end: 5px; -} - -.xpinstallItemIcon { - max-width: 32px; - max-height: 32px; - list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); -} - -installitem[type="theme"] .xpinstallItemIcon { - list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); -} - -installitem[type="locale"] .xpinstallItemIcon { - list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); -} - -installitem[type="plugin"] .xpinstallItemIcon { - list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); -} - -installitem[type="dictionary"] .xpinstallItemIcon { - list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); -} diff --git a/toolkit/themes/windows/mozapps/jar.mn b/toolkit/themes/windows/mozapps/jar.mn index 901e93e4e..b5907d016 100644 --- a/toolkit/themes/windows/mozapps/jar.mn +++ b/toolkit/themes/windows/mozapps/jar.mn @@ -6,21 +6,23 @@ toolkit.jar: #include ../../shared/mozapps.inc.mn skin/classic/mozapps/downloads/downloadIcon.png (downloads/downloadIcon.png) skin/classic/mozapps/downloads/downloads.css (downloads/downloads.css) -* skin/classic/mozapps/extensions/extensions.css (extensions/extensions.css) - skin/classic/mozapps/extensions/category-search.png (extensions/category-search.png) - skin/classic/mozapps/extensions/category-discover.png (extensions/category-discover.png) - skin/classic/mozapps/extensions/category-plugins.png (extensions/category-plugins.png) - skin/classic/mozapps/extensions/category-service.png (extensions/category-service.png) - skin/classic/mozapps/extensions/category-recent.png (extensions/category-recent.png) - skin/classic/mozapps/extensions/category-available.png (extensions/category-available.png) - skin/classic/mozapps/extensions/extensionGeneric-16.png (extensions/extensionGeneric-16.png) - skin/classic/mozapps/extensions/themeGeneric.png (extensions/themeGeneric.png) - skin/classic/mozapps/extensions/themeGeneric-16.png (extensions/themeGeneric-16.png) - skin/classic/mozapps/extensions/dictionaryGeneric.png (extensions/dictionaryGeneric.png) - skin/classic/mozapps/extensions/dictionaryGeneric-16.png (extensions/dictionaryGeneric-16.png) - skin/classic/mozapps/extensions/localeGeneric.png (extensions/localeGeneric.png) - skin/classic/mozapps/extensions/heart.png (extensions/heart.png) -* skin/classic/mozapps/extensions/newaddon.css (extensions/newaddon.css) +#ifdef MOZ_WEBEXTENSIONS +* skin/classic/mozapps/extensions/extensions.css (webextensions/extensions.css) + skin/classic/mozapps/extensions/category-search.png (webextensions/category-search.png) + skin/classic/mozapps/extensions/category-discover.png (webextensions/category-discover.png) + skin/classic/mozapps/extensions/category-plugins.png (webextensions/category-plugins.png) + skin/classic/mozapps/extensions/category-service.png (webextensions/category-service.png) + skin/classic/mozapps/extensions/category-recent.png (webextensions/category-recent.png) + skin/classic/mozapps/extensions/category-available.png (webextensions/category-available.png) + skin/classic/mozapps/extensions/extensionGeneric-16.png (webextensions/extensionGeneric-16.png) + skin/classic/mozapps/extensions/themeGeneric.png (webextensions/themeGeneric.png) + skin/classic/mozapps/extensions/themeGeneric-16.png (webextensions/themeGeneric-16.png) + skin/classic/mozapps/extensions/dictionaryGeneric.png (webextensions/dictionaryGeneric.png) + skin/classic/mozapps/extensions/dictionaryGeneric-16.png (webextensions/dictionaryGeneric-16.png) + skin/classic/mozapps/extensions/localeGeneric.png (webextensions/localeGeneric.png) + skin/classic/mozapps/extensions/heart.png (webextensions/heart.png) +* skin/classic/mozapps/extensions/newaddon.css (webextensions/newaddon.css) +#endif skin/classic/mozapps/plugins/pluginGeneric.png (plugins/pluginGeneric.png) skin/classic/mozapps/plugins/pluginBlocked.png (plugins/pluginBlocked.png) skin/classic/mozapps/plugins/pluginGeneric-16.png (plugins/pluginGeneric-16.png) @@ -29,14 +31,16 @@ toolkit.jar: skin/classic/mozapps/viewsource/viewsource.css (viewsource/viewsource.css) skin/classic/mozapps/downloads/downloadButtons-XP.png (downloads/downloadButtons-XP.png) skin/classic/mozapps/downloads/downloadIcon-XP.png (downloads/downloadIcon-XP.png) - skin/classic/mozapps/extensions/category-discover-XP.png (extensions/category-discover-XP.png) - skin/classic/mozapps/extensions/category-plugins-XP.png (extensions/category-plugins-XP.png) - skin/classic/mozapps/extensions/category-recent-XP.png (extensions/category-recent-XP.png) - skin/classic/mozapps/extensions/category-available-XP.png (extensions/category-available-XP.png) - skin/classic/mozapps/extensions/extensionGeneric-16-XP.png (extensions/extensionGeneric-16-XP.png) - skin/classic/mozapps/extensions/themeGeneric-XP.png (extensions/themeGeneric-XP.png) - skin/classic/mozapps/extensions/themeGeneric-16-XP.png (extensions/themeGeneric-16-XP.png) - skin/classic/mozapps/extensions/localeGeneric-XP.png (extensions/localeGeneric-XP.png) +#ifdef MOZ_WEBEXTENSIONS + skin/classic/mozapps/extensions/category-discover-XP.png (webextensions/category-discover-XP.png) + skin/classic/mozapps/extensions/category-plugins-XP.png (webextensions/category-plugins-XP.png) + skin/classic/mozapps/extensions/category-recent-XP.png (webextensions/category-recent-XP.png) + skin/classic/mozapps/extensions/category-available-XP.png (webextensions/category-available-XP.png) + skin/classic/mozapps/extensions/extensionGeneric-16-XP.png (webextensions/extensionGeneric-16-XP.png) + skin/classic/mozapps/extensions/themeGeneric-XP.png (webextensions/themeGeneric-XP.png) + skin/classic/mozapps/extensions/themeGeneric-16-XP.png (webextensions/themeGeneric-16-XP.png) + skin/classic/mozapps/extensions/localeGeneric-XP.png (webextensions/localeGeneric-XP.png) +#endif skin/classic/mozapps/plugins/pluginGeneric-XP.png (plugins/pluginGeneric-XP.png) skin/classic/mozapps/plugins/pluginBlocked-XP.png (plugins/pluginBlocked-XP.png) skin/classic/mozapps/plugins/pluginGeneric-16-XP.png (plugins/pluginGeneric-16-XP.png) @@ -50,6 +54,7 @@ toolkit.jar: #endif % override chrome://mozapps/skin/downloads/downloadButtons.png chrome://mozapps/skin/downloads/downloadButtons-XP.png osversion<6 % override chrome://mozapps/skin/downloads/downloadIcon.png chrome://mozapps/skin/downloads/downloadIcon-XP.png osversion<6 +#ifdef MOZ_WEBEXTENSIONS % override chrome://mozapps/skin/extensions/category-discover.png chrome://mozapps/skin/extensions/category-discover-XP.png osversion<6 % override chrome://mozapps/skin/extensions/category-plugins.png chrome://mozapps/skin/extensions/category-plugins-XP.png osversion<6 % override chrome://mozapps/skin/extensions/category-recent.png chrome://mozapps/skin/extensions/category-recent-XP.png osversion<6 @@ -58,6 +63,7 @@ toolkit.jar: % override chrome://mozapps/skin/extensions/themeGeneric.png chrome://mozapps/skin/extensions/themeGeneric-XP.png osversion<6 % override chrome://mozapps/skin/extensions/themeGeneric-16.png chrome://mozapps/skin/extensions/themeGeneric-16-XP.png osversion<6 % override chrome://mozapps/skin/extensions/localeGeneric.png chrome://mozapps/skin/extensions/localeGeneric-XP.png osversion<6 +#endif % override chrome://mozapps/skin/plugins/pluginGeneric.png chrome://mozapps/skin/plugins/pluginGeneric-XP.png osversion<6 % override chrome://mozapps/skin/plugins/pluginBlocked.png chrome://mozapps/skin/plugins/pluginBlocked-XP.png osversion<6 % override chrome://mozapps/skin/plugins/pluginGeneric-16.png chrome://mozapps/skin/plugins/pluginGeneric-16-XP.png osversion<6 diff --git a/toolkit/themes/windows/mozapps/webextensions/about.css b/toolkit/themes/windows/mozapps/webextensions/about.css new file mode 100644 index 000000000..19eaddca8 --- /dev/null +++ b/toolkit/themes/windows/mozapps/webextensions/about.css @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#genericAbout { + padding: 0px; + min-height: 200px; + max-height: 400px; + width: 30em; +} + +#clientBox { + background-color: -moz-Dialog; + color: -moz-DialogText; +} + +@media (-moz-windows-compositor) { + #genericAbout { + -moz-appearance: -moz-win-glass; + background: transparent; + } + + #clientBox { + -moz-appearance: -moz-win-exclude-glass; + } +} + + +.basic-info { + padding: 10px; +} + +#extensionIcon { + list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); + max-width: 64px; + max-height: 64px; + margin-inline-end: 6px; +} + +#genericAbout[addontype="theme"] #extensionIcon { + list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); +} + +#genericAbout[addontype="locale"] #extensionIcon { + list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); +} + +#genericAbout[addontype="plugin"] #extensionIcon { + list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); +} + +#genericAbout[addontype="dictionary"] #extensionIcon { + list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); +} + +#extensionName { + font-size: 200%; + font-weight: bolder; +} + +#extensionVersion { + font-weight: bold; +} + +#extensionDescription { + margin-top: 4px; +} + +#groove { + margin-top: 8px; +} + +#extensionDetailsBox { + overflow: auto; + min-height: 100px; +} + +.boxIndent { + margin-inline-start: 18px; +} + +#extensionCreator, .contributor { + margin: 0px; +} + +.sectionTitle { + padding: 2px 0px 3px 0px; + margin-top: 3px; + font-weight: bold; +} + diff --git a/toolkit/themes/windows/mozapps/webextensions/blocklist.css b/toolkit/themes/windows/mozapps/webextensions/blocklist.css new file mode 100644 index 000000000..1cdbb35ac --- /dev/null +++ b/toolkit/themes/windows/mozapps/webextensions/blocklist.css @@ -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/. */ + +richlistitem { + padding-top: 6px; + padding-bottom: 6px; + padding-inline-start: 7px; + padding-inline-end: 7px; + border-bottom: 1px solid #C0C0C0; +} + +.addonName { + font-weight: bold; +} + +.blockedLabel { + font-weight: bold; + font-style: italic; +} diff --git a/toolkit/themes/windows/mozapps/webextensions/cancel.png b/toolkit/themes/windows/mozapps/webextensions/cancel.png new file mode 100644 index 000000000..0d98ab235 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/cancel.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-available-XP.png b/toolkit/themes/windows/mozapps/webextensions/category-available-XP.png new file mode 100644 index 000000000..d1b737ab0 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-available-XP.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-available.png b/toolkit/themes/windows/mozapps/webextensions/category-available.png new file mode 100644 index 000000000..9341f2aa7 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-available.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-discover-XP.png b/toolkit/themes/windows/mozapps/webextensions/category-discover-XP.png new file mode 100644 index 000000000..a6f5b49b3 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-discover-XP.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-discover.png b/toolkit/themes/windows/mozapps/webextensions/category-discover.png new file mode 100644 index 000000000..af954a613 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-discover.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-plugins-XP.png b/toolkit/themes/windows/mozapps/webextensions/category-plugins-XP.png new file mode 100644 index 000000000..5c4d8bf47 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-plugins-XP.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-plugins.png b/toolkit/themes/windows/mozapps/webextensions/category-plugins.png new file mode 100644 index 000000000..100a90307 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-plugins.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-recent-XP.png b/toolkit/themes/windows/mozapps/webextensions/category-recent-XP.png new file mode 100644 index 000000000..7ecfc7d4c Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-recent-XP.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-recent.png b/toolkit/themes/windows/mozapps/webextensions/category-recent.png new file mode 100644 index 000000000..d65158646 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-recent.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-search.png b/toolkit/themes/windows/mozapps/webextensions/category-search.png new file mode 100644 index 000000000..52e91a7ce Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-search.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/category-service.png b/toolkit/themes/windows/mozapps/webextensions/category-service.png new file mode 100644 index 000000000..997c8541c Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/category-service.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/dictionaryGeneric-16.png b/toolkit/themes/windows/mozapps/webextensions/dictionaryGeneric-16.png new file mode 100644 index 000000000..37e2a5e4c Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/dictionaryGeneric-16.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/dictionaryGeneric.png b/toolkit/themes/windows/mozapps/webextensions/dictionaryGeneric.png new file mode 100644 index 000000000..b26bb7100 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/dictionaryGeneric.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/discover-logo.png b/toolkit/themes/windows/mozapps/webextensions/discover-logo.png new file mode 100644 index 000000000..cd50735a8 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/discover-logo.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/eula.css b/toolkit/themes/windows/mozapps/webextensions/eula.css new file mode 100644 index 000000000..05aeb3c1c --- /dev/null +++ b/toolkit/themes/windows/mozapps/webextensions/eula.css @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#icon { + list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); + max-width: 48px; + max-height: 48px; + margin-inline-end: 6px; +} + +#eula-dialog[addontype="theme"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); +} + +#eula-dialog[addontype="locale"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); +} + +#eula-dialog[addontype="plugin"] #icon { + list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); +} + +#eula-dialog[addontype="dictionary"] #icon { + list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); +} + +#heading-container { + -moz-box-align: center; +} + +#heading { + font-size: 120%; +} + +#eula { + -moz-appearance: none; + color: -moz-FieldText; + background-color: -moz-Field; + margin: 1em; + border: 1px solid; + -moz-border-top-colors: ActiveBorder; + -moz-border-right-colors: ActiveBorder; + -moz-border-bottom-colors: ActiveBorder; + -moz-border-left-colors: ActiveBorder; +} + diff --git a/toolkit/themes/windows/mozapps/webextensions/experimentGeneric.png b/toolkit/themes/windows/mozapps/webextensions/experimentGeneric.png new file mode 100644 index 000000000..a9d00545e Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/experimentGeneric.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/extensionGeneric-16-XP.png b/toolkit/themes/windows/mozapps/webextensions/extensionGeneric-16-XP.png new file mode 100644 index 000000000..36e7689a3 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/extensionGeneric-16-XP.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/extensionGeneric-16.png b/toolkit/themes/windows/mozapps/webextensions/extensionGeneric-16.png new file mode 100644 index 000000000..2724b9e7c Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/extensionGeneric-16.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/extensions.css b/toolkit/themes/windows/mozapps/webextensions/extensions.css new file mode 100644 index 000000000..7c4aed05e --- /dev/null +++ b/toolkit/themes/windows/mozapps/webextensions/extensions.css @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +%include ../../../shared/webextensions/extensions.inc.css + +#header-utils-btn { + list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities"); + margin-inline-end: 16px; +} + +@media not all and (-moz-windows-default-theme) { + #header-utils-btn { + list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities-native"); + } +} + +.sorter[checkState="1"] { + list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif"); +} + +.sorter[checkState="2"] { + list-style-image: url("chrome://global/skin/arrow/arrow-up.gif"); +} + +.addon .relnotes-toggle { + -moz-box-direction: reverse; + list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif"); +} + +.addon[show-relnotes] .relnotes-toggle { + list-style-image: url("chrome://global/skin/arrow/arrow-up.gif"); +} + +.meta-rating > .star { + list-style-image: url("chrome://mozapps/skin/extensions/rating-not-won.png"); + padding: 0 1px; +} + +.meta-rating > .star[on="true"] { + list-style-image: url("chrome://mozapps/skin/extensions/rating-won.png"); +} diff --git a/toolkit/themes/windows/mozapps/webextensions/heart.png b/toolkit/themes/windows/mozapps/webextensions/heart.png new file mode 100644 index 000000000..655f4c4be Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/heart.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/localeGeneric-XP.png b/toolkit/themes/windows/mozapps/webextensions/localeGeneric-XP.png new file mode 100644 index 000000000..4d9ac5ad8 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/localeGeneric-XP.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/localeGeneric.png b/toolkit/themes/windows/mozapps/webextensions/localeGeneric.png new file mode 100644 index 000000000..623ba3a6a Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/localeGeneric.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/newaddon.css b/toolkit/themes/windows/mozapps/webextensions/newaddon.css new file mode 100644 index 000000000..5856c08b5 --- /dev/null +++ b/toolkit/themes/windows/mozapps/webextensions/newaddon.css @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +%include ../../../shared/webextensions/newaddon.inc.css diff --git a/toolkit/themes/windows/mozapps/webextensions/rating-not-won.png b/toolkit/themes/windows/mozapps/webextensions/rating-not-won.png new file mode 100644 index 000000000..2761f1925 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/rating-not-won.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/rating-won.png b/toolkit/themes/windows/mozapps/webextensions/rating-won.png new file mode 100644 index 000000000..336dd8f6e Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/rating-won.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/themeGeneric-16-XP.png b/toolkit/themes/windows/mozapps/webextensions/themeGeneric-16-XP.png new file mode 100644 index 000000000..16d77a4a2 Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/themeGeneric-16-XP.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/themeGeneric-16.png b/toolkit/themes/windows/mozapps/webextensions/themeGeneric-16.png new file mode 100644 index 000000000..ff13ce37f Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/themeGeneric-16.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/themeGeneric-XP.png b/toolkit/themes/windows/mozapps/webextensions/themeGeneric-XP.png new file mode 100644 index 000000000..be645f76d Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/themeGeneric-XP.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/themeGeneric.png b/toolkit/themes/windows/mozapps/webextensions/themeGeneric.png new file mode 100644 index 000000000..9cea549dd Binary files /dev/null and b/toolkit/themes/windows/mozapps/webextensions/themeGeneric.png differ diff --git a/toolkit/themes/windows/mozapps/webextensions/update.css b/toolkit/themes/windows/mozapps/webextensions/update.css new file mode 100644 index 000000000..0db179330 --- /dev/null +++ b/toolkit/themes/windows/mozapps/webextensions/update.css @@ -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/. */ + +.throbber { + list-style-image: url("chrome://global/skin/icons/loading.png"); + width: 16px; + height: 16px; + margin-top: 5px; + margin-bottom: 5px; + margin-inline-start: 5px; + margin-inline-end: 2px; +} + +@media (min-resolution: 1.1dppx) { + .throbber { + list-style-image: url("chrome://global/skin/icons/loading@2x.png"); + } +} + +.alertBox { + background-color: InfoBackground; + color: InfoText; + border: 1px outset InfoBackground; + margin-left: 3px; + margin-right: 3px; + padding: 5px; +} diff --git a/toolkit/themes/windows/mozapps/webextensions/xpinstallConfirm.css b/toolkit/themes/windows/mozapps/webextensions/xpinstallConfirm.css new file mode 100644 index 000000000..42db4cd4d --- /dev/null +++ b/toolkit/themes/windows/mozapps/webextensions/xpinstallConfirm.css @@ -0,0 +1,101 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#xpinstallheader { + margin-bottom: 2em; +} + +#itemList { + -moz-appearance: listbox; + margin: 3px 4px 10px 4px; + height: 14em; + border: 2px solid; + -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow; + -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow; + -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow; + -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow; + background-color: -moz-Field; + color: -moz-FieldText; +} + +#itemWarningIntro { + margin-inline-start: 8px; +} + +#dialogContentBox { + padding: 5px; +} + +installitem { + padding-top: 5px; + padding-bottom: 5px; + padding-inline-start: 5px; + padding-inline-end: 0; + border-bottom: 1px dotted #C0C0C0; + margin-bottom: 5px; +} + +.alert-icon { +%ifdef XP_WIN + list-style-image: url("chrome://global/skin/icons/warning-large.png"); + width: 48px; + height: 48px; +%endif + margin-inline-end: 20px; +} + +.warning { + font-weight: bold; + font-size: 1.25em; + margin-bottom: 1em; +} + +.xpinstallIconContainer { + width: 32px; + height: 32px; + margin-inline-end: 5px; +} + +.xpinstallItemName { + font-weight: bold; +} + +.xpinstallItemSigned { + font-style: italic; + font-size: 0.9em; +} + +.xpinstallItemURL { + -moz-appearance: none; + border: none; + padding: 0; + background-color: -moz-Field; + color: -moz-FieldText; + margin-top: 1px; + margin-bottom: 1px; + margin-inline-start: 6px; + margin-inline-end: 5px; +} + +.xpinstallItemIcon { + max-width: 32px; + max-height: 32px; + list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg"); +} + +installitem[type="theme"] .xpinstallItemIcon { + list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png"); +} + +installitem[type="locale"] .xpinstallItemIcon { + list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png"); +} + +installitem[type="plugin"] .xpinstallItemIcon { + list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png"); +} + +installitem[type="dictionary"] .xpinstallItemIcon { + list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png"); +} -- cgit v1.2.3