aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.4/packages
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.4/packages')
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/README.md8
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/data/moz_favicon.icobin0 -> 1406 bytes
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/data/test-page-mod.html8
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/data/test-page-worker.html8
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/data/test-page-worker.js25
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/data/test-request-invalid.json1
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/data/test-request.json1
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/data/test-request.txt1
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/data/test.html8
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/clipboard.md58
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/context-menu.md702
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/hotkeys.md74
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/notifications.md60
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/page-mod.md367
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/page-worker.md218
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/panel.md301
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/passwords.md564
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/private-browsing.md46
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/request.md192
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/selection.md86
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/self.md73
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/simple-prefs.md70
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/simple-storage.md125
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/tabs.md381
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/timers.md48
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/widget.md692
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/docs/windows.md187
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/clipboard.js266
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/context-menu.js1526
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/hotkeys.js71
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/notifications.js112
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/page-mod.js229
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/page-worker.js101
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/panel.js403
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/passwords.js92
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/private-browsing.js102
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/request.js309
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/selection.js448
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/simple-prefs.js107
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js263
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/tabs.js62
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/timers.js40
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/widget.js947
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/windows.js238
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/package.json12
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/helpers.js19
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/pagemod-test-helpers.js63
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-clipboard.js60
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.html78
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js2075
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-hotkeys.js156
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-module.js33
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-notifications.js79
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-page-mod.js416
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-page-worker.js362
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-panel.js437
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-passwords.js277
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-private-browsing.js238
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-request.js358
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-selection.js490
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-simple-prefs.js180
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-simple-storage.js346
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-tabs.js905
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-timers.js44
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-widget.js950
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/tests/test-windows.js342
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/README.md31
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/data/content-proxy.js834
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/data/test-content-symbiont.js1
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/data/test-httpd.txt1
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/data/test-trusted-document.html13
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/api-utils.md153
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/app-strings.md61
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/byte-streams.md64
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/collection.md73
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/content.md11
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/content/loader.md88
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/content/proxy.md237
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/content/symbiont.md136
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/content/worker.md126
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/cortex.md156
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/cuddlefish.md5
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/environment.md39
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/errors.md38
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/es5.md52
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/events.md74
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/file.md147
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/hidden-frame.md79
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/httpd.md27
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/light-traits.md291
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/list.md94
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/match-pattern.md242
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/memory.md3
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/namespace.md66
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/observer-service.md69
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/plain-text-console.md3
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/preferences-service.md80
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/runtime.md71
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/securable-module.md93
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/tab-browser.md136
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/text-streams.md98
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/traceback.md62
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/traits.md240
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/unit-test.md389
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/unload.md57
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/url.md81
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/window-utils.md84
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/xhr.md91
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/xpcom.md223
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/docs/xul-app.md72
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/api-utils.js186
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/app-strings.js95
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/array.js102
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/byte-streams.js135
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/channel.js67
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/collection.js141
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/content.js44
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/content/loader.js203
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/content/symbiont.js217
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/content/worker.js662
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/cortex.js139
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/cuddlefish.js320
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/dom/events.js169
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/dom/events/keys.js93
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/env!.js52
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/environment.js86
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/errors.js92
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/events.js202
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/events/assembler.js86
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/file.js227
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/find-tests.js1
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/globals!.js113
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/hidden-frame.js200
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/httpd.js5202
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/hotkeys.js141
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/observer.js86
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/utils.js220
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/light-traits.js626
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/list.js147
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/match-pattern.js137
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/memory.js146
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/namespace.js55
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/observer-service.js212
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/passwords/utils.js134
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/plain-text-console.js114
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/preferences-service.js138
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/process.js95
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/runtime.js48
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/self!.js82
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/system.js131
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tab-browser.js761
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tabs/events.js56
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tabs/observer.js126
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tabs/tab.js297
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/tabs/utils.js87
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/test.js140
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/test/assert.js360
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/text-streams.js273
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/timer.js141
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/traceback.js155
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/traits.js215
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/traits/core.js349
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/type.js372
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/unit-test-finder.js106
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/unit-test.js466
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/unload.js59
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/url.js123
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/utils/data.js104
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/utils/function.js64
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/utils/registry.js90
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/utils/thumbnail.js76
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/window-utils.js270
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/windows/dom.js60
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/windows/loader.js152
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/windows/observer.js86
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/windows/tabs.js207
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/xhr.js181
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/xpcom.js152
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/lib/xul-app.js95
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/package.json14
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/commonjs-test-adapter/asserts.js50
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/fixtures/es5.js4
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/helpers.js19
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/loader/fixture.js2
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/add.js5
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/async1.js10
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/async2.js4
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/badExportAndReturn.js6
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/badFirst.js5
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/badSecond.js4
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/blue.js5
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/castor.js6
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/cheetah.js5
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/color.js3
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupe.js11
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupeNested.js11
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupeSetExports.js4
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/exportsEquals.js1
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/green.js6
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/lion.js3
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/orange.js6
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/pollux.js6
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/red.js12
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/setExports.js1
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/subtract.js5
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/tiger.js4
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/traditional1.js8
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/traditional2.js2
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/modules/types/cat.js1
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-api-utils.js274
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-app-strings.js58
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-array.js36
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-byte-streams.js203
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-collection.js160
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-commonjs-test-adapter.js7
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-content-loader.js223
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-content-proxy.js754
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-content-symbiont.js186
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-content-worker.js374
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-cortex.js118
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-dom.js84
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-e10s-porting.js73
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-environment.js46
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-errors.js66
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-events.js182
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-file.js276
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-function-utils.js23
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-globals.js21
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-hidden-frame.js47
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-httpd.js28
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-keyboard-observer.js32
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-keyboard-utils.js58
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-light-traits.js7
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-list.js203
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-loader.js31
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-match-pattern.js161
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-memory.js15
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-modules.js144
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-namespace.js70
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-observer-service.js73
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-passwords-utils.js138
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-plain-text-console.js64
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-preferences-service.js87
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-registry.js76
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-require.js25
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-self.js29
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-set-exports.js31
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-tab-browser.js525
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-tab-observer.js35
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-tab.js106
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-text-streams.js190
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-timer.js127
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-traceback.js114
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-traits-core.js834
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-traits.js394
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-type.js88
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-unit-test.js247
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-unload.js196
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-url.js157
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-window-loader.js152
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-window-observer.js44
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-window-utils.js271
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-xhr.js70
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-xpcom.js107
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/test-xul-app.js41
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/traits/assert.js94
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/traits/descriptor-tests.js331
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/traits/inheritance-tests.js100
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/traits/object-tests.js317
-rw-r--r--tools/addon-sdk-1.4/packages/api-utils/tests/traits/utils.js52
-rw-r--r--tools/addon-sdk-1.4/packages/development-mode/README.md8
-rw-r--r--tools/addon-sdk-1.4/packages/development-mode/docs/bootstrap.md6
-rw-r--r--tools/addon-sdk-1.4/packages/development-mode/docs/main.md5
-rw-r--r--tools/addon-sdk-1.4/packages/development-mode/lib/bootstrap.js153
-rw-r--r--tools/addon-sdk-1.4/packages/development-mode/lib/main.js95
-rw-r--r--tools/addon-sdk-1.4/packages/development-mode/package.json10
-rw-r--r--tools/addon-sdk-1.4/packages/test-harness/README.md8
-rw-r--r--tools/addon-sdk-1.4/packages/test-harness/docs/harness.md2
-rw-r--r--tools/addon-sdk-1.4/packages/test-harness/docs/run-tests.md9
-rw-r--r--tools/addon-sdk-1.4/packages/test-harness/lib/harness.js352
-rw-r--r--tools/addon-sdk-1.4/packages/test-harness/lib/run-tests.js133
-rw-r--r--tools/addon-sdk-1.4/packages/test-harness/package.json8
-rw-r--r--tools/addon-sdk-1.4/packages/test-harness/tests/test-packaging.js14
283 files changed, 49601 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/README.md b/tools/addon-sdk-1.4/packages/addon-kit/README.md
new file mode 100644
index 0000000..9a68554
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/README.md
@@ -0,0 +1,8 @@
+The addon-kit package provides high-level APIs for add-on developers.
+Most of the needs of most add-on developers should be served by the modules
+found here. Modules in this packages don't require any special privileges to
+run.
+
+The modules in the addon-kit package are relatively stable. We intend to add
+new APIs here and extend existing ones, but will avoid making incompatible
+changes to them unless absolutely necessary.
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/data/moz_favicon.ico b/tools/addon-sdk-1.4/packages/addon-kit/data/moz_favicon.ico
new file mode 100644
index 0000000..d444389
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/data/moz_favicon.ico
Binary files differ
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-mod.html b/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-mod.html
new file mode 100644
index 0000000..9211698
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-mod.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Page Mod test</title>
+</head>
+<body>
+ <p id="paragraph">Lorem ipsum dolor sit amet.</p>
+</body>
+</html>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-worker.html b/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-worker.html
new file mode 100644
index 0000000..e89a283
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-worker.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Page Worker test</title>
+</head>
+<body>
+ <p id="paragraph">Lorem ipsum dolor sit amet.</p>
+</body>
+</html>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-worker.js b/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-worker.js
new file mode 100644
index 0000000..d8effb9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/data/test-page-worker.js
@@ -0,0 +1,25 @@
+
+// get title directly
+self.postMessage(["assertEqual", document.title, "Page Worker test",
+ "Correct page title accessed directly"]);
+
+// get <p> directly
+let p = document.getElementById("paragraph");
+self.postMessage(["assert", !!p, "<p> can be accessed directly"]);
+self.postMessage(["assertEqual", p.firstChild.nodeValue,
+ "Lorem ipsum dolor sit amet.",
+ "Correct text node expected"]);
+
+// Modify page
+let div = document.createElement("div");
+div.setAttribute("id", "block");
+div.appendChild(document.createTextNode("Test text created"));
+document.body.appendChild(div);
+
+// Check back the modification
+div = document.getElementById("block");
+self.postMessage(["assert", !!div, "<div> can be accessed directly"]);
+self.postMessage(["assertEqual", div.firstChild.nodeValue,
+ "Test text created", "Correct text node expected"]);
+self.postMessage(["done"]);
+
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/data/test-request-invalid.json b/tools/addon-sdk-1.4/packages/addon-kit/data/test-request-invalid.json
new file mode 100644
index 0000000..35f9ff6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/data/test-request-invalid.json
@@ -0,0 +1 @@
+"this": "isn't JSON"
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/data/test-request.json b/tools/addon-sdk-1.4/packages/addon-kit/data/test-request.json
new file mode 100644
index 0000000..8c850a5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/data/test-request.json
@@ -0,0 +1 @@
+{ "foo": "bar" }
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/data/test-request.txt b/tools/addon-sdk-1.4/packages/addon-kit/data/test-request.txt
new file mode 100644
index 0000000..390125d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/data/test-request.txt
@@ -0,0 +1 @@
+Look ma, no hands!
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/data/test.html b/tools/addon-sdk-1.4/packages/addon-kit/data/test.html
new file mode 100644
index 0000000..39de477
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/data/test.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>foo</title>
+ </head>
+ <body>
+ <p>bar</p>
+ </body>
+</html>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/clipboard.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/clipboard.md
new file mode 100644
index 0000000..7292017
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/clipboard.md
@@ -0,0 +1,58 @@
+<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
+
+The `clipboard` module allows callers to interact with the system clipboard,
+setting and retrieving its contents.
+
+You can optionally specify the type of the data to set and retrieve.
+The following types are supported:
+
+* `text` (plain text)
+* `html` (a string of HTML)
+
+If no data type is provided, then the module will detect it for you.
+
+Examples
+--------
+
+Set and get the contents of the clipboard.
+
+ let clipboard = require("clipboard");
+ clipboard.set("Lorem ipsum dolor sit amet");
+ let contents = clipboard.get();
+
+Set the clipboard contents to some HTML.
+
+ let clipboard = require("clipboard");
+ clipboard.set("<blink>Lorem ipsum dolor sit amet</blink>", "html");
+
+If the clipboard contains HTML content, open it in a new tab.
+
+ let clipboard = require("clipboard");
+ if (clipboard.currentFlavors.indexOf("html") != -1)
+ require("tabs").open("data:text/html," + clipboard.get("html"));
+
+<api name="set">
+@function
+ Replace the contents of the user's clipboard with the provided data.
+@param data {string}
+ The data to put on the clipboard.
+@param [datatype] {string}
+ The type of the data (optional).
+</api>
+
+<api name="get">
+@function
+ Get the contents of the user's clipboard.
+@param [datatype] {string}
+ Retrieve the clipboard contents only if matching this type (optional).
+ The function will return null if the contents of the clipboard do not match
+ the supplied type.
+</api>
+
+<api name="currentFlavors">
+@property {array}
+ Data on the clipboard is sometimes available in multiple types. For example,
+ HTML data might be available as both a string of HTML (the `html` type)
+ and a string of plain text (the `text` type). This function returns an array
+ of all types in which the data currently on the clipboard is available.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/context-menu.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/context-menu.md
new file mode 100644
index 0000000..f2bb5b3
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/context-menu.md
@@ -0,0 +1,702 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `context-menu` module lets you add items to Firefox's page context menu.
+
+
+Introduction
+------------
+
+The `context-menu` API provides a simple, declarative way to add items to the
+page's context menu. You can add items that perform an action when clicked,
+submenus, and menu separators.
+
+Instead of manually adding items when particular contexts occur and then
+removing them when those contexts go away, you *bind* items to contexts, and the
+adding and removing is automatically handled for you. Items are bound to
+contexts in much the same way that event listeners are bound to events. When
+the user invokes the context menu, all of the items bound to the current context
+are automatically added to the menu. If no items are bound, none are added.
+Likewise, any items that were previously in the menu but are not bound to the
+current context are automatically removed from the menu. You never need to
+manually remove your items from the menu unless you want them to never appear
+again.
+
+For example, if your add-on needs to add a context menu item whenever the
+user visits a certain page, don't create the item when that page loads, and
+don't remove it when the page unloads. Rather, create your item only once and
+supply a context that matches the target URL.
+
+
+Specifying Contexts
+-------------------
+
+As its name implies, the context menu should be reserved for the occurrence of
+specific contexts. Contexts can be related to page content or the page itself,
+but they should never be external to the page.
+
+For example, a good use of the menu would be to show an "Edit Image" item when
+the user right-clicks an image in the page. A bad use would be to show a
+submenu that listed all the user's tabs, since tabs aren't related to the page
+or the node the user clicked to open the menu.
+
+### The Page Context
+
+First of all, you may not need to specify a context at all. When an item does
+not specify a context, the page context applies.
+
+The *page context* occurs when the user invokes the context menu on a
+non-interactive portion of the page. Try right-clicking a blank spot in this
+page, or on text. Make sure that no text is selected. The menu that appears
+should contain the items "Back", "Forward", "Reload", "Stop", and so on. This
+is the page context.
+
+The page context is appropriate when your item acts on the page as a whole. It
+does not occur when the user invokes the context menu on a link, image, or other
+non-text node, or while a selection exists.
+
+### Declarative Contexts
+
+You can specify some simple, declarative contexts when you create a menu item by
+setting the `context` property of the options object passed to its constructor,
+like this:
+
+ var cm = require("context-menu");
+ cm.Item({
+ label: "My Menu Item",
+ context: cm.URLContext("*.mozilla.org")
+ });
+
+These contexts may be specified by calling the following constructors. Each is
+exported by the `context-menu` module.
+
+<table>
+ <tr>
+ <th>Constructor</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td><code>
+ PageContext()
+ </code></td>
+ <td>
+ The page context.
+ </td>
+ </tr>
+ <tr>
+ <td><code>
+ SelectionContext()
+ </code></td>
+ <td>
+ This context occurs when the menu is invoked on a page in which the user
+ has made a selection.
+ </td>
+ </tr>
+ <tr>
+ <td><code>
+ SelectorContext(selector)
+ </code></td>
+ <td>
+ This context occurs when the menu is invoked on a node that either matches
+ <code>selector</code>, a CSS selector, or has an ancestor that matches.
+ <code>selector</code> may include multiple selectors separated by commas,
+ e.g., <code>"a[href], img"</code>.
+ </td>
+ </tr>
+ <tr>
+ <td><code>
+ URLContext(matchPattern)
+ </code></td>
+ <td>
+ This context occurs when the menu is invoked on pages with particular
+ URLs. <code>matchPattern</code> is a match pattern string or an array of
+ match pattern strings. When <code>matchPattern</code> is an array, the
+ context occurs when the menu is invoked on a page whose URL matches any of
+ the patterns. These are the same match pattern strings that you use with
+ the <a href="packages/addon-kit/docs/page-mod.html"><code>page-mod</code></a>
+ <code>include</code> property.
+ <a href="packages/api-utils/docs/match-pattern.html">Read more about patterns</a>.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ array
+ </td>
+ <td>
+ An array of any of the other types. This context occurs when all contexts
+ in the array occur.
+ </td>
+ </tr>
+</table>
+
+Menu items also have a `context` property that can be used to add and remove
+declarative contexts after construction. For example:
+
+ var context = require("context-menu").SelectorContext("img");
+ myMenuItem.context.add(context);
+ myMenuItem.context.remove(context);
+
+When a menu item is bound to more than one context, it appears in the menu when
+all of those contexts occur.
+
+### In Content Scripts
+
+The declarative contexts are handy but not very powerful. For instance, you
+might want your menu item to appear for any page that has at least one image,
+but declarative contexts won't help you there.
+
+When you need more control control over the context in which your menu items are
+shown, you can use content scripts. Like other APIs in the SDK, the
+`context-menu` API uses
+[content scripts](dev-guide/addon-development/web-content.html) to let your
+add-on interact with pages in the browser. Each menu item you create in the
+top-level context menu can have a content script.
+
+A special event named `"context"` is emitted in your content scripts whenever
+the context menu is about to be shown. If you register a listener function for
+this event and it returns true, the menu item associated with the listener's
+content script is shown in the menu.
+
+For example, this item appears whenever the context menu is invoked on a page
+that contains at least one image:
+
+ require("context-menu").Item({
+ label: "This Page Has Images",
+ contentScript: 'self.on("context", function (node) {' +
+ ' return !!document.querySelector("img");' +
+ '});'
+ });
+
+Note that the listener function has a parameter called `node`. This is the node
+in the page that the user context-clicked to invoke the menu. You can use it to
+determine whether your item should be shown.
+
+You can both specify declarative contexts and listen for contexts in a content
+script. In that case, the declarative contexts are evaluated first. If they
+are not current, then your context listener is never called.
+
+This example takes advantage of that fact. The listener can be assured that
+`node` will always be an image:
+
+ require("context-menu").Item({
+ label: "A Mozilla Image",
+ context: contextMenu.SelectorContext("img"),
+ contentScript: 'self.on("context", function (node) {' +
+ ' return /mozilla/.test(node.src);' +
+ '});'
+ });
+
+Your item is shown only when all declarative contexts are current and your
+context listener returns true.
+
+
+Handling Menu Item Clicks
+-------------------------
+
+In addition to using content scripts to listen for the `"context"` event as
+described above, you can use content scripts to handle item clicks. When the
+user clicks your menu item, an event named `"click"` is emitted in the item's
+content script.
+
+Therefore, to handle an item click, listen for the `"click"` event in that
+item's content script like so:
+
+ require("context-menu").Item({
+ label: "My Item",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' console.log("Item clicked!");' +
+ '});'
+ });
+
+Note that the listener function has parameters called `node` and `data`. `node`
+is the node that the user context-clicked to invoke the menu. You can use it
+when performing some action. `data` is the `data` property of the menu item
+that was clicked. Since only top-level menu items have content scripts, this
+comes in handy for determining which item in a `Menu` was clicked:
+
+ var cm = require("context-menu");
+ cm.Menu({
+ label: "My Menu",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' console.log("You clicked " + data);' +
+ '});',
+ items: [
+ cm.Item({ label: "Item 1", data: "item1" }),
+ cm.Item({ label: "Item 2", data: "item2" }),
+ cm.Item({ label: "Item 3", data: "item3" })
+ ]
+ });
+
+Often you will need to collect some kind of information in the click listener
+and perform an action unrelated to content. To communicate to the menu item
+associated with the content script, the content script can call the
+`postMessage` function attached to the global `self` object, passing it some
+JSON-able data. The menu item's `"message"` event listener will be called with
+that data.
+
+ require("context-menu").Item({
+ label: "Edit Image",
+ context: contextMenu.SelectorContext("img"),
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' self.postMessage(node.src);' +
+ '});',
+ onMessage: function (imgSrc) {
+ openImageEditor(imgSrc);
+ }
+ });
+
+
+Updating a Menu Item's Label
+----------------------------
+
+Each menu item must be created with a label, but you can change its label later
+using a couple of methods.
+
+The simplest method is to set the menu item's `label` property. This example
+updates the item's label based on the number of times it's been clicked:
+
+ var numClicks = 0;
+ var myItem = require("context-menu").Item({
+ label: "Click Me: " + numClicks,
+ contentScript: 'self.on("click", self.postMessage);',
+ onMessage: function () {
+ numClicks++;
+ this.label = "Click Me: " + numClicks;
+ // Setting myItem.label is equivalent.
+ }
+ });
+
+Sometimes you might want to update the label based on the context. For
+instance, if your item performs a search with the user's selected text, it would
+be nice to display the text in the item to provide feedback to the user. In
+these cases you can use the second method. Recall that your content scripts can
+listen for the `"context"` event and if your listeners return true, the items
+associated with the content scripts are shown in the menu. In addition to
+returning true, your `"context"` listeners can also return strings. When a
+`"context"` listener returns a string, it becomes the item's new label.
+
+This item implements the aforementioned search example:
+
+ var cm = require("context-menu");
+ cm.Item({
+ label: "Search Google",
+ context: cm.SelectionContext(),
+ contentScript: 'self.on("context", function () {' +
+ ' var text = window.getSelection().toString();' +
+ ' if (text.length > 20)' +
+ ' text = text.substr(0, 20) + "...";' +
+ ' return "Search Google for " + text;' +
+ '});'
+ });
+
+The `"context"` listener gets the window's current selection, truncating it if
+it's too long, and includes it in the returned string. When the item is shown,
+its label will be "Search Google for `text`", where `text` is the truncated
+selection.
+
+
+More Examples
+-------------
+
+For conciseness, these examples create their content scripts as strings and use
+the `contentScript` property. In your own add-on, you will probably want to
+create your content scripts in separate files and pass their URLs using the
+`contentScriptFile` property. See
+[Working with Content Scripts](dev-guide/addon-development/web-content.html)
+for more information.
+
+Show an "Edit Page Source" item when the user right-clicks a non-interactive
+part of the page:
+
+ require("context-menu").Item({
+ label: "Edit Page Source",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' self.postMessage(document.URL);' +
+ '});',
+ onMessage: function (pageURL) {
+ editSource(pageURL);
+ }
+ });
+
+Show an "Edit Image" item when the menu is invoked on an image:
+
+ require("context-menu").Item({
+ label: "Edit Image",
+ context: contextMenu.SelectorContext("img"),
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' self.postMessage(node.src);' +
+ '});',
+ onMessage: function (imgSrc) {
+ openImageEditor(imgSrc);
+ }
+ });
+
+Show an "Edit Mozilla Image" item when the menu is invoked on an image in a
+mozilla.org or mozilla.com page:
+
+ var cm = require("context-menu");
+ cm.Item({
+ label: "Edit Mozilla Image",
+ context: [
+ cm.URLContext(["*.mozilla.org", "*.mozilla.com"]),
+ cm.SelectorContext("img")
+ ],
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' self.postMessage(node.src);' +
+ '});',
+ onMessage: function (imgSrc) {
+ openImageEditor(imgSrc);
+ }
+ });
+
+Show an "Edit Page Images" item when the page contains at least one image:
+
+ require("context-menu").Item({
+ label: "Edit Page Images",
+ // This ensures the item only appears during the page context.
+ context: contextMenu.PageContext(),
+ contentScript: 'self.on("context", function (node) {' +
+ ' var pageHasImgs = !!document.querySelector("img");' +
+ ' return pageHasImgs;' +
+ '});' +
+ 'self.on("click", function (node, data) {' +
+ ' var imgs = document.querySelectorAll("img");' +
+ ' var imgSrcs = [];' +
+ ' for (var i = 0 ; i < imgs.length; i++)' +
+ ' imgSrcs.push(imgs[i].src);' +
+ ' self.postMessage(imgSrcs);' +
+ '});',
+ onMessage: function (imgSrcs) {
+ openImageEditor(imgSrcs);
+ }
+ });
+
+Show a "Search With" menu when the user right-clicks an anchor that searches
+Google or Wikipedia with the text contained in the anchor:
+
+ var cm = require("context-menu");
+ var googleItem = cm.Item({
+ label: "Google",
+ data: "http://www.google.com/search?q="
+ });
+ var wikipediaItem = cm.Item({
+ label: "Wikipedia",
+ data: "http://en.wikipedia.org/wiki/Special:Search?search="
+ });
+ var searchMenu = cm.Menu({
+ label: "Search With",
+ context: contextMenu.SelectorContext("a[href]"),
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' var searchURL = data + node.textContent;' +
+ ' window.location.href = searchURL;' +
+ '});',
+ items: [googleItem, wikipediaItem]
+ });
+
+
+<api name="Item">
+@class
+A labeled menu item that can perform an action when clicked.
+<api name="Item">
+@constructor
+ Creates a labeled menu item that can perform an action when clicked.
+@param options {object}
+ An object with the following keys:
+ @prop label {string}
+ The item's label. It must either be a string or an object that implements
+ `toString()`.
+ @prop [image] {string}
+ The item's icon, a string URL. The URL can be remote, a reference to an
+ image in the add-on's `data` directory, or a data URI.
+ @prop [data] {string}
+ An optional arbitrary value to associate with the item. It must be either a
+ string or an object that implements `toString()`. It will be passed to
+ click listeners.
+ @prop [context] {value}
+ If the item is contained in the top-level context menu, this declaratively
+ specifies the context under which the item will appear; see Specifying
+ Contexts above. Ignored if the item is contained in a submenu.
+ @prop [contentScript] {string,array}
+ If the item is contained in the top-level context menu, this is the content
+ script or an array of content scripts that the item can use to interact with
+ the page. Ignored if the item is contained in a submenu.
+ @prop [contentScriptFile] {string,array}
+ If the item is contained in the top-level context menu, this is the local
+ file URL of the content script or an array of such URLs that the item can
+ use to interact with the page. Ignored if the item is contained in a
+ submenu.
+ @prop [onMessage] {function}
+ If the item is contained in the top-level context menu, this function will
+ be called when the content script calls `self.postMessage`. It will be
+ passed the data that was passed to `postMessage`. Ignored if the item is
+ contained in a submenu.
+</api>
+
+<api name="label">
+@property {string}
+ The menu item's label. You can set this after creating the item to update its
+ label later.
+</api>
+
+<api name="image">
+@property {string}
+ The item's icon, a string URL. The URL can be remote, a reference to an image
+ in the add-on's `data` directory, or a data URI. You can set this after
+ creating the item to update its image later. To remove the item's image, set
+ it to `null`.
+</api>
+
+<api name="data">
+@property {string}
+ An optional arbitrary value to associate with the item. It must be either a
+ string or an object that implements `toString()`. It will be passed to
+ click listeners. You can set this after creating the item to update its data
+ later.
+</api>
+
+<api name="context">
+@property {list}
+ A list of declarative contexts for which the menu item will appear in the
+ context menu. Contexts can be added by calling `context.add()` and removed by
+ called `context.remove()`. This property is meaningful only for items
+ contained in the top-level context menu.
+</api>
+
+<api name="parentMenu">
+@property {Menu}
+ The item's parent `Menu`, or `null` if the item is contained in the top-level
+ context menu. This property is read-only. To add the item to a new menu,
+ call that menu's `addItem()` method.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+ The content script or the array of content scripts associated with the menu
+ item during creation. This property is meaningful only for items contained in
+ the top-level context menu.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+ The URL of a content script or the array of such URLs associated with the menu
+ item during creation. This property is meaningful only for items contained in
+ the top-level context menu.
+</api>
+
+<api name="destroy">
+@method
+ Permanently removes the item from its parent menu and frees its resources.
+ The item must not be used afterward. If you need to remove the item from its
+ parent menu but use it afterward, call `removeItem()` on the parent menu
+ instead.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this menu item. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the menu item's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+</api>
+
+<api name="Menu">
+@class
+A labeled menu item that expands into a submenu.
+
+<api name="Menu">
+@constructor
+ Creates a labeled menu item that expands into a submenu.
+@param options {object}
+ An object with the following keys:
+ @prop label {string}
+ The item's label. It must either be a string or an object that implements
+ `toString()`.
+ @prop items {array}
+ An array of menu items that the menu will contain. Each must be an `Item`,
+ `Menu`, or `Separator`.
+ @prop [image] {string}
+ The menu's icon, a string URL. The URL can be remote, a reference to an
+ image in the add-on's `data` directory, or a data URI.
+ @prop [context] {value}
+ If the menu is contained in the top-level context menu, this declaratively
+ specifies the context under which the menu will appear; see Specifying
+ Contexts above. Ignored if the menu is contained in a submenu.
+ @prop [contentScript] {string,array}
+ If the menu is contained in the top-level context menu, this is the content
+ script or an array of content scripts that the menu can use to interact with
+ the page. Ignored if the menu is contained in a submenu.
+ @prop [contentScriptFile] {string,array}
+ If the menu is contained in the top-level context menu, this is the local
+ file URL of the content script or an array of such URLs that the menu can
+ use to interact with the page. Ignored if the menu is contained in a
+ submenu.
+ @prop [onMessage] {function}
+ If the menu is contained in the top-level context menu, this function will
+ be called when the content script calls `self.postMessage`. It will be
+ passed the data that was passed to `postMessage`. Ignored if the item is
+ contained in a submenu.
+</api>
+
+<api name="label">
+@property {string}
+ The menu's label. You can set this after creating the menu to update its
+ label later.
+</api>
+
+<api name="items">
+@property {array}
+ An array containing the items in the menu. The array is read-only, meaning
+ that modifications to it will not affect the menu. However, setting this
+ property to a new array will replace all the items currently in the menu with
+ the items in the new array.
+</api>
+
+<api name="image">
+@property {string}
+ The menu's icon, a string URL. The URL can be remote, a reference to an image
+ in the add-on's `data` directory, or a data URI. You can set this after
+ creating the menu to update its image later. To remove the menu's image, set
+ it to `null`.
+</api>
+
+<api name="context">
+@property {list}
+ A list of declarative contexts for which the menu will appear in the context
+ menu. Contexts can be added by calling `context.add()` and removed by called
+ `context.remove()`. This property is meaningful only for menus contained in
+ the top-level context menu.
+</api>
+
+<api name="parentMenu">
+@property {Menu}
+ The menu's parent `Menu`, or `null` if the menu is contained in the top-level
+ context menu. This property is read-only. To add the menu to a new menu,
+ call that menu's `addItem()` method.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+ The content script or the array of content scripts associated with the menu
+ during creation. This property is meaningful only for menus contained in the
+ top-level context menu.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+ The URL of a content script or the array of such URLs associated with the menu
+ during creation. This property is meaningful only for menus contained in the
+ top-level context menu.
+</api>
+
+<api name="addItem">
+@method
+ Appends a menu item to the end of the menu. If the item is already contained
+ in another menu or in the top-level context menu, it's automatically removed
+ first.
+@param item {Item,Menu,Separator}
+ The `Item`, `Menu`, or `Separator` to add to the menu.
+</api>
+
+<api name="removeItem">
+@method
+ Removes the given menu item from the menu. If the menu does not contain the
+ item, this method does nothing.
+@param item {Item,Menu,Separator}
+ The menu item to remove from the menu.
+</api>
+
+<api name="destroy">
+@method
+ Permanently removes the menu from its parent menu and frees its resources.
+ The menu must not be used afterward. If you need to remove the menu from its
+ parent menu but use it afterward, call `removeItem()` on the parent menu
+ instead.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this menu item. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the menu item's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+</api>
+
+<api name="Separator">
+@class
+A menu separator. Separators can be contained only in `Menu`s, not in the
+top-level context menu.
+
+<api name="Separator">
+@constructor
+ Creates a menu separator.
+</api>
+
+<api name="parentMenu">
+@property {Menu}
+ The separator's parent `Menu`. This property is read-only. To add the
+ separator to a new menu, call that menu's `addItem()` method.
+</api>
+
+<api name="destroy">
+@method
+ Permanently removes the separator from its parent menu and frees its
+ resources. The separator must not be used afterward. If you need to remove
+ the separator from its parent menu but use it afterward, call `removeItem()`
+ on the parent menu instead.
+</api>
+
+</api>
+
+<api name="PageContext">
+@class
+<api name="PageContext">
+@constructor
+ Creates a page context. See Specifying Contexts above.
+</api>
+</api>
+
+<api name="SelectionContext">
+@class
+<api name="SelectionContext">
+@constructor
+ Creates a context that occurs when a page contains a selection. See
+ Specifying Contexts above.
+</api>
+</api>
+
+<api name="SelectorContext">
+@class
+<api name="SelectorContext">
+@constructor
+ Creates a context that matches a given CSS selector. See Specifying Contexts
+ above.
+@param selector {string}
+ A CSS selector.
+</api>
+</api>
+
+<api name="URLContext">
+@class
+<api name="URLContext">
+@constructor
+ Creates a context that matches pages with particular URLs. See Specifying
+ Contexts above.
+@param matchPattern {string,array}
+ A [match pattern](packages/api-utils/docs/match-pattern.html) string or an array of
+ match pattern strings.
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/hotkeys.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/hotkeys.md
new file mode 100644
index 0000000..dfbe572
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/hotkeys.md
@@ -0,0 +1,74 @@
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+Some add-ons may wish to define keyboard shortcuts for certain operations. This
+module exposes an API to create those.
+
+<api name="Hotkey">
+@class
+
+Module exports `Hotkey` constructor allowing users to create a `hotkey` for the
+host application.
+
+<api name="Hotkey">
+@constructor
+Creates a hotkey who's `onPress` listener method is invoked when key combination
+defined by `hotkey` is pressed.
+
+Please note: If more than one `hotkey` is created for the same key
+combination, the listener is executed only on the last one created
+
+@param options {Object}
+ Options for the hotkey, with the following keys:
+
+@prop combo {String}
+Any function key: `"f1, f2, ..., f24"` or key combination in the format
+of `'modifier-key'`:
+
+ "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.
+
+@prop onPress {Function}
+Function that is invoked when the key combination `hotkey` is pressed.
+
+</api>
+<api name="destroy">
+@method
+Stops this instance of `Hotkey` from reacting on the key combinations. Once
+destroyed it can no longer be used.
+</api>
+</api>
+
+## Example ##
+
+ // Define keyboard shortcuts for showing and hiding a custom panel.
+ var { Hotkey } = require("hotkeys");
+
+ var showHotKey = Hotkey({
+ combo: "accel-shift-o",
+ onPress: function() {
+ showMyPanel();
+ }
+ });
+ var hideHotKey = Hotkey({
+ combo: "accel-alt-shift-o",
+ onPress: function() {
+ hideMyPanel();
+ }
+ });
+
+[Mozilla keyboard planning FAQ]:http://www.mozilla.org/access/keyboard/
+[keyboard shortcuts]:https://developer.mozilla.org/en/XUL_Tutorial/Keyboard_Shortcuts
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/notifications.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/notifications.md
new file mode 100644
index 0000000..5e76c1b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/notifications.md
@@ -0,0 +1,60 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+
+The `notifications` module allows you to display transient,
+[toaster](http://en.wikipedia.org/wiki/Toast_%28computing%29)-style
+desktop messages to the user.
+
+This API supports desktop notifications on Windows, OS X using
+[Growl](http://growl.info/), and Linux using libnotify. If the user's system
+does not support desktop notifications or if its notifications service is not
+running, then notifications made with this API are logged to Firefox's error
+console and, if the user launched Firefox from the command line, the terminal.
+
+Examples
+--------
+
+Here's a typical example. When the message is clicked, a string is logged to
+the console.
+
+ var notifications = require("notifications");
+ notifications.notify({
+ title: "Jabberwocky",
+ text: "'Twas brillig, and the slithy toves",
+ data: "did gyre and gimble in the wabe",
+ onClick: function (data) {
+ console.log(data);
+ // console.log(this.data) would produce the same result.
+ }
+ });
+
+This one displays an icon that's stored in the add-on's `data` directory. (See
+the [`self`](packages/addon-kit/docs/self.html) module documentation for more information.)
+
+ var notifications = require("notifications");
+ var self = require("self");
+ var myIconURL = self.data.url("myIcon.png");
+ notifications.notify({
+ text: "I have an icon!",
+ iconURL: myIconURL
+ });
+
+
+<api name="notify">
+@function
+ Displays a transient notification to the user.
+@param options {object}
+ An object with the following keys. Each is optional.
+ @prop [title] {string}
+ A string to display as the message's title.
+ @prop [text] {string}
+ A string to display as the body of the message.
+ @prop [iconURL] {string}
+ The URL of an icon to display inside the message. It may be a remote URL,
+ a data URI, or a URL returned by the [`self`](packages/addon-kit/docs/self.html)
+ module.
+ @prop [onClick] {function}
+ A function to be called when the user clicks the message. It will be passed
+ the value of `data`.
+ @prop [data] {string}
+ A string that will be passed to `onClick`.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/page-mod.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/page-mod.md
new file mode 100644
index 0000000..e523d48
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/page-mod.md
@@ -0,0 +1,367 @@
+<!-- contributed by Nickolay Ponomarev [asqueella@gmail.com] -->
+<!-- contributed by Myk Melez [myk@mozilla.org] -->
+<!-- contributed by Irakli Gozalishvil [gozala@mozilla.com] -->
+
+Overview
+--------
+The page-mod module enables add-on developers to execute scripts in the context
+of specific web pages. Most obviously you could use page-mod to dynamically
+modify the content of certain pages.
+
+The module exports a constructor function `PageMod` which creates a new page
+modification (or "mod" for short).
+
+A page mod does not modify its pages until those pages are loaded or reloaded.
+In other words, if your add-on is loaded while the user's browser is open, the
+user will have to reload any open pages that match the mod for the mod to affect
+them.
+
+To stop a page mod from making any more modifications, call its `destroy`
+method.
+
+Like all modules that interact with web content, page-mod uses content
+scripts that execute in the content process and defines a messaging API to
+communicate between the content scripts and the main add-on script. For more
+details on content scripting see the tutorial on [interacting with web
+content](dev-guide/addon-development/web-content.html).
+
+To create a PageMod the add-on developer supplies:
+
+* a set of rules to select the desired subset of web pages based on their URL.
+Each rule is specified using the
+[match-pattern](packages/api-utils/docs/match-pattern.html) syntax.
+
+* a set of content scripts to execute in the context of the desired pages.
+
+* a value for the onAttach option: this value is a function which will be
+called when a page is loaded that matches the ruleset. This is used to set up a
+communication channel between the add-on code and the content script.
+
+All these parameters are optional except for the ruleset, which must include
+at least one rule.
+
+The following add-on displays an alert whenever a page matching the ruleset is
+loaded:
+
+ var pageMod = require("page-mod");
+ pageMod.PageMod({
+ include: "*.org",
+ contentScript: 'window.alert("Page matches ruleset");'
+ });
+
+If you specify a value of "ready" or "end" for `contentScriptWhen`,
+as opposed to "start",
+then the content script can interact with the DOM itself:
+
+ var pageMod = require("page-mod");
+ pageMod.PageMod({
+ include: "*.org",
+ contentScriptWhen: 'end',
+ contentScript: 'document.body.innerHTML = ' +
+ ' "<h1>Page matches ruleset</h1>";'
+ });
+
+### Using `contentScriptFile` ###
+
+Most of the examples in this page define content scripts as strings,
+and use the `contentScript` option to assign them to page mods.
+
+In your code you will more often create content scripts in separate files
+under your add-on's `data` directory. Then you can use the
+[`self`](packages/addon-kit/docs/self.html) module to retrieve a URL pointing
+to the file, and assign this to the page-mod's `contentScriptFile`
+property.
+
+For example, if you save the content script
+file in your `data` directory as "myScript.js", you would assign it using
+code like:
+
+ var data = require("self").data;
+
+ var pageMod = require("page-mod");
+ pageMod.PageMod({
+ include: "*.org",
+ contentScriptWhen: 'end',
+ contentScriptFile: data.url("myScript.js")
+ });
+
+## Communicating With Content Scripts ##
+
+When a matching page is loaded the `PageMod` will call the function that the
+add-on code supplied to `onAttach`. The `PageMod` supplies one argument to
+this function: a `worker` object.
+
+The worker can be thought of as the add-on's end of
+a communication channel between the add-on code and the content scripts that
+have been attached to this page.
+
+Thus the add-on can pass messages to the content scripts by calling the
+worker's `postMessage` function and can receive messages from the content
+scripts by registering a function as a listener to the worker's `on` function.
+
+Note that if multiple matching pages are loaded simultaneously then each page
+is loaded into its own execution context with its own copy of the content
+scripts. In this case `onAttach` is called once for each loaded page, and the
+add-on code will have a separate worker for each page:
+
+![Multiple workers](static-files/media/multiple-workers.jpg)
+
+This is demonstrated in the following example:
+
+ var pageMod = require("page-mod");
+ var tabs = require("tabs");
+
+ var workers = [];
+
+ pageMod.PageMod({
+ include: ["http://www.mozilla*"],
+ contentScriptWhen: 'end',
+ contentScript: "onMessage = function onMessage(message) {" +
+ " window.alert(message);};",
+ onAttach: function onAttach(worker) {
+ if (workers.push(worker) == 3) {
+ workers[0].postMessage("The first worker!");
+ workers[1].postMessage("The second worker!");
+ workers[2].postMessage("The third worker!");
+ }
+ }
+ });
+
+ tabs.open("http://www.mozilla.com");
+ tabs.open("http://www.mozilla.org");
+ tabs.open("http://www.mozilla-europe.org");
+
+Here we specify a ruleset to match any URLs starting with
+"http://www.mozilla". When a page matches we add the supplied worker to
+an array, and when we have three workers in the array we send a message to
+each worker in turn, telling it the order in which it was attached. The
+worker just displays the message in an alert box.
+
+This shows that separate pages execute in separate contexts and that each
+context has its own communication channel with the add-on script.
+
+Note though that while there is a separate worker for each execution context,
+the worker is shared across all the content scripts associated with a single
+execution context. In the following example we pass two content scripts into
+the `PageMod`: these content scripts will share a worker instance.
+
+In the example each content script identifies itself to the add-on script
+by sending it a message using the global `postMessage` function. In the
+`onAttach` function the add-on code logs the fact that a new page is
+attached and registers a listener function that simply logs the message:
+
+
+ var pageMod = require("page-mod");
+ var data = require("self").data;
+ var tabs = require("tabs");
+
+ pageMod.PageMod({
+ include: ["http://www.mozilla*"],
+ contentScriptWhen: 'end',
+ contentScript: ["postMessage('Content script 1 is attached to '+ " +
+ "document.URL);",
+ "postMessage('Content script 2 is attached to '+ " +
+ "document.URL);"],
+ onAttach: function onAttach(worker) {
+ console.log("Attaching content scripts")
+ worker.on('message', function(data) {
+ console.log(data);
+ });
+ }
+ });
+
+ tabs.open("http://www.mozilla.com");
+
+The console output of this add-on is:
+
+<pre>
+ info: Attaching content scripts
+ info: Content script 1 is attached to http://www.mozilla.com/en-US/
+ info: Content script 2 is attached to http://www.mozilla.com/en-US/
+</pre>
+
+### Mapping workers to tabs ###
+
+The [`worker`](packages/api-utils/docs/content/worker.html) has a `tab`
+property which returns the tab associated with this worker. You can use this
+to access the [`tabs API`](packages/addon-kit/docs/tabs.html) for the tab
+associated with a specific page:
+
+ var pageMod = require("page-mod");
+ var tabs = require("tabs");
+
+ pageMod.PageMod({
+ include: ["*"],
+ onAttach: function onAttach(worker) {
+ console.log(worker.tab.title);
+ }
+ });
+
+### Attaching content scripts to tabs ###
+
+We've seen that the page mod API attaches content scripts to pages based on
+their URL. Sometimes, though, we don't care about the URL: we just want
+to execute a script on demand in the context of a particular tab.
+
+For example, we might want to run a script in the context of the currently
+active tab when the user clicks a widget: to block certain content, to
+change the font style, or to display the page's DOM structure.
+
+Using the `attach` method of the [`tab`](packages/addon-kit/docs/tabs.html)
+object, you can attach a set of content scripts to a particular tab. The
+scripts are executed immediately.
+
+The following add-on creates a widget which, when clicked, highlights all the
+`div` elements in the page loaded into the active tab:
+
+ var widgets = require("widget");
+ var tabs = require("tabs");
+
+ var widget = widgets.Widget({
+ id: "div-show",
+ label: "Show divs",
+ contentURL: "http://www.mozilla.org/favicon.ico",
+ onClick: function() {
+ tabs.activeTab.attach({
+ contentScript:
+ 'var divs = document.getElementsByTagName("div");' +
+ 'for (var i = 0; i < divs.length; ++i) {' +
+ 'divs[i].setAttribute("style", "border: solid red 1px;");' +
+ '}'
+ });
+ }
+ });
+
+## Destroying Workers ##
+
+Workers generate a `detach` event when their associated page is closed: that
+is, when the tab is closed or the tab's location changes. If
+you are maintaining a list of workers belonging to a page mod, you can use
+this event to remove workers that are no longer valid.
+
+For example, if you maintain a list of workers attached to a page mod:
+
+ var workers = [];
+
+ var pageMod = require("page-mod").PageMod({
+ include: ['*'],
+ contentScriptWhen: 'ready',
+ contentScriptFile: data.url('pagemod.js'),
+ onAttach: function(worker) {
+ workers.push(worker);
+ }
+ });
+
+You can remove workers when they are no longer valid by listening to `detach`:
+
+ var workers = [];
+
+ function detachWorker(worker, workerArray) {
+ var index = workerArray.indexOf(worker);
+ if(index != -1) {
+ workerArray.splice(index, 1);
+ }
+ }
+
+ var pageMod = require("page-mod").PageMod({
+ include: ['*'],
+ contentScriptWhen: 'ready',
+ contentScriptFile: data.url('pagemod.js'),
+ onAttach: function(worker) {
+ workers.push(worker);
+ worker.on('detach', function () {
+ detachWorker(this, workers);
+ });
+ }
+ });
+
+<api name="PageMod">
+@class
+A PageMod object. Once activated a page mod will execute the supplied content
+scripts in the context of any pages matching the pattern specified by the
+'include' property.
+<api name="PageMod">
+@constructor
+Creates a PageMod.
+@param options {object}
+ Options for the PageMod, with the following keys:
+ @prop include {string,array}
+ A match pattern string or an array of match pattern strings. These define
+ the pages to which the PageMod applies. See the
+ [match-pattern](packages/api-utils/docs/match-pattern.html) module for
+ a description of match pattern syntax.
+ At least one match pattern must be supplied.
+
+ @prop [contentScriptFile] {string,array}
+ The local file URLs of content scripts to load. Content scripts specified
+ by this option are loaded *before* those specified by the `contentScript`
+ option. Optional.
+ @prop [contentScript] {string,array}
+ The texts of content scripts to load. Content scripts specified by this
+ option are loaded *after* those specified by the `contentScriptFile` option.
+ Optional.
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the page is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the page has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+ @prop [onAttach] {function}
+A function to call when the PageMod attaches content scripts to
+a matching page. The function will be called with one argument, a `worker`
+object which the add-on script can use to communicate with the content scripts
+attached to the page in question.
+
+</api>
+
+<api name="include">
+@property {List}
+A [list](packages/api-utils/docs/list.html) of match pattern strings. These
+define the pages to which the page mod applies. See the
+[match-pattern](packages/api-utils/docs/match-pattern.html) module for a
+description of match patterns. Rules can be added to the list by calling its
+`add` method and removed by calling its `remove` method.
+
+</api>
+
+<api name="destroy">
+@method
+Stops the page mod from making any more modifications. Once destroyed the page
+mod can no longer be used. Note that modifications already made to open pages
+will not be undone.
+</api>
+
+<api name="attach">
+@event
+This event is emitted this event when the page-mod's content scripts are
+attached to a page whose URL matches the page-mod's `include` filter.
+
+@argument {Worker}
+The listener function is passed a [`Worker`](packages/api-utils/docs/content/worker.html) object that can be used to communicate
+with any content scripts attached to this page.
+</api>
+
+<api name="error">
+@event
+This event is emitted when an uncaught runtime error occurs in one of the page
+mod's content scripts.
+
+@argument {Error}
+Listeners are passed a single argument, the
+[Error](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error)
+object.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/page-worker.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/page-worker.md
new file mode 100644
index 0000000..06097ae
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/page-worker.md
@@ -0,0 +1,218 @@
+<!-- contributed by Felipe Gomes [felipc@gmail.com] -->
+
+The `page-worker` module provides a way to create a permanent, invisible page
+and access its DOM.
+
+Introduction
+------------
+
+The module exports a constructor function `Page`, which constructs a new page
+worker. A page worker may be destroyed, after which its memory is freed, and
+you must create a new instance to load another page.
+
+Page workers have associated content scripts, which are JavaScript scripts that
+have access to the content loaded into the pages. You can specify scripts to
+load for a page worker, and you communicate with those scripts over an
+asynchronous JSON pipe. For more information on content scripts, see
+[Working with Content Scripts](dev-guide/addon-development/web-content.html).
+
+Examples
+--------
+
+For conciseness, these examples create their content scripts as strings and use
+the `contentScript` property. In your own add-ons, you will probably want to
+create your content scripts in separate files and pass their URLs using the
+`contentScriptFile` property. See
+[Working with Content Scripts](dev-guide/addon-development/web-content.html)
+for more information.
+
+### Print all header titles from a Wikipedia article ###
+
+ var pageWorkers = require("page-worker");
+
+ // This content script sends header titles from the page to the add-on:
+ var script = "var elements = document.querySelectorAll('h2 > span'); " +
+ "for (var i = 0; i < elements.length; i++) { " +
+ " postMessage(elements[i].textContent) " +
+ "}";
+
+ // Create a page worker that loads Wikipedia:
+ pageWorkers.Page({
+ contentURL: "http://en.wikipedia.org/wiki/Internet",
+ contentScript: script,
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ console.log(message);
+ }
+ });
+
+The page worker's "message" event listener, specified by `onMessage`, will print
+all the titles it receives from the content script.
+
+<api name="Page">
+@class
+A `Page` object loads the page specified by its `contentURL` option and
+executes any content scripts that have been supplied to it in the
+`contentScript` and `contentScriptFile` options.
+
+The page is not displayed to the user.
+
+The page worker is loaded as soon as the `Page` object is created and stays
+loaded until its `destroy` method is called or the add-on is unloaded.
+
+<api name="Page">
+@constructor
+ Creates an uninitialized page worker instance.
+@param [options] {object}
+ The *`options`* parameter is optional, and if given it should be an object
+ with any of the following keys:
+ @prop [contentURL] {string}
+ The URL of the content to load in the panel.
+ @prop [allow] {object}
+ An object with keys to configure the permissions on the page worker. The
+ boolean key `script` controls if scripts from the page are allowed to run.
+ `script` defaults to true.
+ @prop [contentScriptFile] {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+ Content scripts specified by this option are loaded *before* those specified
+ by the `contentScript` option. See
+ [Working with Content Scripts](dev-guide/addon-development/web-content.html)
+ for help on setting this property.
+ @prop [contentScript] {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load. Content scripts specified by this option are loaded *after* those
+ specified by the `contentScriptFile` option.
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the page is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the page has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+ @prop [onMessage] {function}
+ Use this to add a listener to the page worker's `message` event.
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send events to the content script using the `port.emit` function
+* receive events from the content script using the `port.on` function
+
+See the guide to
+<a href="dev-guide/addon-development/content-scripts/using-port.html">
+communicating using <code>port</code></a> for details.
+</api>
+
+<api name="contentURL">
+@property {string}
+The URL of the content loaded.
+</api>
+
+<api name="allow">
+@property {object}
+ A object describing permissions for the content. It contains a single key
+ named `script` whose value is a boolean that indicates whether or not to
+ execute script in the content. `script` defaults to true.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+A local file URL or an array of local file URLs of content scripts to load.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+A string or an array of strings containing the texts of content scripts to
+load.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+ When to load the content scripts. This may have one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the page is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the page has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+</api>
+
+<api name="destroy">
+@method
+Unloads the page worker. After you destroy a page worker, its memory is freed
+and you must create a new instance if you need to load another page.
+</api>
+
+<api name="postMessage">
+@method
+Sends a message to the content scripts.
+@param message {value}
+The message to send. Must be JSON-able.
+</api>
+
+<api name="on">
+@method
+Registers an event listener with the page worker. See
+[Working with Events](dev-guide/addon-development/events.html) for help with
+events.
+@param type {string}
+The type of event to listen for.
+@param listener {function}
+The listener function that handles the event.
+</api>
+
+<api name="removeListener">
+@method
+Unregisters an event listener from the page worker.
+@param type {string}
+The type of event for which `listener` was registered.
+@param listener {function}
+The listener function that was registered.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this page worker. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the page worker's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>
+</api>
+
+<api name="error">
+@event
+This event is emitted when an uncaught runtime error occurs in one of the
+page worker's content scripts.
+
+@argument {Error}
+Listeners are passed a single argument, the
+[Error](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error)
+object.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/panel.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/panel.md
new file mode 100644
index 0000000..45eea2f
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/panel.md
@@ -0,0 +1,301 @@
+<!-- contributed by Myk Melez [myk@mozilla.org] -->
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `panel` module creates floating modal "popup dialogs" that appear on top of
+web content and browser chrome and persist until dismissed by users or programs.
+Panels are useful for presenting temporary interfaces to users in a way that is
+easier for users to ignore and dismiss than a modal dialog, since panels are
+hidden the moment users interact with parts of the application interface outside
+them.
+
+The module exports a single constructor function `Panel` which constructs a
+new panel.
+
+A panel's content is loaded as soon as it is created, before the panel is shown,
+and the content remains loaded when a panel is hidden, so it is possible
+to keep a panel around in the background, updating its content as appropriate
+in preparation for the next time it is shown.
+
+Your add-on can receive notifications when a panel is shown or hidden by
+listening to its `show` and `hide` events.
+
+Panels have associated content scripts, which are JavaScript scripts that have
+access to the content loaded into the panels. An add-on can specify one or
+more content scripts to load for a panel, and the add-on can communicate with
+those scripts either using the `message` event or by using user-defined
+events. See
+[Working with Content Scripts](dev-guide/addon-development/web-content.html)
+for more information.
+
+The panel's default style is different for each operating system.
+For example, suppose a panel's content is specified with the following HTML:
+
+<script type="syntaxhighlighter" class="brush: html"><![CDATA[
+<h1>Default Style</h1>
+
+This is what a panel with no custom styling looks like.
+]]>
+</script>
+
+On OS X it will look like this:
+
+<img class="image-center" src="static-files/media/screenshots/default-panel-osx.png"
+alt="OS X panel default style">
+<br>
+
+On Windows 7 it will look like this:
+
+<img class="image-center" src="static-files/media/screenshots/default-panel-windows.png"
+alt="Windows 7 panel default style">
+<br>
+
+On Ubuntu it will look like this:
+
+<img class="image-center" src="static-files/media/screenshots/default-panel-ubuntu.png"
+alt="Ubuntu panel default style">
+<br>
+
+This helps to ensure that the panel's style is consistent with the dialogs
+displayed by Firefox and other applications, but means you need to take care
+when applying your own styles. For example, if you set the panel's
+`background-color` property to `white` and do not set the `color` property,
+then the panel's text will be invisible on OS X although it looks fine on Ubuntu.
+
+Examples
+--------
+
+Create and show a simple panel with content from the `data/` directory:
+
+ var data = require("self").data;
+ var panel = require("panel").Panel({
+ contentURL: data.url("foo.html")
+ });
+
+ panel.show();
+
+The tutorial section on
+[web content](dev-guide/addon-development/web-content.html) has
+a more complex example using panels.
+
+<api name="Panel">
+@class
+The Panel object represents a floating modal dialog that can by an add-on to
+present user interface content.
+
+Once a panel object has been created it can be shown and hidden using its
+`show()` and `hide()` methods. Once a panel is no longer needed it can be
+deactivated using `destroy()`.
+
+The content of a panel is specified using the `contentURL` option. An add-on
+can interact with the content of a panel using content scripts which it
+supplies in the `contentScript` and/or `contentScriptFile` options. For example,
+a content script could create a menu and send the user's selection to the
+add-on.
+
+<api name="Panel">
+@constructor
+Creates a panel.
+@param options {object}
+ Options for the panel, with the following keys:
+ @prop [width] {number}
+ The width of the panel in pixels. Optional.
+ @prop [height] {number}
+ The height of the panel in pixels. Optional.
+ @prop [contentURL] {string}
+ The URL of the content to load in the panel.
+ @prop [allow] {object}
+ An optional object describing permissions for the content. It should
+ contain a single key named `script` whose value is a boolean that indicates
+ whether or not to execute script in the content. `script` defaults to true.
+ @prop [contentScriptFile] {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+ Content scripts specified by this property are loaded *before* those
+ specified by the `contentScript` property.
+ @prop [contentScript] {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load. Content scripts specified by this property are loaded *after* those
+ specified by the `contentScriptFile` property.
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the panel is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the panel has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+ @prop [onMessage] {function}
+ Include this to listen to the panel's `message` event.
+ @prop [onShow] {function}
+ Include this to listen to the panel's `show` event.
+ @prop [onHide] {function}
+ Include this to listen to the panel's `hide` event.
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send events to the content script using the `port.emit` function
+* receive events from the content script using the `port.on` function
+
+See the guide to
+<a href="dev-guide/addon-development/content-scripts/using-port.html">
+communicating using <code>port</code></a> for details.
+</api>
+
+<api name="isShowing">
+@property {boolean}
+Tells if the panel is currently shown or not. This property is read-only.
+</api>
+
+<api name="height">
+@property {number}
+The height of the panel in pixels.
+</api>
+
+<api name="width">
+@property {number}
+The width of the panel in pixels.
+</api>
+
+<api name="contentURL">
+@property {string}
+The URL of the content loaded in the panel.
+</api>
+
+<api name="allow">
+@property {object}
+An object describing permissions for the content. It contains a single key
+named `script` whose value is a boolean that indicates whether or not to execute
+script in the content.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+A local file URL or an array of local file URLs of content scripts to load.
+Content scripts specified by this property are loaded *before* those
+specified by the `contentScript` property.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+A string or an array of strings containing the texts of content scripts to
+load. Content scripts specified by this property are loaded *after* those
+specified by the `contentScriptFile` property.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+When to load the content scripts. This may have one of the following
+values:
+
+* "start": load content scripts immediately after the document
+element for the panel is inserted into the DOM, but before the DOM content
+itself has been loaded
+* "ready": load content scripts once DOM content has been loaded,
+corresponding to the
+[DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+event
+* "end": load content scripts once all the content (DOM, JS, CSS,
+images) for the panel has been loaded, at the time the
+[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+fires
+
+</api>
+
+<api name="destroy">
+@method
+Destroys the panel, unloading any content that was loaded in it. Once
+destroyed, the panel can no longer be used. If you just want to hide
+the panel and might show it later, use `hide` instead.
+</api>
+
+<api name="postMessage">
+@method
+Sends a message to the content scripts.
+@param message {value}
+The message to send. Must be stringifiable to JSON.
+</api>
+
+<api name="show">
+@method
+Displays the panel.
+</api>
+
+<api name="hide">
+@method
+Stops displaying the panel.
+</api>
+
+<api name="resize">
+@method
+Resizes the panel.
+@param width {number}
+The new width of the panel in pixels.
+@param height {number}
+The new height of the panel in pixels.
+</api>
+
+<api name="on">
+@method
+ Registers an event listener with the panel.
+@param type {string}
+ The type of event to listen for.
+@param listener {function}
+ The listener function that handles the event.
+</api>
+
+<api name="removeListener">
+@method
+ Unregisters an event listener from the panel.
+@param type {string}
+ The type of event for which `listener` was registered.
+@param listener {function}
+ The listener function that was registered.
+</api>
+
+<api name="show">
+@event
+This event is emitted when the panel is shown.
+</api>
+
+<api name="hide">
+@event
+This event is emitted when the panel is hidden.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this panel. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the panel's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="error">
+@event
+This event is emitted when an uncaught runtime error occurs in one of the
+panel's content scripts.
+
+@argument {Error}
+Listeners are passed a single argument, the
+[Error](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error)
+object.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/passwords.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/passwords.md
new file mode 100644
index 0000000..6a4b246
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/passwords.md
@@ -0,0 +1,564 @@
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `passwords` module allows add-ons to interact with Firefox's
+[Password Manager](http://support.mozilla.com/en-US/kb/Remembering%20passwords)
+to add, retrieve and remove stored credentials.
+
+A _credential_ is the set of information a user supplies to authenticate
+herself with a service. Typically a credential consists of a username and a
+password.
+
+Using this module you can:
+
+1. **Search** for credentials which have been stored in the Password Manager.
+ You can then use the credentials to access their related service (for
+ example, by logging into a web site).
+
+2. **Store** credentials in the Password Manager. You can store different sorts
+ of credentials, as outlined in the "Credentials" section below.
+
+3. **Remove** stored credentials from the Password Manager.
+
+## Credentials ##
+
+In this API, credentials are represented by objects.
+
+You create credential objects to pass into the API, and the API also returns
+credential objects to you. The sections below explain both the properties you
+should define on credential objects and the properties you can expect on
+credential objects returned by the API.
+
+All credential objects include `username` and `password` properties. Different
+sorts of stored credentials include various additional properties, as
+outlined in this section.
+
+You can use the Passwords API with three sorts of credentials:
+
+* Add-on credentials
+* HTML form credentials
+* HTTP Authentication credentials
+
+### Add-on Credential ###
+
+These are associated with your add-on rather than a particular web site.
+They contain the following properties:
+
+<table>
+<colgroup>
+<col width="25%">
+</colgroup>
+<tr>
+ <td>
+ <code>username</code>
+ </td>
+ <td>
+ The username.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>password</code>
+ </td>
+ <td>
+ The password.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>url</code>
+ </td>
+ <td>
+ <p>For an add-on credential, this property is of the form:<br><code>
+ addon:&lt;addon-id&gt;</code>, where <code>&lt;addon-id&gt;</code>
+ is the add-on's
+ <a href="dev-guide/addon-development/program-id.html">
+ Program ID</a>.</p>
+ <p>You don't supply this value when storing an add-on credential: it is
+ automatically generated for you. However, you can use it to work out
+ which stored credentials belong to your add-on by comparing it with the
+ <code>uri</code> property of the
+ <a href="packages/addon-kit/docs/self.html"><code>self</code></a>
+ module.</p>
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>realm</code>
+ </td>
+ <td>
+ <p>You can use this as a name for the credential, to distinguish
+ it from any other credentials you've stored.</p>
+ <p>The realm is displayed
+ in Firefox's Password Manager, under "Site", in brackets after the URL.
+ For example, if the realm for a credential is "User Registration", then
+ its "Site" field will look something like:</p>
+ <code>addon:jid0-01mBBFyu0ZAXCFuB1JYKooSTKIc (User Registration)</code>
+ </td>
+</tr>
+
+</table>
+
+### HTML Form Credential ###
+
+If a web service uses HTML forms to authenticate its users, then the
+corresponding credential is an HTML Form credential.
+
+It contains the following properties:
+
+<table>
+<colgroup>
+<col width="25%">
+</colgroup>
+<tr>
+ <td>
+ <code>username</code>
+ </td>
+ <td>
+ The username.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>password</code>
+ </td>
+ <td>
+ The password.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>url</code>
+ </td>
+ <td>
+ The URL for the web service which requires the credential.
+ You should omit anything after the hostname and (optional) port.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>formSubmitURL</code>
+ </td>
+ <td>
+ The value of the form's "action" attribute.
+ You should omit anything after the hostname and (optional) port.
+ If the form doesn't contain an "action" attribute, this property should
+ match the <code>url</code> property.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>usernameField</code>
+ </td>
+ <td>
+ The value of the "name" attribute for the form's username field.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>passwordField</code>
+ </td>
+ <td>
+ The value of the "name" attribute for the form's password field.
+ </td>
+</tr>
+
+</table>
+
+So: given a form at `http://www.example.com/login`
+with the following HTML:
+
+<script type="syntaxhighlighter" class="brush: html"><![CDATA[
+<form action="http://login.example.com/foo/authenticate.cgi">
+ <div>Please log in.</div>
+ <label>Username:</label> <input type="text" name="uname">
+ <label>Password:</label> <input type="password" name="pword">
+</form>
+]]>
+</script>
+
+The corresponding values for the credential (excluding username and password)
+should be:
+
+<pre>
+ url: "http://www.example.com"
+ formSubmitURL: "http://login.example.com"
+ usernameField: "uname"
+ passwordField: "pword"
+</pre>
+
+Note that for both `url` and `formSubmitURL`, the portion of the URL after the
+hostname is omitted.
+
+### HTTP Authentication Credential ###
+
+These are used to authenticate the user to a web site
+which uses HTTP Authentication, as detailed in
+[RFC 2617](http://tools.ietf.org/html/rfc2617).
+They contain the following properties:
+
+<table>
+<colgroup>
+<col width="25%">
+</colgroup>
+<tr>
+ <td>
+ <code>username</code>
+ </td>
+ <td>
+ The username.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>password</code>
+ </td>
+ <td>
+ The password.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>url</code>
+ </td>
+ <td>
+ The URL for the web service which requires the credential.
+ You should omit anything after the hostname and (optional) port.
+ </td>
+</tr>
+
+<tr>
+ <td>
+ <code>realm</code>
+ </td>
+ <td>
+ <p>The WWW-Authenticate response header sent by the server may include a
+ "realm" field as detailed in
+ <a href="http://tools.ietf.org/html/rfc2617">RFC 2617</a>. If it does,
+ this property contains the value for the "realm" field. Otherwise, it is
+ omitted.</p>
+ <p>The realm is displayed in Firefox's Password Manager, under "Site",
+ in brackets after the URL.</p>
+ </td>
+</tr>
+
+</table>
+
+So: if a web server at `http://www.example.com` requested authentication with
+a response code like this:
+
+<pre>
+ HTTP/1.0 401 Authorization Required
+ Server: Apache/1.3.27
+ WWW-Authenticate: Basic realm="ExampleCo Login"
+</pre>
+
+The corresponding values for the credential (excluding username and password)
+should be:
+
+<pre>
+ url: "http://www.example.com"
+ realm: "ExampleCo Login"
+</pre>
+
+## onComplete and onError ##
+
+This API is explicitly asynchronous, so all its functions take two callback
+functions as additional options: `onComplete` and `onError`.
+
+`onComplete` is called when the operation has completed successfully and
+`onError` is called when the function encounters an error.
+
+Because the `search` function is expected to return a list of matching
+credentials, its `onComplete` option is mandatory. Because the other functions
+don't return a value in case of success their `onComplete` options are
+optional.
+
+For all functions, `onError` is optional.
+
+<api name="search">
+@function
+
+This function is used to retrieve a credential, or a list of credentials,
+stored in the Password Manager.
+
+You pass it any subset of the possible properties a credential can contain.
+Credentials which match all the properties you supplied are returned as an
+argument to the `onComplete` callback.
+
+So if you pass in an empty set of properties, all stored credentials are
+returned:
+
+ function show_all_passwords() {
+ require("passwords").search({
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(function(credential) {
+ console.log(credential.username);
+ console.log(credential.password);
+ });
+ }
+ });
+ }
+
+If you pass it a single property, only credentials matching that property are
+returned:
+
+ function show_passwords_for_joe() {
+ require("passwords").search({
+ username: "joe",
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(function(credential) {
+ console.log(credential.username);
+ console.log(credential.password);
+ });
+ }
+ });
+ }
+
+If you pass more than one property, returned credentials must match all of
+them:
+
+ function show_google_password_for_joe() {
+ require("passwords").search({
+ username: "joe",
+ url: "https://www.google.com",
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(function(credential) {
+ console.log(credential.username);
+ console.log(credential.password);
+ });
+ }
+ });
+ }
+
+To retrieve only credentials associated with your add-on, use the `url`
+property, initialized from `self.uri`:
+
+ function show_my_addon_passwords() {
+ require("passwords").search({
+ url: require("self").uri,
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(function(credential) {
+ console.log(credential.username);
+ console.log(credential.password);
+ });
+ }
+ });
+ }
+
+@param options {object}
+The `options` object may contain any credential properties. It is used to
+restrict the set of credentials returned by the `search` function.
+
+See "Credentials" above for details on what these properties should be.
+
+Additionally, `options` must contain a function assigned to its `onComplete`
+property: this is called when the function completes and is passed the set of
+credentials retrieved.
+
+`options` may contain a function assigned to its `onError` property, which is
+called if the function encounters an error. `onError` is passed the error as an
+[nsIException](https://developer.mozilla.org/en/nsIException) object.
+
+@prop [username] {string}
+The username for the credential.
+
+@prop [password] {string}
+The password for the credential.
+
+@prop [url] {string}
+The URL associated with the credential.
+
+@prop [formSubmitURL] {string}
+The URL an HTML form credential is submitted to.
+
+@prop [realm] {string}
+For HTTP Authentication credentials, the realm for which the credential was
+requested. For add-on credentials, a name for the credential.
+
+@prop [usernameField] {string}
+The value of the `name` attribute for the user name input field in a form.
+
+@prop [passwordField] {string}
+The value of the `name` attribute for the password input field in a form.
+
+@prop onComplete {function}
+The callback function that is called once the function completes successfully.
+It is passed all the matching credentials as a list. This is the only
+mandatory option.
+
+@prop [onError] {function}
+The callback function that is called if the function failed. The
+callback is passed an `error` containing a reason of a failure: this is an
+[nsIException](https://developer.mozilla.org/en/nsIException) object.
+
+</api>
+
+<api name="store">
+@function
+
+This function is used to store a credential in the Password Manager.
+
+It takes an `options` object as an argument: this contains all the properties
+for the new credential.
+
+As different sorts of credentials contain different properties, the
+appropriate options differ depending on the sort of credential being stored.
+
+To store an add-on credential:
+
+ require("passwords").store({
+ realm: "User Registration",
+ username: "joe",
+ password: "SeCrEt123",
+ });
+
+To store an HTML form credential:
+
+ require("passwords").store({
+ url: "http://www.example.com",
+ formSubmitURL: "http://login.example.com",
+ username: "joe",
+ usernameField: "uname",
+ password: "SeCrEt123",
+ passwordField: "pword"
+ });
+
+To store an HTTP Authentication credential:
+
+ require("passwords").store({
+ url: "http://www.example.com",
+ realm: "ExampleCo Login",
+ username: "joe",
+ password: "SeCrEt123",
+ });
+
+See "Credentials" above for more details on how to set these properties.
+
+The options parameter may also include `onComplete` and `onError`
+callback functions, which are called when the function has completed
+successfully and when it encounters an error, respectively. These options
+are both optional.
+
+@param options {object}
+An object containing the properties of the credential to be stored, and
+optional `onComplete` and `onError` callback functions.
+
+@prop username {string}
+The username for the credential.
+
+@prop password {string}
+The password for the credential.
+
+@prop [url] {string}
+The URL to which the credential applies. Omitted for add-on
+credentials.
+
+@prop [formSubmitURL] {string}
+The URL a form-based credential was submitted to. Omitted for add-on
+credentials and HTTP Authentication credentials.
+
+@prop [realm] {string}
+For HTTP Authentication credentials, the realm for which the credential was
+requested. For add-on credentials, a name for the credential.
+
+@prop [usernameField] {string}
+The value of the `name` attribute for the username input in a form.
+Omitted for add-on credentials and HTTP Authentication credentials.
+
+@prop [passwordField] {string}
+The value of the `name` attribute for the password input in a form.
+Omitted for add-on credentials and HTTP Authentication credentials.
+
+@prop [onComplete] {function}
+The callback function that is called once the function completes successfully.
+
+@prop [onError] {function}
+The callback function that is called if the function failed. The
+callback is passed an `error` argument: this is an
+[nsIException](https://developer.mozilla.org/en/nsIException) object.
+
+</api>
+
+<api name="remove">
+@function
+
+Removes a stored credential. You supply it all the properties of the credential
+to remove, along with optional `onComplete` and `onError` callbacks.
+
+Because you must supply all the credential's properties, it may be convenient
+to call `search` first, and use its output as the input to `remove`. For
+example, to remove all of joe's stored credentials:
+
+ require("passwords").search({
+ username: "joe",
+ onComplete: function onComplete(credentials) {
+ credentials.forEach(require("passwords").remove);
+ })
+ });
+
+To change an existing credential just call `store` after `remove` succeeds:
+
+ require("passwords").remove({
+ realm: "User Registration",
+ username: "joe",
+ password: "SeCrEt123"
+ onComplete: function onComplete() {
+ require("passwords").store({
+ realm: "User Registration",
+ username: "joe",
+ password: "{{new password}}"
+ })
+ }
+ });
+
+@param options {object}
+
+An object containing all the properties of the credential to be removed,
+and optional `onComplete` and `onError` callback functions.
+
+@prop username {string}
+The username for the credential.
+
+@prop password {string}
+The password for the credential.
+
+@prop [url] {string}
+The URL to which the credential applies. Omitted for add-on
+credentials.
+
+@prop [formSubmitURL] {string}
+The URL a form-based credential was submitted to. Omitted for add-on
+credentials and HTTP Authentication credentials.
+
+@prop [realm] {string}
+For HTTP Authentication credentials, the realm for which the credential was
+requested. For add-on credentials, a name for the credential.
+
+@prop [usernameField] {string}
+The value of the `name` attribute for the username input in a form.
+Omitted for add-on credentials and HTTP Authentication credentials.
+
+@prop [passwordField] {string}
+The value of the `name` attribute for the password input in a form.
+Omitted for add-on credentials and HTTP Authentication credentials.
+
+@prop [onComplete] {function}
+The callback function that is called once the function has completed
+successfully.
+
+@prop [onError] {function}
+The callback function that is called if the function failed. The
+callback is passed an `error` argument: this is an
+[nsIException](https://developer.mozilla.org/en/nsIException) object.
+
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/private-browsing.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/private-browsing.md
new file mode 100644
index 0000000..cad6166
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/private-browsing.md
@@ -0,0 +1,46 @@
+<!-- contributed by Paul O'Shannessy [paul@oshannessy.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `private-browsing` module allows you to access Firefox's private browsing
+mode, detecting if it is active and when its state changes.
+
+This module is available in all applications. However, only Firefox will ever
+transition into or out of private browsing mode. For all other applications,
+`pb.isActive` will always be `false`, and none of the events will be emitted.
+
+<api name="isActive">
+@property {boolean}
+ This read-only boolean is true if private browsing mode is turned on.
+</api>
+
+<api name="activate">
+@function
+ Turns on private browsing mode.
+</api>
+
+<api name="deactivate">
+@function
+ Turns off private browsing mode.
+</api>
+
+<api name="start">
+@event
+Emitted immediately after the browser enters private browsing mode.
+
+ var pb = require("private-browsing");
+ pb.on("start", function() {
+ // Do something when the browser starts private browsing mode.
+ });
+
+</api>
+
+<api name="stop">
+@event
+Emitted immediately after the browser exits private browsing mode.
+
+ var pb = require("private-browsing");
+ pb.on("stop", function() {
+ // Do something when the browser stops private browsing mode.
+ });
+</api> \ No newline at end of file
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/request.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/request.md
new file mode 100644
index 0000000..77bea43
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/request.md
@@ -0,0 +1,192 @@
+The `request` module lets you make simple yet powerful network requests.
+
+<api name="Request">
+@class
+The `Request` object is used to make `GET` or `POST` network requests. It is
+constructed with a URL to which the request is sent. Optionally the user may
+specify a collection of headers and content to send alongside the request and
+a callback which will be executed once the request completes.
+
+Once a `Request` object has been created a `GET` request can be executed by
+calling its `get()` method, or a `POST` request by calling its `post()` method.
+
+When the server completes the request, the `Request` object emits a "complete"
+event. Registered event listeners are passed a `Response` object.
+
+Each `Request` object is designed to be used once. Once `GET` or `POST` are
+called, attempting to call either will throw an error.
+
+Since the request is not being made by any particular website, requests made
+here are not subject to the same-domain restriction that requests made in web
+pages are subject to.
+
+With the exception of `response`, all of a `Request` object's properties
+correspond with the options in the constructor. Each can be set by simply
+performing an assignment. However, keep in mind that the same validation rules
+that apply to `options` in the constructor will apply during assignment. Thus,
+each can throw if given an invalid value.
+
+The example below shows how to use Request to get the most recent public tweet.
+
+ var Request = require("request").Request;
+ var latestTweetRequest = Request({
+ url: "http://api.twitter.com/1/statuses/public_timeline.json",
+ onComplete: function (response) {
+ var tweet = response.json[0];
+ console.log("User: " + tweet.user.screen_name);
+ console.log("Tweet: " + tweet.text);
+ }
+ });
+
+ // Be a good consumer and check for rate limiting before doing more.
+ Request({
+ url: "http://api.twitter.com/1/account/rate_limit_status.json",
+ onComplete: function (response) {
+ if (response.json.remaining_hits) {
+ latestTweetRequest.get();
+ } else {
+ console.log("You have been rate limited!");
+ }
+ }
+ }).get();
+
+<api name="Request">
+@constructor
+This constructor creates a request object that can be used to make network
+requests. The constructor takes a single parameter `options` which is used to
+set several properties on the resulting `Request`.
+@param options {object}
+ @prop url {string}
+ This is the url to which the request will be made.
+
+ @prop [onComplete] {function}
+ This function will be called when the request has received a response (or in
+ terms of XHR, when `readyState == 4`). The function is passed a `Response`
+ object.
+
+ @prop [headers] {object}
+ An unordered collection of name/value pairs representing headers to send
+ with the request.
+
+ @prop [content] {string,object}
+ The content to send to the server. If `content` is a string, it should be
+ URL-encoded (use `encodeURIComponent`). If `content` is an object, it
+ should be a collection of name/value pairs. Nested objects & arrays should
+ encode safely.
+
+ For `GET` requests, the query string (`content`) will be appended to the
+ URL. For `POST` requests, the query string will be sent as the body of the
+ request.
+
+ @prop [contentType] {string}
+ The type of content to send to the server. This explicitly sets the
+ `Content-Type` header. The default value is `application/x-www-form-urlencoded`.
+
+ @prop [overrideMimeType] {string}
+ Use this string to override the MIME type returned by the server in the
+ response's Content-Type header. You can use this to treat the content as a
+ different MIME type, or to force text to be interpreted using a specific
+ character.
+
+ For example, if you're retrieving text content which was encoded as
+ ISO-8859-1 (Latin 1), it will be given a content type of "utf-8" and
+ certain characters will not display correctly. To force the response to
+ be interpreted as Latin-1, use `overrideMimeType`:
+
+ var Request = require("request").Request;
+ var quijote = Request({
+ url: "http://www.latin1files.org/quijote.txt",
+ overrideMimeType: "text/plain; charset=latin1",
+ onComplete: function (response) {
+ console.log(response.text);
+ }
+ });
+
+ quijote.get();
+
+</api>
+
+<api name="url">
+@property {string}
+</api>
+
+<api name="headers">
+@property {object}
+</api>
+
+<api name="content">
+@property {string,object}
+</api>
+
+<api name="contentType">
+@property {string}
+</api>
+
+<api name="response">
+@property {Response}
+</api>
+
+<api name="get">
+@method
+Make a `GET` request.
+@returns {Request}
+</api>
+
+<api name="post">
+@method
+Make a `POST` request.
+@returns {Request}
+</api>
+
+<api name="complete">
+@event
+The `Request` object emits this event when the request has completed and a
+response has been received.
+
+@argument {Response}
+Listener functions are passed the response to the request as a `Response` object.
+</api>
+
+</api>
+
+
+<api name="Response">
+@class
+The Response object contains the response to a network request issued using a
+`Request` object. It is returned by the `get()` or `post()` method of a
+`Request` object.
+
+All members of a `Response` object are read-only.
+<api name="text">
+@property {string}
+The content of the response as plain text.
+</api>
+
+<api name="json">
+@property {object}
+The content of the response as a JavaScript object. The value will be `null`
+if the document cannot be processed by `JSON.parse`.
+</api>
+
+<api name="status">
+@property {string}
+The HTTP response status code (e.g. *200*).
+</api>
+
+<api name="statusText">
+@property {string}
+The HTTP response status line (e.g. *OK*).
+</api>
+
+<api name="headers">
+@property {object}
+The HTTP response headers represented as key/value pairs.
+
+To print all the headers you can do something like this:
+
+ for (var headerName in response.headers) {
+ console.log(headerName + " : " + response.headers[headerName]);
+ }
+
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/selection.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/selection.md
new file mode 100644
index 0000000..62a3d64
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/selection.md
@@ -0,0 +1,86 @@
+<!-- contributed by Eric H. Jung [eric.jung@yahoo.com] -->
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `selection` module provides a means to get and set text and HTML selections
+in the current Firefox page. It can also observe new selections.
+
+Registering for Selection Notifications
+---------------------------------------
+
+To be notified when the user makes a selection, register a listener for the
+"select" event. Each listener will be called after a selection is made.
+
+ function myListener() {
+ console.log("A selection has been made.");
+ }
+ var selection = require("selection");
+ selection.on('select', myListener);
+
+ // You can remove listeners too.
+ selection.removeListener('select', myListener);
+
+Iterating Over Discontiguous Selections
+---------------------------------------
+
+Discontiguous selections can be accessed by iterating over the `selection`
+module itself. Each iteration yields a `Selection` object from which `text`,
+`html`, and `isContiguous` properties can be accessed.
+
+
+Examples
+--------
+
+Log the current contiguous selection as text:
+
+ var selection = require("selection");
+ if (selection.text)
+ console.log(selection.text);
+
+Log the current discontiguous selections as HTML:
+
+ var selection = require("selection");
+ if (!selection.isContiguous) {
+ for (var subselection in selection) {
+ console.log(subselection.html);
+ }
+ }
+
+Surround HTML selections with delimiters:
+
+ var selection = require("selection");
+ selection.on('select', function () {
+ selection.html = "\\\" + selection.html + "///";
+ });
+
+<api name="text">
+@property {string}
+ Gets or sets the current selection as plain text. Setting the selection
+ removes all current selections, inserts the specified text at the location of
+ the first selection, and selects the new text. Getting the selection when
+ there is no current selection returns `null`. Setting the selection when there
+ is no current selection throws an exception. Getting the selection when
+ `isContiguous` is `true` returns the text of the first selection.
+</api>
+
+<api name="html">
+@property {string}
+ Gets or sets the current selection as HTML. Setting the selection removes all
+ current selections, inserts the specified text at the location of the first
+ selection, and selects the new text. Getting the selection when there is no
+ current selection returns `null`. Setting the selection when there is no
+ current selection throws an exception. Getting the selection when
+ `isContiguous` is `true` returns the text of the first selection.
+</api>
+
+<api name="isContiguous">
+@property {boolean}
+ `true` if the current selection is a single, contiguous selection, and `false`
+ if there are two or more discrete selections, each of which may or may not be
+ spatially adjacent. (Discontiguous selections can be created by the user with
+ Ctrl+click-and-drag.)
+</api>
+
+<api name="select">
+@event
+ This event is emitted whenever the user makes a new selection in a page.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/self.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/self.md
new file mode 100644
index 0000000..e76ad50
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/self.md
@@ -0,0 +1,73 @@
+The `self` module provides access to data that is bundled with the add-on
+as a whole. It also provides access to the
+[Program ID](dev-guide/addon-development/program-id.html), a value which is
+unique for each add-on.
+
+Note that the `self` module is completely different from the global `self`
+object accessible to content scripts, which is used by a content script to
+[communicate with the add-on code](dev-guide/addon-development/content-scripts/using-port.html).
+
+<api name="id">
+@property {string}
+This property is a printable string that is unique for each add-on. It comes
+from the `id` property set in the `package.json` file in the main package
+(i.e. the package in which you run `cfx xpi`). While not generally of use to
+add-on code directly, it can be used by internal API code to index local
+storage and other resources that are associated with a particular add-on.
+Eventually, this ID will be unspoofable (see
+[JEP 118](https://wiki.mozilla.org/Labs/Jetpack/Reboot/JEP/118) for details).
+</api>
+
+<api name="name">
+@property {string}
+This property contains the add-on's short name. It comes from the `name`
+property in the main package's `package.json` file.
+</api>
+
+<api name="version">
+@property {string}
+This property contains the add-on's version string. It comes from the
+`version` property set in the `package.json` file in the main package.
+</api>
+
+<api name="data">
+@property {object}
+The `data` object is used to access data that was bundled with the add-on.
+This data lives in the main package's `data/` directory, immediately below
+the `package.json` file. All files in this directory will be copied into the
+XPI and made available through the `data` object.
+
+The [Package Specification](dev-guide/addon-development/package-spec.html)
+section explains the `package.json` file.
+
+<api name="data.load">
+@method
+The `data.load(NAME)` method returns the contents of an embedded data file,
+as a string. It is most useful for data that will be modified or parsed in
+some way, such as JSON, XML, plain text, or perhaps an HTML template. For
+data that can be displayed directly in a content frame, use `data.url(NAME)`.
+@param name {string} The filename to be read, relative to the
+ package's `data` directory. Each package that uses the `self` module
+ will see its own `data` directory.
+@returns {string}
+</api>
+
+<api name="data.url">
+@method
+The `data.url(NAME)` method returns a URL instance that points at an embedded
+data file. It is most useful for data that can be displayed directly in a
+content frame. The URL instance can be passed to a content frame constructor,
+such as the Panel:
+
+ var self = require("self");
+ var myPanel = require("panel").Panel({
+ contentURL: self.data.url("my-panel-content.html")
+ });
+ myPanel.show();
+
+@param name {string} The filename to be read, relative to the
+ package's `data` directory. Each package that uses the `self` module
+ will see its own `data` directory.
+@returns {URL}
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/simple-prefs.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/simple-prefs.md
new file mode 100644
index 0000000..824a151
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/simple-prefs.md
@@ -0,0 +1,70 @@
+<!-- contributed by Erik Vold [erikvvold@gmail.com] -->
+
+#### *Experimental*
+
+The `simple-prefs` module lets you easily and persistently store preferences
+across application restarts, which can be configured by users in the
+Add-ons Manager.
+
+Introduction
+------------
+
+With the simple preferences module you can store booleans, integers, and string
+values.
+
+
+Inline Options & Default Values
+-------------------------------
+
+In order to have a `options.xul` (for inline options) generated, or a
+`defaults/preferences/prefs.js` for default preferences, you will need to
+define the preferences in your `package.json`, like so:
+
+ {
+ "fullName": "Example Add-on",
+ ...
+ "preferences": [{
+ "name": "somePreference",
+ "title": "Some preference title",
+ "type": "string",
+ "value": "this is the default string value"
+ }]
+ }
+
+
+<api name="prefs">
+@property {object}
+ *experimental* A persistent object private to your add-on. Properties with boolean,
+ number, and string values will be persisted in the Mozilla preferences system.
+</api>
+
+
+<api name="on">
+@function
+ *experimental* Registers an event `listener` that will be called when a preference is changed.
+
+**Example:**
+
+ function onPrefChange(prefName) {
+ console.log("The " + prefName + " preference changed.");
+ }
+ require("simple-prefs").on("somePreference", onPrefChange);
+ require("simple-prefs").on("someOtherPreference", onPrefChange);
+
+
+@param prefName {String}
+ The name of the preference to watch for changes.
+@param listener {Function}
+ The listener function that processes the event.
+</api>
+
+<api name="removeListener">
+@function
+ *experimental* Unregisters an event `listener` for the specified preference.
+
+@param prefName {String}
+ The name of the preference to watch for changes.
+@param listener {Function}
+ The listener function that processes the event.
+</api>
+
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/simple-storage.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/simple-storage.md
new file mode 100644
index 0000000..e34e333
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/simple-storage.md
@@ -0,0 +1,125 @@
+The `simple-storage` module lets you easily and persistently store data across
+application restarts. If you're familiar with [DOM storage][] on the Web, it's
+kind of like that, but for add-ons.
+
+[DOM storage]: https://developer.mozilla.org/en/DOM/Storage
+
+
+Introduction
+------------
+
+The simple storage module exports an object called `storage` that is persistent
+and private to your add-on. It's a normal JavaScript object, and you can treat
+it as you would any other.
+
+To store a value, just assign it to a property on `storage`:
+
+ var ss = require("simple-storage");
+ ss.storage.myArray = [1, 1, 2, 3, 5, 8, 13];
+ ss.storage.myBoolean = true;
+ ss.storage.myNull = null;
+ ss.storage.myNumber = 3.1337;
+ ss.storage.myObject = { a: "foo", b: { c: true }, d: null };
+ ss.storage.myString = "O frabjous day!";
+
+You can store array, boolean, number, object, null, and string values. If you'd
+like to store other types of values, you'll first have to convert them to
+strings or another one of these types.
+
+Be careful to set properties on the `storage` object and not the module itself:
+
+ // This is no good!
+ var ss = require("simple-storage");
+ ss.foo = "I will not be saved! :(";
+
+
+Quotas
+------
+
+The simple storage available to your add-on is limited. Currently this limit is
+about five megabytes (5,242,880 bytes). You can choose to be notified when you
+go over quota, and you should respond by reducing the amount of data in storage.
+If the user quits the application while you are over quota, all data stored
+since the last time you were under quota will not be persisted. You should not
+let that happen.
+
+To listen for quota notifications, register a listener for the `"OverQuota"`
+event. It will be called when your storage goes over quota.
+
+ function myOnOverQuotaListener() {
+ console.log("Uh oh.");
+ }
+ ss.on("OverQuota", myOnOverQuotaListener);
+
+Listeners can also be removed:
+
+ ss.removeListener("OverQuota", myOnOverQuotaListener);
+
+To find out how much of your quota you're using, check the module's `quotaUsage`
+property. It indicates the percentage of quota your storage occupies. If
+you're within your quota, it's a number from 0 to 1, inclusive, and if you're
+over, it's a number greater than 1.
+
+Therefore, when you're notified that you're over quota, respond by removing
+storage until your `quotaUsage` is less than or equal to 1. Which particular
+data you remove is up to you. For example:
+
+ ss.storage.myList = [ /* some long array */ ];
+ ss.on("OverQuota", function () {
+ while (ss.quotaUsage > 1)
+ ss.storage.myList.pop();
+ });
+
+
+Private Browsing
+----------------
+
+*This section applies only to add-ons running on Firefox.*
+
+If your storage is related to your users' Web history, personal information, or
+other sensitive data, your add-on should respect [private browsing mode][SUMO].
+While private browsing mode is active, you should not store any sensitive data.
+
+Because any kind of data can be placed into simple storage, support for private
+browsing is not built into the module. Instead, use the
+[`private-browsing`](packages/addon-kit/docs/private-browsing.html) module to
+check private browsing status and respond accordingly.
+
+For example, the URLs your users visit should not be stored during private
+browsing. If your add-on records the URL of the selected tab, here's how you
+might handle that:
+
+ ss.storage.history = [];
+ var privateBrowsing = require("private-browsing");
+ if (!privateBrowsing.active) {
+ var url = getSelectedTabURL();
+ ss.storage.history.push(url);
+ }
+
+For more information on supporting private browsing, see its [Mozilla Developer
+Network documentation][MDN]. While that page does not apply specifically to
+SDK-based add-ons (and its code samples don't apply at all), you should follow
+its guidance on best practices and policies.
+
+[SUMO]: http://support.mozilla.com/en-US/kb/Private+Browsing
+[MDN]: https://developer.mozilla.org/En/Supporting_private_browsing_mode
+
+
+<api name="storage">
+@property {object}
+ A persistent object private to your add-on. Properties with array, boolean,
+ number, object, null, and string values will be persisted.
+</api>
+
+<api name="quotaUsage">
+@property {number}
+ A number in the range [0, Infinity) that indicates the percentage of quota
+ occupied by storage. A value in the range [0, 1] indicates that the storage
+ is within quota. A value greater than 1 indicates that the storage exceeds
+ quota.
+</api>
+
+<api name="OverQuota">
+@event
+The module emits this event when your add-on's storage goes over its quota.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/tabs.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/tabs.md
new file mode 100644
index 0000000..a6a858f
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/tabs.md
@@ -0,0 +1,381 @@
+<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `tabs` module provides easy access to tabs and tab-related events.
+
+The module itself can be used like a basic list of all opened
+tabs across all windows. In particular, you can enumerate it:
+
+ var tabs = require('tabs');
+ for each (var tab in tabs)
+ console.log(tab.title);
+
+You can also access individual tabs by index:
+
+ var tabs = require('tabs');
+
+ tabs.on('ready', function () {
+ console.log('first: ' + tabs[0].title);
+ console.log('last: ' + tabs[tabs.length-1].title);
+ });
+
+You can open a new tab, specifying various properties including location:
+
+ var tabs = require("tabs");
+ tabs.open("http://www.example.com");
+
+You can register event listeners to be notified when tabs open, close, finish
+loading DOM content, or are made active or inactive:
+
+ var tabs = require("tabs");
+
+ // Listen for tab openings.
+ tabs.on('open', function onOpen(tab) {
+ myOpenTabs.push(tab);
+ });
+
+ // Listen for tab content loads.
+ tabs.on('ready', function(tab) {
+ console.log('tab is loaded', tab.title, tab.url)
+ });
+
+You can get and set various properties of tabs (but note that properties
+ relating to the tab's content, such as the URL, will not contain valid
+values until after the tab's `ready` event fires). By setting the `url`
+property you can load a new page in the tab:
+
+ var tabs = require("tabs");
+ tabs.on('activate', function(tab) {
+ tab.url = "http://www.example.com";
+ });
+
+You can attach a [content script](dev-guide/addon-development/web-content.html)
+to the page hosted in a tab, and use that to access and manipulate the page's
+content:
+
+ var tabs = require("tabs");
+
+ tabs.on('activate', function(tab) {
+ tab.attach({
+ contentScript: 'self.postMessage(document.body.innerHTML);',
+ onMessage: function (message) {
+ console.log(message);
+ }
+ });
+ });
+
+<api name="activeTab">
+@property {Tab}
+
+The currently active tab in the active window. This property is read-only. To
+activate a `Tab` object, call its `activate` method.
+
+**Example**
+
+ // Get the active tab's title.
+ var tabs = require("tabs");
+ console.log("title of active tab is " + tabs.activeTab.title);
+</api>
+
+<api name="length">
+@property {number}
+The number of open tabs across all windows.
+</api>
+
+<api name="open">
+@function
+Opens a new tab. The new tab will open in the active window or in a new window,
+depending on the `inNewWindow` option.
+
+**Example**
+
+ var tabs = require("tabs");
+
+ // Open a new tab on active window and make tab active.
+ tabs.open("http://www.mysite.com");
+
+ // Open a new tab in a new window and make it active.
+ tabs.open({
+ url: "http://www.mysite.com",
+ inNewWindow: true
+ });
+
+ // Open a new tab on active window in the background.
+ tabs.open({
+ url: "http://www.mysite.com",
+ inBackground: true
+ });
+
+ // Open a new tab as an app tab and do something once it's open.
+ tabs.open({
+ url: "http://www.mysite.com",
+ isPinned: true,
+ onOpen: function onOpen(tab) {
+ // do stuff like listen for content
+ // loading.
+ }
+ });
+
+@param options {object}
+An object containing configurable options for how and where the tab will be
+opened, as well as a listeners for the tab events.
+
+If the only option being used is `url`, then a bare string URL can be passed to
+`open` instead of adding at a property of the `options` object.
+
+@prop [url] {string}
+String URL to be opened in the new tab.
+This is a required property.
+
+@prop [inNewWindow] {boolean}
+If present and true, a new browser window will be opened and the URL will be
+opened in the first tab in that window. This is an optional property.
+
+@prop [inBackground] {boolean}
+If present and true, the new tab will be opened to the right of the active tab
+and will not be active. This is an optional property.
+
+@prop [isPinned] {boolean}
+If present and true, then the new tab will be pinned as an
+[app tab](http://support.mozilla.com/en-US/kb/what-are-app-tabs).
+
+@prop [onOpen] {function}
+A callback function that will be registered for 'open' event.
+This is an optional property.
+@prop [onClose] {function}
+A callback function that will be registered for 'close' event.
+This is an optional property.
+@prop [onReady] {function}
+A callback function that will be registered for 'ready' event.
+This is an optional property.
+@prop [onActivate] {function}
+A callback function that will be registered for 'activate' event.
+This is an optional property.
+@prop [onDeactivate] {function}
+A callback function that will be registered for 'deactivate' event.
+This is an optional property.
+</api>
+
+<api name="Tab">
+@class
+A `Tab` instance represents a single open tab. It contains various tab
+properties, several methods for manipulation, as well as per-tab event
+registration.
+
+Tabs emit all the events described in the Events section. Listeners are
+passed the `Tab` object that triggered the event.
+
+<api name="title">
+@property {string}
+The title of the page currently loaded in the tab.
+This property can be set to change the tab title.
+</api>
+
+<api name="url">
+@property {String}
+The URL of the page currently loaded in the tab.
+This property can be set to load a different URL in the tab.
+</api>
+
+<api name="favicon">
+@property {string}
+The URL of the favicon for the page currently loaded in the tab.
+This property is read-only.
+</api>
+
+<api name="index">
+@property {integer}
+The index of the tab relative to other tabs in the application window.
+This property can be set to change its relative position.
+</api>
+
+<api name="isPinned">
+@property {boolean}
+Whether or not tab is pinned as an [app tab][].
+This property is read-only.
+[app tab]:http://support.mozilla.com/en-US/kb/what-are-app-tabs
+</api>
+
+<api name="getThumbnail">
+@property {method}
+Returns thumbnail data URI of the page currently loaded in this tab.
+</api>
+
+<api name="pin">
+@method
+Pins this tab as an [app tab][].
+[app tab]:http://support.mozilla.com/en-US/kb/what-are-app-tabs
+</api>
+
+<api name="unpin">
+@method
+Unpins this tab.
+</api>
+
+<api name="close">
+@method
+Closes this tab.
+
+@param [callback] {function}
+A function to be called when the tab finishes its closing process.
+This is an optional argument.
+</api>
+
+<api name="reload">
+@method
+Reloads this tab.
+</api>
+
+<api name="activate">
+@method
+Makes this tab active, which will bring this tab to the foreground.
+</api>
+
+<api name="attach">
+@method
+ Create a page mod and attach it to the document in the tab.
+
+**Example**
+
+ var tabs = require("tabs");
+
+ tabs.on('ready', function(tab) {
+ tab.attach({
+ contentScript:
+ 'document.body.style.border = "5px solid red";'
+ });
+ });
+
+@param options {object}
+ Options for the page mod, with the following keys:
+
+@prop [contentScriptFile] {string,array}
+ The local file URLs of content scripts to load. Content scripts specified
+ by this option are loaded *before* those specified by the `contentScript`
+ option. Optional.
+@prop [contentScript] {string,array}
+ The texts of content scripts to load. Content scripts specified by this
+ option are loaded *after* those specified by the `contentScriptFile` option.
+ Optional.
+@prop [onMessage] {function}
+ A function called when the page mod receives a message from content scripts.
+ Listeners are passed a single argument, the message posted from the
+ content script.
+
+@returns {Worker}
+ See [Content Scripts guide](dev-guide/addon-development/web-content.html)
+ to learn how to use the `Worker` object to communicate with the content script.
+
+</api>
+
+<api name="close">
+@event
+
+This event is emitted when the tab is closed. It's also emitted when the
+tab's window is closed.
+
+@argument {Tab}
+Listeners are passed the tab object.
+</api>
+
+<api name="ready">
+@event
+
+This event is emitted when the DOM for the tab's content is ready. It is
+equivalent to the `DOMContentLoaded` event for the given content page.
+
+A single tab will emit this event every time the DOM is loaded: so it will be
+emitted again if the tab's location changes or the content is reloaded.
+
+After this event has been emitted, all properties relating to the tab's
+content can be used.
+
+@argument {Tab}
+Listeners are passed the tab object.
+</api>
+
+<api name="activate">
+@event
+
+This event is emitted when the tab is made active.
+
+@argument {Tab}
+Listeners are passed the tab object.
+</api>
+
+<api name="deactivate">
+@event
+
+This event is emitted when the tab is made inactive.
+
+@argument {Tab}
+Listeners are passed the tab object.
+</api>
+
+</api>
+
+<api name="open">
+@event
+
+This event is emitted when a new tab is opened. This does not mean that
+the content has loaded, only that the browser tab itself is fully visible
+to the user.
+
+Properties relating to the tab's content (for example: `title`, `favicon`,
+and `url`) will not be correct at this point. If you need to access these
+properties, listen for the `ready` event:
+
+ var tabs = require("tabs");
+ tabs.on('open', function(tab){
+ tab.on('ready', function(tab){
+ console.log(tab.url);
+ });
+ });
+
+@argument {Tab}
+Listeners are passed the tab object that just opened.
+</api>
+
+<api name="close">
+@event
+
+This event is emitted when a tab is closed. When a window is closed
+this event will be emitted for each of the open tabs in that window.
+
+@argument {Tab}
+Listeners are passed the tab object that has closed.
+</api>
+
+<api name="ready">
+@event
+
+This event is emitted when the DOM for a tab's content is ready.
+It is equivalent to the `DOMContentLoaded` event for the given content page.
+
+A single tab will emit this event every time the DOM is loaded: so it will be
+emitted again if the tab's location changes or the content is reloaded.
+
+After this event has been emitted, all properties relating to the tab's
+content can be used.
+
+@argument {Tab}
+Listeners are passed the tab object that has loaded.
+</api>
+
+<api name="activate">
+@event
+
+This event is emitted when an inactive tab is made active.
+
+@argument {Tab}
+Listeners are passed the tab object that has become active.
+</api>
+
+<api name="deactivate">
+@event
+
+This event is emitted when the active tab is made inactive.
+
+@argument {Tab}
+Listeners are passed the tab object that has become inactive.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/timers.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/timers.md
new file mode 100644
index 0000000..bc36750
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/timers.md
@@ -0,0 +1,48 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+<!-- contributed by Irakli Gozalishvil [gozala@mozilla.com] -->
+
+The `timers` module provides access to web-like timing functionality.
+
+<api name="setTimeout">
+@function
+ Schedules `callback` to be called in `ms` milliseconds. Any additional
+ arguments are passed straight through to the callback.
+@returns {integer}
+ An ID that can later be used to undo this scheduling, if `callback` hasn't yet
+ been called.
+@param callback {function}
+ Function to be called.
+@param ms {integer}
+ Interval in milliseconds after which the function will be called.
+</api>
+
+<api name="clearTimeout">
+@function
+ Given an ID returned from `setTimeout()`, prevents the callback with the ID
+ from being called (if it hasn't yet been called).
+@param ID {integer}
+ An ID returned from `setTimeout()`.
+</api>
+
+<api name="setInterval">
+@function
+ Schedules `callback` to be called repeatedly every `ms` milliseconds. Any
+ additional arguments are passed straight through to the callback.
+@returns {integer}
+ An ID that can later be used to unschedule the callback.
+@param callback {function}
+ Function to be called.
+@param ms {integer}
+ Interval in milliseconds at which the function will be called.
+</api>
+
+<api name="clearInterval">
+@function
+ Given an ID returned from `setInterval()`, prevents the callback with the ID
+ from being called again.
+@param ID {integer}
+ An ID returned from `setInterval()`.
+</api>
+
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/widget.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/widget.md
new file mode 100644
index 0000000..aa12cbe
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/widget.md
@@ -0,0 +1,692 @@
+<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `widget` module provides your add-on with a simple user interface that is
+consistent with other add-ons and blends in well with Firefox.
+
+## Introduction ##
+
+"Widgets" are small pieces of content that live in the Firefox 4
+[add-on bar](https://developer.mozilla.org/en/The_add-on_bar).
+They can be simple icons or complex web pages. You can attach
+[panels](packages/addon-kit/docs/panel.html) to them that open when they're
+clicked, or you can define a custom click handler to perform some other action,
+like opening a web page in a tab.
+
+There are a few advantages to using widgets over an ad hoc user interface.
+First, your users will be accustomed to interacting with add-ons via widgets and
+the add-on bar. Second, it allows Firefox to treat your interface as a
+first-class citizen. For example, in the future Firefox may allow the user to
+drag widgets from the add-on bar to other toolbars. By exposing your interface
+as a widget, your add-on would automatically inherit such functionality.
+
+## Creation and Content ##
+
+Widgets can contain images or arbitrary web content. You can include this
+content inline as a string by using the `content` property, or point to content
+using a URL with the `contentURL` property.
+
+For example, this widget contains an image, so it looks like a simple icon:
+
+ require("widget").Widget({
+ id: "mozilla-icon",
+ label: "My Mozilla Widget",
+ contentURL: "http://www.mozilla.org/favicon.ico"
+ });
+
+Upon creation, the widget is automatically added to the add-on bar.
+You can set the width of a widget, but the height is fixed so as to fit in the
+add-on bar. If the content is an image, it is automatically scaled to be 16x16
+pixels.
+
+This widget contains an entire web page:
+
+ require("widget").Widget({
+ id: "hello-display",
+ label: "My Hello Widget",
+ content: "Hello!",
+ width: 50
+ });
+
+Widgets are quite small by default, so this example used the `width` property to
+grow it in order to show all the text.
+
+As with many SDK APIs, communication with the content inside your widgets is
+handled by [content scripts](dev-guide/addon-development/web-content.html).
+So, for example, to be notified when your widget's content has loaded, you can
+make a small script that calls back to the widget when it finishes loading.
+
+## Attaching Panels to Widgets ##
+
+You can supply a [panel](packages/addon-kit/docs/panel.html) to the widget's
+constructor: if you do this, the panel is automatically displayed when the
+user clicks the widget.
+
+<!-- The icon the widget displays, shown in the screenshot, is taken from the
+Circular icon set, http://prothemedesign.com/circular-icons/ which is made
+available under the Creative Commons Attribution 2.5 Generic License:
+http://creativecommons.org/licenses/by/2.5/ -->
+
+<img class="image-right" src="static-files/media/screenshots/widget-panel-clock.png"
+alt="Panel attached to a widget">
+
+ data = require("self").data
+
+ var clockPanel = require("panel").Panel({
+ width:215,
+ height:160,
+ contentURL: data.url("clock.html")
+ });
+
+ require("widget").Widget({
+ id: "open-clock-btn",
+ label: "Clock",
+ contentURL: data.url("History.png"),
+ panel: clockPanel
+ });
+
+Note that this is, at the moment, the only way you can attach a panel to a widget.
+
+You must supply the panel in the widget's constructor for it to work. If you
+assign the panel to the widget after construction, the panel can still be shown
+but will not be anchored to the widget:
+
+ data = require("self").data
+
+ var clockPanel = require("panel").Panel({
+ width:215,
+ height:160,
+ contentURL: data.url("clock.html")
+ });
+
+ widget = require("widget").Widget({
+ id: "open-clock-btn",
+ label: "Clock",
+ contentURL: data.url("History.png")
+ });
+
+ widget.panel = clockPanel;
+
+ // Will not be anchored
+ widget.panel.show();
+
+Also, if you try to call `panel.show()` inside your widget's `click` event
+listener, the panel will not be anchored:
+
+ data = require("self").data
+
+ var clockPanel = require("panel").Panel({
+ width:215,
+ height:160,
+ contentURL: data.url("clock.html")
+ });
+
+ require("widget").Widget({
+ id: "open-clock-btn",
+ label: "Clock",
+ contentURL: data.url("History.png"),
+ panel: clockPanel,
+ onClick: function() {
+ // Will not be anchored
+ this.panel.show();
+ }
+ });
+
+See [bug 638142](https://bugzilla.mozilla.org/show_bug.cgi?id=638142).
+
+## Examples ##
+
+For conciseness, these examples create their content scripts as strings and use
+the `contentScript` property. In your own add-ons, you will probably want to
+create your content scripts in separate files and pass their URLs using the
+`contentScriptFile` property. See
+[Working with Content Scripts](dev-guide/addon-development/web-content.html) for more
+information.
+
+ var widgets = require("widget");
+
+ // A basic click-able image widget.
+ widgets.Widget({
+ id: "google-link",
+ label: "Widget with an image and a click handler",
+ contentURL: "http://www.google.com/favicon.ico",
+ onClick: function() {
+ require("tabs").activeTab.url = "http://www.google.com/";
+ }
+ });
+<br>
+
+ // A widget that changes display on mouseover.
+ widgets.Widget({
+ id: "mouseover-effect",
+ label: "Widget with changing image on mouseover",
+ contentURL: "http://www.yahoo.com/favicon.ico",
+ onMouseover: function() {
+ this.contentURL = "http://www.bing.com/favicon.ico";
+ },
+ onMouseout: function() {
+ this.contentURL = "http://www.yahoo.com/favicon.ico";
+ }
+ });
+<br>
+
+ // A widget that updates content on a timer.
+ widgets.Widget({
+ id: "auto-update-widget",
+ label: "Widget that updates content on a timer",
+ content: "0",
+ contentScript: 'setTimeout(function() {' +
+ ' document.body.innerHTML++;' +
+ '}, 2000)',
+ contentScriptWhen: "ready"
+ });
+<br>
+
+ // A widget that loads a random Flickr photo every 5 minutes.
+ widgets.Widget({
+ id: "random-flickr",
+ label: "Random Flickr Photo Widget",
+ contentURL: "http://www.flickr.com/explore/",
+ contentScriptWhen: "ready",
+ contentScript: 'postMessage(document.querySelector(".pc_img").src);' +
+ 'setTimeout(function() {' +
+ ' document.location = "http://www.flickr.com/explore/";' +
+ '}, 5 * 60 * 1000);',
+ onMessage: function(imgSrc) {
+ this.contentURL = imgSrc;
+ },
+ onClick: function() {
+ require("tabs").activeTab.url = this.contentURL;
+ }
+ });
+<br>
+
+ // A widget created with a specified width, that grows.
+ let myWidget = widgets.Widget({
+ id: "widget-effect",
+ label: "Wide widget that grows wider on a timer",
+ content: "I'm getting longer.",
+ width: 50,
+ });
+ require("timers").setInterval(function() {
+ myWidget.width += 10;
+ }, 1000);
+<br>
+
+ // A widget communicating bi-directionally with a content script.
+ let widget = widgets.Widget({
+ id: "message-test",
+ label: "Bi-directional communication!",
+ content: "<foo>bar</foo>",
+ contentScriptWhen: "ready",
+ contentScript: 'on("message", function(message) {' +
+ ' alert("Got message: " + message);' +
+ '});' +
+ 'postMessage("ready");',
+ onMessage: function(message) {
+ if (message == "ready")
+ widget.postMessage("me too");
+ }
+ });
+
+<api-name="Widget">
+@class
+Represents a widget object.
+
+<api name="Widget">
+@constructor {options}
+ Creates a new widget. The widget is immediately added to the add-on bar.
+
+@param options {object}
+ An object with the following keys:
+
+ @prop label {string}
+ A required string description of the widget used for accessibility,
+ title bars, and error reporting.
+
+ @prop id {string}
+ Mandatory string used to identify your widget in order to save its
+ location when the user moves it in the browser.
+ This string has to be unique and must not be changed over time.
+
+ @prop [content] {string}
+ An optional string value containing the displayed content of the widget.
+ It may contain HTML. Widgets must have either the `content` property or the
+ `contentURL` property set.
+
+ If the content is an image, it is automatically scaled to be 16x16 pixels.
+
+ @prop [contentURL] {string}
+ An optional string URL to content to load into the widget. This can be
+ [local content](dev-guide/addon-development/web-content.html) or remote
+ content, an image or web content. Widgets must have either the `content`
+ property or the `contentURL` property set.
+
+ If the content is an image, it is automatically scaled to be 16x16 pixels.
+
+ @prop [panel] {Panel}
+ An optional [panel](packages/addon-kit/docs/panel.html) to open when the
+ user clicks on the widget. Note: If you also register a "click" listener,
+ it will be called instead of the panel being opened. However, you can show
+ the panel from the listener by calling `this.panel.show()`.
+
+ @prop [width] {integer}
+ Optional width in pixels of the widget. If not given, a default width is
+ used.
+
+ @prop [onClick] {function}
+ Include this to listen to the widget's `click` event.
+
+ @prop [onMessage] {function}
+ Include this to listen to the widget's `message` event.
+
+ @prop [onMouseover] {function}
+ Include this to listen to the widget's `mouseover` event.
+
+ @prop [onMouseout] {function}
+ Include this to listen to the widget's `mouseout` event.
+
+ @prop [onAttach] {function}
+ Include this to listen to the widget's `attach` event.
+
+ @prop [tooltip] {string}
+ Optional text to show when the user's mouse hovers over the widget. If not
+ given, the `label` is used.
+
+ @prop [allow] {object}
+ An optional object describing permissions for the content. It should
+ contain a single key named `script` whose value is a boolean that indicates
+ whether or not to execute script in the content. `script` defaults to true.
+
+ @prop [contentScriptFile] {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+ Content scripts specified by this property are loaded *before* those
+ specified by the `contentScript` property.
+
+ @prop [contentScript] {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load. Content scripts specified by this property are loaded *after* those
+ specified by the `contentScriptFile` property.
+
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the widget is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the widget has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+</api>
+
+<api name="destroy">
+@method
+ Removes the widget from the add-on bar.
+</api>
+
+<api name="postMessage">
+@method
+ Sends a message to the widget's content scripts.
+@param data {value}
+ The message to send.
+ The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="on">
+@method
+ Registers an event listener with the widget.
+@param type {string}
+ The type of event to listen for.
+@param listener {function}
+ The listener function that handles the event.
+</api>
+
+<api name="removeListener">
+@method
+ Unregisters an event listener from the widget.
+@param type {string}
+ The type of event for which `listener` was registered.
+@param listener {function}
+ The listener function that was registered.
+</api>
+
+<api name="getView">
+@method
+ Retrieve a `WidgetView` instance of this widget relative to a browser window.
+@param window {BrowserWindow}
+ The [BrowserWindow](packages/addon-kit/docs/windows.html) instance to match.
+@returns {WidgetView}
+ A `WidgetView` instance associated with the browser window. Any changes
+ subsequently applied to this object will only be applied to the widget
+ attached to that window.
+</api>
+
+<api name="label">
+@property {string}
+ The widget's label. Read-only.
+</api>
+
+<api name="content">
+@property {string}
+ A string containing the widget's content. It can contain HTML. Setting it
+ updates the widget's appearance immediately. However, if the widget was
+ created using `contentURL`, then this property is meaningless, and setting it
+ has no effect.
+</api>
+
+<api name="contentURL">
+@property {string}
+ The URL of content to load into the widget. This can be
+ [local content](dev-guide/addon-development/web-content.html) or remote
+ content, an image or web content. Setting it updates the widget's appearance
+ immediately. However, if the widget was created using `content`, then this
+ property is meaningless, and setting it has no effect.
+</api>
+
+<api name="panel">
+@property {Panel}
+ A [panel](packages/addon-kit/docs/panel.html) to open when the user clicks on
+ the widget.
+</api>
+
+<api name="width">
+@property {number}
+ The widget's width in pixels. Setting it updates the widget's appearance
+ immediately.
+</api>
+
+<api name="tooltip">
+@property {string}
+ The text of the tooltip that appears when the user hovers over the widget.
+</api>
+
+<api name="allow">
+@property {object}
+ A object describing permissions for the content. It contains a single key
+ named `script` whose value is a boolean that indicates whether or not to
+ execute script in the content.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+ When to load the content scripts. This may have one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the widget is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the widget has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send events to the content script using the `port.emit` function
+* receive events from the content script using the `port.on` function
+
+See the guide to
+<a href="dev-guide/addon-development/content-scripts/using-port.html">
+communicating using <code>port</code></a> for details.
+</api>
+
+<api name="attach">
+@event
+This event is emitted when a new `WidgetView` object is created using the
+`getView()` function.
+</api>
+
+<api name="click">
+@event
+This event is emitted when the widget is clicked.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this widget. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the widget's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="mouseover">
+@event
+This event is emitted when the user moves the mouse over the widget.
+</api>
+
+<api name="mouseout">
+@event
+This event is emitted when the user moves the mouse away from the widget.
+</api>
+
+</api>
+
+
+<api-name="WidgetView">
+@class
+Represents a widget instance specific to one browser window.
+
+Anything you do to an instance of this object will only be applied to the
+instance attached to its browser window: widget instances attached to other
+browser windows will be unaffected.
+
+By contrast, any changes you make to an instance of the normal `Widget` class
+will be applied across all browser windows.
+
+This class has all the same methods, attributes and events as the `Widget`
+class except for the `getView` method and the `attach` event.
+
+In this example `WidgetView` is used to display different content for
+`http` and `https` schemes:
+
+ // A widget that update its content specifically to each window.
+ let tabs = require("tabs");
+ let windows = require("windows").browserWindows;
+ let widget = widgets.Widget({
+ id: "window-specific-test",
+ label: "Widget with content specific to each window",
+ content: " ",
+ width: 50
+ });
+ // Observe tab switch or document changes in each existing tab:
+ function updateWidgetState(tab) {
+ let view = widget.getView(tab.window);
+ if (!view) return;
+ // Update widget displayed text:
+ view.content = tab.url.match(/^https/) ? "Secured" : "Unsafe";
+ }
+ tabs.on('ready', updateWidgetState);
+ tabs.on('activate', updateWidgetState);
+
+<api name="destroy">
+@method
+ Removes the widget view from the add-on bar.
+</api>
+
+<api name="postMessage">
+@method
+ Sends a message to the widget view's content scripts.
+@param data {value}
+ The message to send. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="on">
+@method
+ Registers an event listener with the widget view.
+@param type {string}
+ The type of event to listen for.
+@param listener {function}
+ The listener function that handles the event.
+</api>
+
+<api name="removeListener">
+@method
+ Unregisters an event listener from the widget view.
+@param type {string}
+ The type of event for which `listener` was registered.
+@param listener {function}
+ The listener function that was registered.
+</api>
+
+<api name="label">
+@property {string}
+ The widget view's label. Read-only.
+</api>
+
+<api name="content">
+@property {string}
+ A string containing the widget view's content. It can contain HTML.
+ Setting it updates the widget view's appearance immediately. However,
+ if the widget view was created using `contentURL`, then this property
+ is meaningless, and setting it has no effect.
+</api>
+
+<api name="contentURL">
+@property {string}
+ The URL of content to load into the widget view. This can be
+ [local content](dev-guide/addon-development/web-content.html) or remote
+ content, an image or web content. Setting it updates the widget view's
+ appearance immediately. However, if the widget view was created using
+ `content`, then this property is meaningless, and setting it has no effect.
+</api>
+
+<api name="panel">
+@property {Panel}
+ A [panel](packages/addon-kit/docs/panel.html) to open when the user clicks on
+ the widget view.
+</api>
+
+<api name="width">
+@property {number}
+ The widget view's width in pixels. Setting it updates the widget view's
+ appearance immediately.
+</api>
+
+<api name="tooltip">
+@property {string}
+ The text of the tooltip that appears when the user hovers over the widget
+ view.
+</api>
+
+<api name="allow">
+@property {object}
+ A object describing permissions for the content. It contains a single key
+ named `script` whose value is a boolean that indicates whether or not to
+ execute script in the content.
+</api>
+
+<api name="contentScriptFile">
+@property {string,array}
+ A local file URL or an array of local file URLs of content scripts to load.
+</api>
+
+<api name="contentScript">
+@property {string,array}
+ A string or an array of strings containing the texts of content scripts to
+ load.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+ When to load the content scripts. This may have one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the widget view is inserted into the DOM, but before the DOM
+ content itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the widget view has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send events to the content script using the `port.emit` function
+* receive events from the content script using the `port.on`
+
+See the guide to
+<a href="dev-guide/addon-development/content-scripts/using-port.html">
+communicating using <code>port</code></a> for details.
+</api>
+
+<api name="detach">
+@event
+The `detach` event is fired when the widget view is removed from its related
+window.
+This can occur if the window is closed, Firefox exits, or the add-on is
+disabled.
+</api>
+
+<api name="click">
+@event
+This event is emitted when the widget view is clicked.
+</api>
+
+<api name="message">
+@event
+If you listen to this event you can receive message events from content
+scripts associated with this widget view. When a content script posts a
+message using `self.postMessage()`, the message is delivered to the add-on
+code in the widget view's `message` event.
+
+@argument {value}
+Listeners are passed a single argument which is the message posted
+from the content script. The message can be any
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="mouseover">
+@event
+This event is emitted when the user moves the mouse over the widget view.
+</api>
+
+<api name="mouseout">
+@event
+This event is emitted when the user moves the mouse away from the widget view.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/docs/windows.md b/tools/addon-sdk-1.4/packages/addon-kit/docs/windows.md
new file mode 100644
index 0000000..a5bed95
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/docs/windows.md
@@ -0,0 +1,187 @@
+<!-- contributed by Felipe Gomes [felipc@gmail.com] -->
+
+
+The `windows` module provides easy access to browser windows, their
+tabs, and open/close related functions and events.
+
+This module currently only supports browser windows and does not provide
+access to non-browser windows such as the Bookmarks Library, preferences
+or other non-browser windows created via add-ons.
+
+<api name="browserWindows">
+@property {List}
+An object that contains various properties and methods to access
+functionality from browser windows, such as opening new windows, accessing
+their tabs or switching the current active window.
+
+`browserWindows` provides access to all the currently open browser windows:
+
+ var windows = require("windows");
+ for each (var window in windows.browserWindows) {
+ console.log(window.title);
+ }
+
+ console.log(windows.browserWindows.length);
+
+Object emits all the events listed under "Events" section.
+
+####Examples####
+
+ var windows = require("windows").browserWindows;
+
+ // add a listener to the 'open' event
+ windows.on('open', function(window) {
+ myOpenWindows.push(window);
+ });
+
+ // add a listener to the 'close' event
+ windows.on('close', function(window) {
+ console.log("A window was closed.");
+ });
+
+<api name="activeWindow">
+@property {BrowserWindow}
+
+The currently active window. This property is read-only.
+
+**Example**
+
+ // get
+ var windows = require("windows");
+ console.log("title of active window is " +
+ windows.browserWindows.activeWindow.title);
+
+ anotherWindow.activate();
+ // set
+ windows.activeWindow == anotherWindow // true
+</api>
+
+</api>
+
+<api name="open">
+@function
+Open a new window.
+
+ var windows = require("windows").browserWindows;
+
+ // Open a new window.
+ windows.open("http://www.example.com");
+
+ // Open a new window and set a listener for "open" event.
+ windows.open({
+ url: "http://www.example.com",
+ onOpen: function(window) {
+ // do stuff like listen for content
+ // loading.
+ }
+ });
+
+Returns the window that was opened:
+
+ var widgets = require("widget");
+ var windows = require("windows").browserWindows;
+
+ var example = windows.open("http://www.example.com");
+
+ var widget = widgets.Widget({
+ id: "close-window",
+ label: "Close window",
+ contentURL: "http://www.mozilla.org/favicon.ico",
+ onClick: function() {
+ example.close();
+ }
+ });
+
+@param options {object}
+An object containing configurable options for how this window will be opened,
+as well as a callback for being notified when the window has fully opened.
+
+If the only option being used is `url`, then a bare string URL can be passed to
+`open` instead of specifying it as a property of the `options` object.
+
+@prop url {string}
+String URL to be opened in the new window.
+This is a required property.
+
+@prop [onOpen] {function}
+A callback function that is called when the window has opened. This does not
+mean that the URL content has loaded, only that the window itself is fully
+functional and its properties can be accessed. This is an optional property.
+
+@prop [onClose] {function}
+A callback function that is called when the window will be called.
+This is an optional property.
+
+@returns {BrowserWindow}
+</api>
+
+<api name="BrowserWindow">
+@class
+A `BrowserWindow` instance represents a single open window. They can be
+retrieved from the `browserWindows` property exported by this module.
+
+ var windows = require("windows").browserWindows;
+
+ //Print how many tabs the current window has
+ console.log("The active window has " +
+ windows.activeWindow.tabs.length +
+ " tabs.");
+
+ // Print the title of all browser windows
+ for each (var window in windows) {
+ console.log(window.title);
+ }
+
+ // close the active window
+ windows.activeWindow.close();
+
+ windows.activeWindow.close(function() {
+ console.log("The active window was closed");
+ });
+
+<api name="title">
+@property {string}
+The current title of the window. Usually the title of the active tab,
+plus an app identifier.
+This property is read-only.
+</api>
+
+<api name="tabs">
+@property {TabList}
+A live list of tabs in this window. This object has the same interface as the
+[`tabs` API](packages/addon-kit/docs/tabs.html), except it contains only the
+tabs in this window, not all tabs in all windows. This property is read-only.
+</api>
+
+<api name="activate">
+@method
+Makes window active, which will focus that window and bring it to the
+foreground.
+</api>
+
+<api name="close">
+@method
+Close the window.
+
+@param [callback] {function}
+A function to be called when the window finishes its closing process.
+This is an optional argument.
+</api>
+
+</api>
+
+<api name="open">
+@event
+Event emitted when a new window is open.
+This does not mean that the content has loaded, only that the browser window
+itself is fully visible to the user.
+@argument {Window}
+Listeners are passed the `window` object that triggered the event.
+</api>
+
+<api name="close">
+@event
+Event emitted when a window is closed.
+@argument {Window}
+Listeners are passed the `window` object that triggered the event.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/clipboard.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/clipboard.js
new file mode 100644
index 0000000..24f4641
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/clipboard.js
@@ -0,0 +1,266 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Paul O’Shannessy <paul@oshannessy.com> (Original Author)
+ * Dietrich Ayala <dietrich@mozilla.com>
+ * Myk Melez <myk@mozilla.org>
+ * Erik Vold <erikvvold@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const errors = require("api-utils/errors");
+const apiUtils = require("api-utils/api-utils");
+
+/*
+While these data flavors resemble Internet media types, they do
+no directly map to them.
+*/
+const kAllowableFlavors = [
+ "text/unicode",
+ "text/html"
+ /* CURRENTLY UNSUPPORTED FLAVORS
+ "text/plain",
+ "image/png",
+ "image/jpg",
+ "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" }
+ // Images are currently unsupported.
+ //{ short: "image", long: "image/png" },
+];
+
+let clipboardService = Cc["@mozilla.org/widget/clipboard;1"].
+ getService(Ci.nsIClipboard);
+
+let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+
+exports.set = function(aData, aDataType) {
+ let options = {
+ data: aData,
+ datatype: aDataType || "text"
+ };
+ options = apiUtils.validateOptions(options, {
+ data: {
+ is: ["string"]
+ },
+ datatype: {
+ is: ["string"]
+ }
+ });
+
+ var flavor = fromJetpackFlavor(options.datatype);
+
+ if (!flavor)
+ throw new Error("Invalid flavor");
+
+ // 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).");
+
+ 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)
+ let (str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString),
+ 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;
+ // TODO: images!
+ 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 || "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).");
+
+ 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;
+ default:
+ data = null;
+ }
+
+ return data;
+};
+
+exports.__defineGetter__("currentFlavors", function() {
+ // 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 each (var flavor in kAllowableFlavors) {
+ var matches = clipboardService.hasDataMatchingFlavors(
+ [flavor],
+ 1,
+ clipboardService.kGlobalClipboard
+ );
+ if (matches)
+ currentFlavors.push(toJetpackFlavor(flavor));
+ }
+ return currentFlavors;
+});
+
+// SUPPORT FUNCTIONS ////////////////////////////////////////////////////////
+
+function toJetpackFlavor(aFlavor) {
+ for each (let flavorMap in 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 each (let flavorMap in 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/tools/addon-sdk-1.4/packages/addon-kit/lib/context-menu.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/context-menu.js
new file mode 100644
index 0000000..c9f00f9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/context-menu.js
@@ -0,0 +1,1526 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Matteo Ferretti <zer0@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Ci} = require("chrome");
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The context-menu module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+const apiUtils = require("api-utils/api-utils");
+const collection = require("api-utils/collection");
+const { Worker } = require("api-utils/content");
+const url = require("api-utils/url");
+const { MatchPattern } = require("api-utils/match-pattern");
+const { EventEmitterTrait: EventEmitter } = require("api-utils/events");
+const observerServ = require("api-utils/observer-service");
+const jpSelf = require("self");
+const winUtils = require("api-utils/window-utils");
+const { Trait } = require("api-utils/light-traits");
+const { Cortex } = require("api-utils/cortex");
+const timer = require("timer");
+
+// All user items we add have this class name.
+const ITEM_CLASS = "jetpack-context-menu-item";
+
+// Items in the top-level context menu also have this class.
+const TOPLEVEL_ITEM_CLASS = "jetpack-context-menu-item-toplevel";
+
+// Items in the overflow submenu also have this class.
+const OVERFLOW_ITEM_CLASS = "jetpack-context-menu-item-overflow";
+
+// The ID of the menu separator that separates standard context menu items from
+// our user items.
+const SEPARATOR_ID = "jetpack-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 this.
+const OVERFLOW_MENU_LABEL = "Add-ons";
+
+// The ID of the overflow sub-xul:menu.
+const OVERFLOW_MENU_ID = "jetpack-content-menu-overflow-menu";
+
+// The ID of the overflow submenu's xul:menupopup.
+const OVERFLOW_POPUP_ID = "jetpack-content-menu-overflow-popup";
+
+// 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,
+];
+
+// This is used to access private properties of Item and Menu instances.
+const PRIVATE_PROPS_KEY = {
+ valueOf: function valueOf() "private properties key"
+};
+
+// Used as an internal ID for items and as part of a public ID for item DOM
+// elements. Careful: This number is not necessarily unique to any one instance
+// of the module. For each module instance, when the first item is created this
+// number will be 0, when the second is created it will be 1, and so on.
+let nextItemID = 0;
+
+// The number of items that haven't finished initializing yet. See
+// AIT__finishActiveItemInit().
+let numItemsWithUnfinishedInit = 0;
+
+exports.Item = Item;
+exports.Menu = Menu;
+exports.Separator = Separator;
+
+
+// A word about traits and privates. `this` inside of traits methods is an
+// object private to the implementation. It should never be publicly leaked.
+// We use Cortex in the exported menu item constructors to create public
+// reflections of the private objects that hide private properties -- those
+// prefixed with an underscore. Public reflections are attached to the private
+// objects via the `_public` property.
+//
+// All item objects passed into the implementation by the client will be public
+// reflections, not private objects. Likewise, all item objects passed out of
+// the implementation to the client must be public, not private. Mixing up
+// public and private is bad and easy to do, so not only are private objects
+// restricted to the implementation, but as much as possible we try to restrict
+// them to the Item, Menu, and Separator traits and constructors. Everybody
+// else in the implementation should expect to be passed public reflections, and
+// they must specifically request private objects via privateItem().
+
+// Item, Menu, and Separator are composed of this trait.
+const ItemBaseTrait = Trait({
+
+ _initBase: function IBT__initBase(opts, optRules, optsToNotSet) {
+ this._optRules = optRules;
+ for (let optName in optRules)
+ if (optsToNotSet.indexOf(optName) < 0)
+ this[optName] = opts[optName];
+ optsToNotSet.forEach(function (opt) validateOpt(opts[opt], optRules[opt]));
+ this._isInited = true;
+
+ this._id = nextItemID++;
+ this._parentMenu = null;
+
+ // This makes the private properties accessible to anyone with access to
+ // PRIVATE_PROPS_KEY. Barring loader tricks, only this file has has access
+ // to it, so only this file has access to the private properties.
+ const self = this;
+ this.valueOf = function IBT_valueOf(key) {
+ return key === PRIVATE_PROPS_KEY ? self : self._public;
+ };
+ },
+
+ destroy: function IBT_destroy() {
+ if (this._wasDestroyed)
+ return;
+ if (this.parentMenu)
+ this.parentMenu.removeItem(this._public);
+ else if (!(this instanceof Separator) && this._hasFinishedInit)
+ browserManager.removeTopLevelItem(this._public);
+ browserManager.unregisterItem(this._public);
+ this._wasDestroyed = true;
+ },
+
+ get parentMenu() {
+ return this._parentMenu;
+ },
+
+ set parentMenu(val) {
+ throw new Error("The 'parentMenu' property is not intended to be set. " +
+ "Use menu.addItem(item) instead.");
+ },
+
+ set _isTopLevel(val) {
+ if (val)
+ this._workerReg = new WorkerRegistry(this._public);
+ else {
+ this._workerReg.destroy();
+ delete this._workerReg;
+ }
+ },
+
+ get _topLevelItem() {
+ let topLevelItem = this._public;
+ let parentMenu = this.parentMenu;
+ while (parentMenu) {
+ topLevelItem = parentMenu;
+ parentMenu = parentMenu.parentMenu;
+ }
+ return topLevelItem;
+ }
+});
+
+// Item and Menu are composed of this trait.
+const ActiveItemTrait = Trait.compose(ItemBaseTrait, EventEmitter, Trait({
+
+ _initActiveItem: function AIT__initActiveItem(opts, optRules, optsToNotSet) {
+ this._initBase(opts, optRules,
+ optsToNotSet.concat(["onMessage", "context"]));
+
+ if ("onMessage" in opts)
+ this.on("message", opts.onMessage);
+
+ // When a URL context is removed (by calling context.remove(urlContext)), we
+ // may need to create workers for windows containing pages that the item now
+ // matches. Likewise, when a URL context is added, we need to destroy
+ // workers for windows containing pages that the item now does not match.
+ //
+ // collection doesn't provide a way to listen for removals. utils/registry
+ // does, but it doesn't allow its elements to be enumerated. So as a hack,
+ // use a collection for item.context and replace its add and remove methods.
+ collection.addCollectionProperty(this, "context");
+ if (opts.context)
+ this.context.add(opts.context);
+
+ const self = this;
+
+ let add = this.context.add;
+ this.context.add = function itemContextAdd() {
+ let args = Array.slice(arguments);
+ add.apply(self.context, args);
+ if (self._workerReg && args.some(function (a) a instanceof URLContext))
+ self._workerReg.destroyUnneededWorkers();
+ };
+
+ let remove = this.context.remove;
+ this.context.remove = function itemContextRemove() {
+ let args = Array.slice(arguments);
+ remove.apply(self.context, args);
+ if (self._workerReg && args.some(function (a) a instanceof URLContext))
+ self._workerReg.createNeededWorkers();
+ };
+ },
+
+ // Workers are only created for top-level menu items. When a top-level item
+ // is later added to a Menu, its workers are destroyed. Well, all items start
+ // out as top-level because there is, unfortunately, no contextMenu.add(). So
+ // when an item is created and immediately added to a Menu, workers for it are
+ // needlessly created and destroyed. The point of this timeout is to avoid
+ // that. Items that are created and added to Menus in the same turn of the
+ // event loop won't have workers created for them.
+ _finishActiveItemInit: function AIT__finishActiveItemInit() {
+ numItemsWithUnfinishedInit++;
+ const self = this;
+ timer.setTimeout(function AIT__finishActiveItemInitTimeout() {
+ if (!self.parentMenu && !self._wasDestroyed)
+ browserManager.addTopLevelItem(self._public);
+ self._hasFinishedInit = true;
+ numItemsWithUnfinishedInit--;
+ }, 0);
+ },
+
+ get label() {
+ return this._label;
+ },
+
+ set label(val) {
+ this._label = validateOpt(val, this._optRules.label);
+ if (this._isInited)
+ browserManager.setItemLabel(this, this._label);
+ return this._label;
+ },
+
+ get image() {
+ return this._image;
+ },
+
+ set image(val) {
+ this._image = validateOpt(val, this._optRules.image);
+ if (this._isInited)
+ browserManager.setItemImage(this, this._image);
+ return this._image;
+ },
+
+ get contentScript() {
+ return this._contentScript;
+ },
+
+ set contentScript(val) {
+ this._contentScript = validateOpt(val, this._optRules.contentScript);
+ return this._contentScript;
+ },
+
+ get contentScriptFile() {
+ return this._contentScriptFile;
+ },
+
+ set contentScriptFile(val) {
+ this._contentScriptFile =
+ validateOpt(val, this._optRules.contentScriptFile);
+ return this._contentScriptFile;
+ }
+}));
+
+// Item is composed of this trait.
+const ItemTrait = Trait.compose(ActiveItemTrait, Trait({
+
+ _initItem: function IT__initItem(opts, optRules) {
+ this._initActiveItem(opts, optRules, []);
+ },
+
+ get data() {
+ return this._data;
+ },
+
+ set data(val) {
+ this._data = validateOpt(val, this._optRules.data);
+ if (this._isInited)
+ browserManager.setItemData(this, this._data);
+ return this._data;
+ },
+
+ toString: function IT_toString() {
+ return '[object Item "' + this.label + '"]';
+ }
+}));
+
+// The exported Item constructor.
+function Item(options) {
+ let optRules = optionsRules();
+ optRules.data = {
+ map: function (v) v.toString(),
+ is: ["string", "undefined"]
+ };
+
+ let item = ItemTrait.create(Item.prototype);
+ item._initItem(options, optRules);
+
+ item._public = Cortex(item);
+ browserManager.registerItem(item._public);
+ item._finishActiveItemInit();
+
+ return item._public;
+}
+
+// Menu is composed of this trait.
+const MenuTrait = Trait.compose(
+ ActiveItemTrait.resolve({ destroy: "_destroyThisItem" }),
+ Trait({
+
+ _initMenu: function MT__initMenu(opts, optRules, optsToNotSet) {
+ this._items = [];
+ this._initActiveItem(opts, optRules, optsToNotSet);
+ },
+
+ destroy: function MT_destroy() {
+ while (this.items.length)
+ this.items[0].destroy();
+ this._destroyThisItem();
+ },
+
+ get items() {
+ return this._items;
+ },
+
+ set items(val) {
+ let newItems = validateOpt(val, this._optRules.items);
+ while (this.items.length)
+ this.items[0].destroy();
+ newItems.forEach(function (i) this.addItem(i), this);
+ return newItems;
+ },
+
+ addItem: function MT_addItem(item) {
+ // First, remove the item from its current parent.
+ let privates = privateItem(item);
+ if (item.parentMenu)
+ item.parentMenu.removeItem(item);
+ else if (!(item instanceof Separator) && privates._hasFinishedInit)
+ browserManager.removeTopLevelItem(item);
+
+ // Now add the item to this menu.
+ this._items.push(item);
+ privates._parentMenu = this._public;
+ browserManager.addItemToMenu(item, this._public);
+ },
+
+ removeItem: function MT_removeItem(item) {
+ let idx = this._items.indexOf(item);
+ if (idx < 0)
+ return;
+ this._items.splice(idx, 1);
+ privateItem(item)._parentMenu = null;
+ browserManager.removeItemFromMenu(item, this._public);
+ },
+
+ toString: function MT_toString() {
+ return '[object Menu "' + this.label + '"]';
+ }
+}));
+
+// The exported Menu constructor.
+function Menu(options) {
+ let optRules = optionsRules();
+ optRules.items = {
+ is: ["array"],
+ ok: function (v) {
+ return v.every(function (item) {
+ return (item instanceof Item) ||
+ (item instanceof Menu) ||
+ (item instanceof Separator);
+ });
+ },
+ msg: "items must be an array, and each element in the array must be an " +
+ "Item, Menu, or Separator."
+ };
+
+ let menu = MenuTrait.create(Menu.prototype);
+
+ // We can't rely on _initBase to set the `items` property, because the menu
+ // needs to be registered with and added to the browserManager before any
+ // child items are added to it.
+ menu._initMenu(options, optRules, ["items"]);
+
+ menu._public = Cortex(menu);
+ browserManager.registerItem(menu._public);
+ menu.items = options.items;
+ menu._finishActiveItemInit();
+
+ return menu._public;
+}
+
+// The exported Separator constructor.
+function Separator() {
+ let sep = ItemBaseTrait.create(Separator.prototype);
+ sep._initBase({}, {}, []);
+
+ sep._public = Cortex(sep);
+ browserManager.registerItem(sep._public);
+ sep._hasFinishedInit = true;
+ return sep._public;
+}
+
+
+function Context() {}
+
+function PageContext() {
+ this.isCurrent = function PageContext_isCurrent(popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ if (win && !win.getSelection().isCollapsed)
+ return false;
+
+ let cursor = popupNode;
+ while (cursor && !(cursor instanceof Ci.nsIDOMHTMLHtmlElement)) {
+ if (NON_PAGE_CONTEXT_ELTS.some(function (iface) cursor instanceof iface))
+ return false;
+ cursor = cursor.parentNode;
+ }
+ return true;
+ };
+}
+
+PageContext.prototype = new Context();
+
+function SelectorContext(selector) {
+ let opts = apiUtils.validateOptions({ selector: selector }, {
+ selector: {
+ is: ["string"],
+ msg: "selector must be a string."
+ }
+ });
+
+ this.adjustPopupNode = function SelectorContext_adjustPopupNode(node) {
+ return closestMatchingAncestor(node);
+ };
+
+ this.isCurrent = function SelectorContext_isCurrent(popupNode) {
+ return !!closestMatchingAncestor(popupNode);
+ };
+
+ // Returns node if it matches selector, or the closest ancestor of node that
+ // matches, or null if node and none of its ancestors matches.
+ function closestMatchingAncestor(node) {
+ let cursor = node;
+ while (cursor) {
+ if (cursor.mozMatchesSelector(selector))
+ return cursor;
+ if (cursor instanceof Ci.nsIDOMHTMLHtmlElement)
+ break;
+ cursor = cursor.parentNode;
+ }
+ return null;
+ }
+}
+
+SelectorContext.prototype = new Context();
+
+function SelectionContext() {
+ this.isCurrent = function SelectionContext_isCurrent(popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ if (!win)
+ return false;
+
+ let hasSelection = !win.getSelection().isCollapsed;
+ if (!hasSelection) {
+ // window.getSelection doesn't return a selection for text selected in a
+ // form field (see bug 85686), so before returning false we want to check
+ // if the popupNode is a text field.
+ let { selectionStart, selectionEnd } = popupNode;
+ hasSelection = !isNaN(selectionStart) &&
+ !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+ }
+ return hasSelection;
+ };
+}
+
+SelectionContext.prototype = new Context();
+
+function URLContext(patterns) {
+ let opts = apiUtils.validateOptions({ patterns: patterns }, {
+ patterns: {
+ map: function (v) apiUtils.getTypeOf(v) === "array" ? v : [v],
+ ok: function (v) v.every(function (p) typeof(p) === "string"),
+ msg: "patterns must be a string or an array of strings."
+ }
+ });
+ try {
+ patterns = opts.patterns.map(function (p) new MatchPattern(p));
+ }
+ catch (err) {
+ console.error("Error creating URLContext match pattern:");
+ throw err;
+ }
+
+ const self = this;
+
+ this.isCurrent = function URLContext_isCurrent(popupNode) {
+ return self.isCurrentForURL(popupNode.ownerDocument.URL);
+ };
+
+ this.isCurrentForURL = function URLContext_isCurrentForURL(url) {
+ return patterns.some(function (p) p.test(url));
+ };
+}
+
+URLContext.prototype = new Context();
+
+exports.PageContext = apiUtils.publicConstructor(PageContext);
+exports.SelectorContext = apiUtils.publicConstructor(SelectorContext);
+exports.SelectionContext = apiUtils.publicConstructor(SelectionContext);
+exports.URLContext = apiUtils.publicConstructor(URLContext);
+
+
+// Returns a version of opt validated against the given rule.
+function validateOpt(opt, rule) {
+ return apiUtils.validateOptions({ opt: opt }, { opt: rule }).opt;
+}
+
+// Returns rules for apiUtils.validateOptions() common to Item and Menu.
+function optionsRules() {
+ return {
+ context: {
+ is: ["undefined", "object", "array"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ let arr = apiUtils.getTypeOf(v) === "array" ? v : [v];
+ return arr.every(function (o) o instanceof Context);
+ },
+ msg: "The 'context' option must be a Context object or an array of " +
+ "Context objects."
+ },
+ label: {
+ map: function (v) v.toString(),
+ is: ["string"],
+ ok: function (v) !!v,
+ msg: "The item must have a non-empty string label."
+ },
+ image: {
+ map: function (v) v.toString(),
+ is: ["string", "undefined", "null"]
+ },
+ contentScript: {
+ is: ["string", "array", "undefined"],
+ ok: function (v) {
+ return apiUtils.getTypeOf(v) !== "array" ||
+ v.every(function (s) typeof(s) === "string");
+ }
+ },
+ contentScriptFile: {
+ is: ["string", "array", "undefined"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ let arr = apiUtils.getTypeOf(v) === "array" ? v : [v];
+ try {
+ return arr.every(function (s) {
+ return apiUtils.getTypeOf(s) === "string" && url.toFilename(s);
+ });
+ }
+ catch (err) {}
+ return false;
+ },
+ msg: "The 'contentScriptFile' option must be a local file URL or " +
+ "an array of local file URLs."
+ },
+ onMessage: {
+ is: ["function", "undefined"]
+ }
+ };
+}
+
+// Does a binary search on elts, a NodeList, and returns the DOM element
+// before which an item with targetLabel should be inserted. null is returned
+// if the new item should be inserted at the end.
+function insertionPoint(targetLabel, elts) {
+ let from = 0;
+ let to = elts.length - 1;
+
+ while (from <= to) {
+ let i = Math.floor((from + to) / 2);
+ let comp = targetLabel.localeCompare(elts[i].getAttribute("label"));
+ if (comp < 0)
+ to = i - 1;
+ else if (comp > 0)
+ from = i + 1;
+ else
+ return elts[i];
+ }
+ return elts[from] || null;
+}
+
+// Builds an ID suitable for a DOM element from the given item ID.
+// isInOverflowSubtree should be true if the returned element will be inserted
+// into the DOM subtree rooted at the overflow menu.
+function domEltIDFromItemID(itemID, isInOverflowSubtree) {
+ let suffix = isInOverflowSubtree ? "-overflow" : "";
+ return jpSelf.id + "-context-menu-item-" + itemID + suffix;
+}
+
+// Parses the item ID out of the given DOM element ID and returns it. If the
+// element's ID is malformed or it indicates that the element was not created by
+// the instance of the module calling this function, returns -1.
+function itemIDFromDOMEltID(domEltID) {
+ let match = /^(.+?)-context-menu-item-([0-9]+)[-a-z]*$/.exec(domEltID);
+ return !match || match[1] !== jpSelf.id ? -1 : match[2];
+}
+
+// Returns the private version of the given public reflection.
+function privateItem(publicItem) {
+ return publicItem.valueOf(PRIVATE_PROPS_KEY);
+}
+
+
+// A type of Worker tailored to our uses.
+const ContextMenuWorker = Worker.compose({
+ destroy: Worker.required,
+
+ // Returns true if any context listeners are defined in the worker's port.
+ anyContextListeners: function CMW_anyContextListeners() {
+ return this._contentWorker._listeners("context").length > 0;
+ },
+
+ // Returns the first string or truthy value returned by a context listener in
+ // the worker's port. If none return a string or truthy value or if there are
+ // no context listeners, returns false. popupNode is the node that was
+ // context-clicked.
+ isAnyContextCurrent: function CMW_isAnyContextCurrent(popupNode) {
+ let listeners = this._contentWorker._listeners("context");
+ for (let i = 0; i < listeners.length; i++) {
+ try {
+ let val = listeners[i].call(this._contentWorker._sandbox, popupNode);
+ if (typeof(val) === "string" || val)
+ return val;
+ }
+ catch (err) {
+ console.exception(err);
+ }
+ }
+ return false;
+ },
+
+ // 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 CMW_fireClick(popupNode, clickedItemData) {
+ this._contentWorker._asyncEmit("click", popupNode, clickedItemData);
+ }
+});
+
+
+// This class creates and stores content workers for pairs of menu items and
+// content windows. Use one instance for each item. Not all pairs need a
+// worker: if an item has a URL context that does not match a window's page,
+// then no worker is created for the pair.
+function WorkerRegistry(item) {
+ this.item = item;
+
+ // inner window ID => { win, worker }
+ this.winWorkers = {};
+
+ // inner window ID => content window
+ this.winsWithoutWorkers = {};
+}
+
+WorkerRegistry.prototype = {
+
+ // Registers a content window, creating a worker for it if it needs one.
+ registerContentWin: function WR_registerContentWin(win) {
+ let innerWinID = winUtils.getInnerId(win);
+ if ((innerWinID in this.winWorkers) ||
+ (innerWinID in this.winsWithoutWorkers))
+ return;
+ if (this._doesURLNeedWorker(win.document.URL))
+ this.winWorkers[innerWinID] = { win: win, worker: this._makeWorker(win) };
+ else
+ this.winsWithoutWorkers[innerWinID] = win;
+ },
+
+ // Unregisters a content window, destroying its related worker if it has one.
+ unregisterContentWin: function WR_unregisterContentWin(innerWinID) {
+ if (innerWinID in this.winWorkers) {
+ let winWorker = this.winWorkers[innerWinID];
+ winWorker.worker.destroy();
+ delete winWorker.worker;
+ delete winWorker.win;
+ delete this.winWorkers[innerWinID];
+ }
+ else
+ delete this.winsWithoutWorkers[innerWinID];
+ },
+
+ // Creates a worker for each window that needs a worker but doesn't have one.
+ createNeededWorkers: function WR_createNeededWorkers() {
+ for (let [innerWinID, win] in Iterator(this.winsWithoutWorkers)) {
+ delete this.winsWithoutWorkers[innerWinID];
+ this.registerContentWin(win);
+ }
+ },
+
+ // Destroys the worker for each window that has a worker but doesn't need it.
+ destroyUnneededWorkers: function WR_destroyUnneededWorkers() {
+ for (let [innerWinID, winWorker] in Iterator(this.winWorkers)) {
+ if (!this._doesURLNeedWorker(winWorker.win.document.URL)) {
+ this.unregisterContentWin(innerWinID);
+ this.winsWithoutWorkers[innerWinID] = winWorker.win;
+ }
+ }
+ },
+
+ // Returns the worker for the item-window pair or null if none exists.
+ find: function WR_find(contentWin) {
+ let innerWinID = winUtils.getInnerId(contentWin);
+ return (innerWinID in this.winWorkers) ?
+ this.winWorkers[innerWinID].worker :
+ null;
+ },
+
+ // Unregisters all content windows from the registry, which destroys all
+ // workers.
+ destroy: function WR_destroy() {
+ for (let innerWinID in this.winWorkers)
+ this.unregisterContentWin(innerWinID);
+ for (let innerWinID in this.winsWithoutWorkers)
+ this.unregisterContentWin(innerWinID);
+ },
+
+ // Returns false if the item has a URL context that does not match the given
+ // URL.
+ _doesURLNeedWorker: function WR__doesURLNeedWorker(url) {
+ for (let ctxt in this.item.context)
+ if ((ctxt instanceof URLContext) && !ctxt.isCurrentForURL(url))
+ return false;
+ return true;
+ },
+
+ _makeWorker: function WR__makeWorker(win) {
+ let worker = ContextMenuWorker({
+ window: win,
+ contentScript: this.item.contentScript,
+ contentScriptFile: this.item.contentScriptFile,
+ onError: function (err) console.exception(err)
+ });
+ let item = this.item;
+ worker.on("message", function workerOnMessage(msg) {
+ try {
+ privateItem(item)._emitOnObject(item, "message", msg);
+ }
+ catch (err) {
+ console.exception(err);
+ }
+ });
+ return worker;
+ }
+};
+
+
+// Mirrors state across all browser windows. Also responsible for detecting
+// all content window loads and unloads.
+let browserManager = {
+ topLevelItems: [],
+ browserWins: [],
+
+ // inner window ID => content window
+ contentWins: {},
+
+ // Call this when a new item is created, top-level or not.
+ registerItem: function BM_registerItem(item) {
+ this.browserWins.forEach(function (w) w.registerItem(item));
+ },
+
+ // Call this when an item is destroyed and won't be used again, top-level or
+ // not.
+ unregisterItem: function BM_unregisterItem(item) {
+ this.browserWins.forEach(function (w) w.unregisterItem(item));
+ },
+
+ addTopLevelItem: function BM_addTopLevelItem(item) {
+ this.topLevelItems.push(item);
+ this.browserWins.forEach(function (w) w.addTopLevelItem(item));
+
+ // Create the item's worker registry and register all currently loaded
+ // content windows with it.
+ let privates = privateItem(item);
+ privates._isTopLevel = true;
+ for each (let win in this.contentWins)
+ privates._workerReg.registerContentWin(win);
+ },
+
+ removeTopLevelItem: function BM_removeTopLevelItem(item) {
+ let idx = this.topLevelItems.indexOf(item);
+ if (idx < 0)
+ throw new Error("Internal error: item not in top-level menu: " + item);
+ this.topLevelItems.splice(idx, 1);
+ this.browserWins.forEach(function (w) w.removeTopLevelItem(item));
+ privateItem(item)._isTopLevel = false;
+ },
+
+ addItemToMenu: function BM_addItemToMenu(item, parentMenu) {
+ this.browserWins.forEach(function (w) w.addItemToMenu(item, parentMenu));
+ },
+
+ removeItemFromMenu: function BM_removeItemFromMenu(item, parentMenu) {
+ this.browserWins.forEach(function (w) w.removeItemFromMenu(item,
+ parentMenu));
+ },
+
+ setItemLabel: function BM_setItemLabel(item, label) {
+ this.browserWins.forEach(function (w) w.setItemLabel(item, label));
+ },
+
+ setItemImage: function BM_setItemImage(item, imageURL) {
+ this.browserWins.forEach(function (w) w.setItemImage(item, imageURL));
+ },
+
+ setItemData: function BM_setItemData(item, data) {
+ this.browserWins.forEach(function (w) w.setItemData(item, data));
+ },
+
+ // Note that calling this method will cause onTrack to be called immediately
+ // for each currently open browser window.
+ init: function BM_init() {
+ require("api-utils/unload").ensure(this);
+ let windowTracker = new winUtils.WindowTracker(this);
+
+ // Register content windows on content-document-global-created and
+ // unregister them on inner-window-destroyed. For rationale, see bug 667957
+ // for the former and bug 642004 for the latter.
+ observerServ.add("content-document-global-created",
+ this._onDocGlobalCreated, this);
+ observerServ.add("inner-window-destroyed",
+ this._onInnerWinDestroyed, this);
+ },
+
+ _onDocGlobalCreated: function BM__onDocGlobalCreated(contentWin) {
+ let doc = contentWin.document;
+ if (doc.readyState == "loading") {
+ const self = this;
+ doc.addEventListener("readystatechange", function onReadyStateChange(e) {
+ if (e.target != doc || doc.readyState != "complete")
+ return;
+ doc.removeEventListener("readystatechange", onReadyStateChange, false);
+ self._registerContentWin(contentWin);
+ }, false);
+ }
+ else if (doc.readyState == "complete")
+ this._registerContentWin(contentWin);
+ },
+
+ _onInnerWinDestroyed: function BM__onInnerWinDestroyed(subj) {
+ this._unregisterContentWin(
+ subj.QueryInterface(Ci.nsISupportsPRUint64).data);
+ },
+
+ // Stores the given content window with the manager and registers it with each
+ // top-level item's worker registry.
+ _registerContentWin: function BM__registerContentWin(win) {
+ let innerID = winUtils.getInnerId(win);
+
+ // It's an error to call this method for the same window more than once, but
+ // we allow it in one case: when onTrack races _onDocGlobalCreated. (See
+ // the comment in onTrack.) Make sure the window is registered only once.
+ if (innerID in this.contentWins)
+ return;
+
+ this.contentWins[innerID] = win;
+ this.topLevelItems.forEach(function (item) {
+ privateItem(item)._workerReg.registerContentWin(win);
+ });
+ },
+
+ // Removes the given content window from the manager and unregisters it from
+ // each top-level item's worker registry.
+ _unregisterContentWin: function BM__unregisterContentWin(innerID) {
+ delete this.contentWins[innerID];
+ this.topLevelItems.forEach(function (item) {
+ privateItem(item)._workerReg.unregisterContentWin(innerID);
+ });
+ },
+
+ unload: function BM_unload() {
+ // The window tracker is unloaded at the same time this method is called,
+ // which causes onUntrack to be called for each open browser window, so
+ // there's no need to clean up browser windows here.
+
+ while (this.topLevelItems.length) {
+ let item = this.topLevelItems[0];
+ this.removeTopLevelItem(item);
+ this.unregisterItem(item);
+ }
+ delete this.contentWins;
+ },
+
+ // Registers a browser window with the manager. This is a WindowTracker
+ // callback. Note that this is called in two cases: for each newly opened
+ // chrome window, and for each chrome window that is open when the loader
+ // loads this module.
+ onTrack: function BM_onTrack(window) {
+ if (!this._isBrowserWindow(window))
+ return;
+
+ let browserWin = new BrowserWindow(window);
+ this.browserWins.push(browserWin);
+
+ // Register all loaded content windows in the browser window. Be sure to
+ // include frames and iframes. If onTrack is called as a result of a new
+ // browser window being opened, as opposed to the module being loaded, then
+ // this will race the content-document-global-created notification. That's
+ // OK, since _registerContentWin will not register the same content window
+ // more than once.
+ window.gBrowser.browsers.forEach(function (browser) {
+ let topContentWin = browser.contentWindow;
+ let allContentWins = Array.slice(topContentWin.frames);
+ allContentWins.push(topContentWin);
+ allContentWins.forEach(function (contentWin) {
+ if (contentWin.document.readyState == "complete")
+ this._registerContentWin(contentWin);
+ }, this);
+ }, this);
+
+ // Add all top-level items and, recursively, their child items to the new
+ // browser window.
+ function addItemTree(item, parentMenu) {
+ browserWin.registerItem(item);
+ if (parentMenu)
+ browserWin.addItemToMenu(item, parentMenu);
+ else
+ browserWin.addTopLevelItem(item);
+ if (item instanceof Menu)
+ item.items.forEach(function (subitem) addItemTree(subitem, item));
+ }
+ this.topLevelItems.forEach(function (item) addItemTree(item, null));
+ },
+
+ // Unregisters a browser window from the manager. This is a WindowTracker
+ // callback. Note that this is called in two cases: for each newly closed
+ // chrome window, and for each chrome window that is open when this module is
+ // unloaded.
+ onUntrack: function BM_onUntrack(window) {
+ if (!this._isBrowserWindow(window))
+ return;
+
+ // Remove the window from the window list.
+ let idx = 0;
+ for (; idx < this.browserWins.length; idx++)
+ if (this.browserWins[idx].window == window)
+ break;
+ if (idx == this.browserWins.length)
+ throw new Error("Internal error: browser window not found");
+ let browserWin = this.browserWins.splice(idx, 1)[0];
+
+ // Remove all top-level items from the window.
+ this.topLevelItems.forEach(function (i) browserWin.removeTopLevelItem(i));
+ browserWin.destroy();
+ },
+
+ _isBrowserWindow: function BM__isBrowserWindow(win) {
+ let winType = win.document.documentElement.getAttribute("windowtype");
+ return winType === "navigator:browser";
+ }
+};
+
+
+// Responsible for creating and managing context menu item DOM elements for a
+// browser window. Also responsible for providing a description of the window's
+// current context and determining whether an item matches the current context.
+//
+// TODO: If other apps besides Firefox want to support the context menu in
+// whatever way is appropriate for them, plugging in a substitute for or an
+// adapter to this class should be the way to do it. Make it easy for them.
+// See bug 560716.
+function BrowserWindow(window) {
+ this.window = window;
+ this.doc = window.document;
+
+ let popupDOMElt = this.doc.getElementById("contentAreaContextMenu");
+ if (!popupDOMElt)
+ throw new Error("Internal error: Context menu popup not found.");
+ this.contextMenuPopup = new ContextMenuPopup(popupDOMElt, this);
+
+ // item ID => { item, domElt, overflowDOMElt, popup, overflowPopup }
+ // item may or may not be top-level. domElt is the item's DOM element
+ // contained in the subtree rooted in the top-level context menu.
+ // overflowDOMElt is the item's DOM element contained in the subtree rooted in
+ // the overflow submenu. popup and overflowPopup are only defined if the item
+ // is a Menu; they're the Popup instances containing the Menu's child items,
+ // with the aforementioned distinction between top-level and overflow
+ // subtrees.
+ this.items = {};
+}
+
+BrowserWindow.prototype = {
+
+ // Creates and stores DOM elements for the given item, top-level or not.
+ registerItem: function BW_registerItem(item) {
+ // this.items[id] is referenced by _makeMenu, so it needs to be defined
+ // before _makeDOMElt is called.
+ let props = { item: item };
+ this.items[privateItem(item)._id] = props;
+ props.domElt = this._makeDOMElt(item, false);
+ props.overflowDOMElt = this._makeDOMElt(item, true);
+ },
+
+ // Removes the given item's DOM elements from the store.
+ unregisterItem: function BW_unregisterItem(item) {
+ delete this.items[privateItem(item)._id];
+ },
+
+ addTopLevelItem: function BW_addTopLevelItem(item) {
+ this.contextMenuPopup.addItem(item);
+ },
+
+ removeTopLevelItem: function BW_removeTopLevelItem(item) {
+ this.contextMenuPopup.removeItem(item);
+ },
+
+ addItemToMenu: function BW_addItemToMenu(item, parentMenu) {
+ let { popup, overflowPopup } = this.items[privateItem(parentMenu)._id];
+ popup.addItem(item);
+ overflowPopup.addItem(item);
+ },
+
+ removeItemFromMenu: function BW_removeItemFromMenu(item, parentMenu) {
+ let { popup, overflowPopup } = this.items[privateItem(parentMenu)._id];
+ popup.removeItem(item);
+ overflowPopup.removeItem(item);
+ },
+
+ setItemLabel: function BW_setItemLabel(item, label) {
+ let privates = privateItem(item);
+ let { domElt, overflowDOMElt } = this.items[privates._id];
+ this._setDOMEltLabel(domElt, label);
+ this._setDOMEltLabel(overflowDOMElt, label);
+ if (!item.parentMenu && privates._hasFinishedInit)
+ this.contextMenuPopup.itemLabelDidChange(item);
+ },
+
+ _setDOMEltLabel: function BW__setDOMEltLabel(domElt, label) {
+ domElt.setAttribute("label", label);
+ },
+
+ setItemImage: function BW_setItemImage(item, imageURL) {
+ let { domElt, overflowDOMElt } = this.items[privateItem(item)._id];
+ let isMenu = item instanceof Menu;
+ this._setDOMEltImage(domElt, imageURL, isMenu);
+ this._setDOMEltImage(overflowDOMElt, imageURL, isMenu);
+ },
+
+ _setDOMEltImage: function BW__setDOMEltImage(domElt, imageURL, isMenu) {
+ if (!imageURL) {
+ domElt.removeAttribute("image");
+ domElt.classList.remove("menu-iconic");
+ domElt.classList.remove("menuitem-iconic");
+ }
+ else {
+ domElt.setAttribute("image", imageURL);
+ domElt.classList.add(isMenu ? "menu-iconic" : "menuitem-iconic");
+ }
+ },
+
+ setItemData: function BW_setItemData(item, data) {
+ let { domElt, overflowDOMElt } = this.items[privateItem(item)._id];
+ this._setDOMEltData(domElt, data);
+ this._setDOMEltData(overflowDOMElt, data);
+ },
+
+ _setDOMEltData: function BW__setDOMEltData(domElt, data) {
+ domElt.setAttribute("value", data);
+ },
+
+ // The context specified for a top-level item may not match exactly the real
+ // context that triggers it. For example, if the user context-clicks a span
+ // inside an anchor, we want items that specify an anchor context to be
+ // triggered, but the real context will indicate that the span was clicked,
+ // not the anchor. Where the real context and an item's context conflict,
+ // clients should be given the item's context, and this method can be used to
+ // make such adjustments. Returns an adjusted popupNode.
+ adjustPopupNode: function BW_adjustPopupNode(popupNode, topLevelItem) {
+ for (let ctxt in topLevelItem.context) {
+ if (typeof(ctxt.adjustPopupNode) === "function") {
+ let ctxtNode = ctxt.adjustPopupNode(popupNode);
+ if (ctxtNode) {
+ popupNode = ctxtNode;
+ break;
+ }
+ }
+ }
+ return popupNode;
+ },
+
+ // Returns true if all of item's contexts are current in the window.
+ areAllContextsCurrent: function BW_areAllContextsCurrent(item, popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ let worker = privateItem(item)._workerReg.find(win);
+
+ // If the worker for the item-window pair doesn't exist (e.g., because the
+ // page hasn't loaded yet), we can't really make a good decision since the
+ // content script may have a context listener. So just don't show the item
+ // at all.
+ if (!worker)
+ return false;
+
+ // If there are no contexts given at all, the page context applies.
+ let hasContentContext = worker.anyContextListeners();
+ if (!hasContentContext && !item.context.length)
+ return new PageContext().isCurrent(popupNode);
+
+ // Otherwise, determine if all given contexts are current. Evaluate the
+ // declarative contexts first and the worker's context listeners last. That
+ // way the listener might be able to avoid some work.
+ let curr = true;
+ for (let ctxt in item.context) {
+ curr = curr && ctxt.isCurrent(popupNode);
+ if (!curr)
+ return false;
+ }
+ return !hasContentContext || worker.isAnyContextCurrent(popupNode);
+ },
+
+ // Sets this.popupNode to the node the user context-clicked to invoke the
+ // context menu. For Gecko 2.0 and later, triggerNode is this node; if it's
+ // falsey, document.popupNode is used. Returns the popupNode.
+ capturePopupNode: function BW_capturePopupNode(triggerNode) {
+ this.popupNode = triggerNode || this.doc.popupNode;
+ if (!this.popupNode)
+ console.warn("popupNode is null.");
+ return this.popupNode;
+ },
+
+ destroy: function BW_destroy() {
+ this.contextMenuPopup.destroy();
+ delete this.window;
+ delete this.doc;
+ delete this.items;
+ },
+
+ // Emits a click event in the port of the content worker related to given top-
+ // level item and popupNode's content window. Listeners will be passed
+ // popupNode and clickedItemData.
+ fireClick: function BW_fireClick(topLevelItem, popupNode, clickedItemData) {
+ let win = popupNode.ownerDocument.defaultView;
+ let worker = privateItem(topLevelItem)._workerReg.find(win);
+ if (worker)
+ worker.fireClick(popupNode, clickedItemData);
+ },
+
+ _makeDOMElt: function BW__makeDOMElt(item, isInOverflowSubtree) {
+ let elt = item instanceof Item ? this._makeMenuitem(item) :
+ item instanceof Menu ? this._makeMenu(item, isInOverflowSubtree) :
+ item instanceof Separator ? this._makeSeparator() :
+ null;
+ if (!elt)
+ throw new Error("Internal error: can't make element, unknown item type");
+
+ elt.id = domEltIDFromItemID(privateItem(item)._id, isInOverflowSubtree);
+ elt.classList.add(ITEM_CLASS);
+ return elt;
+ },
+
+ // Returns a new xul:menu representing the menu.
+ _makeMenu: function BW__makeMenu(menu, isInOverflowSubtree) {
+ let menuElt = this.doc.createElement("menu");
+ this._setDOMEltLabel(menuElt, menu.label);
+ if (menu.image)
+ this._setDOMEltImage(menuElt, menu.image, true);
+ let popupElt = this.doc.createElement("menupopup");
+ menuElt.appendChild(popupElt);
+
+ let popup = new Popup(popupElt, this, isInOverflowSubtree);
+ let props = this.items[privateItem(menu)._id];
+ if (isInOverflowSubtree)
+ props.overflowPopup = popup;
+ else
+ props.popup = popup;
+
+ return menuElt;
+ },
+
+ // Returns a new xul:menuitem representing the item.
+ _makeMenuitem: function BW__makeMenuitem(item) {
+ let elt = this.doc.createElement("menuitem");
+ this._setDOMEltLabel(elt, item.label);
+ if (item.image)
+ this._setDOMEltImage(elt, item.image, false);
+ if (item.data)
+ this._setDOMEltData(elt, item.data);
+ return elt;
+ },
+
+ // Returns a new xul:menuseparator.
+ _makeSeparator: function BW__makeSeparator() {
+ return this.doc.createElement("menuseparator");
+ }
+};
+
+
+// Responsible for adding DOM elements to and removing them from poupDOMElt.
+function Popup(popupDOMElt, browserWin, isInOverflowSubtree) {
+ this.popupDOMElt = popupDOMElt;
+ this.browserWin = browserWin;
+ this.isInOverflowSubtree = isInOverflowSubtree;
+}
+
+Popup.prototype = {
+
+ addItem: function Popup_addItem(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ let elt = this.isInOverflowSubtree ? props.overflowDOMElt : props.domElt;
+ this.popupDOMElt.appendChild(elt);
+ },
+
+ removeItem: function Popup_removeItem(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ let elt = this.isInOverflowSubtree ? props.overflowDOMElt : props.domElt;
+ this.popupDOMElt.removeChild(elt);
+ }
+};
+
+
+// Represents a browser window's context menu popup. Responsible for hiding and
+// showing items according to the browser window's current context and for
+// handling item clicks.
+function ContextMenuPopup(popupDOMElt, browserWin) {
+ this.popupDOMElt = popupDOMElt;
+ this.browserWin = browserWin;
+ this.doc = popupDOMElt.ownerDocument;
+
+ // item ID => item
+ // Calling this variable "topLevelItems" is redundant, since Popup and
+ // ContextMenuPopup are only responsible for their child items, not all their
+ // descendant items. But calling it "items" might encourage one to believe
+ // otherwise, so topLevelItems it is.
+ this.topLevelItems = {};
+
+ popupDOMElt.addEventListener("popupshowing", this, false);
+ popupDOMElt.addEventListener("command", this, false);
+}
+
+ContextMenuPopup.prototype = {
+
+ addItem: function CMP_addItem(item) {
+ this._ensureStaticEltsExist();
+ let itemID = privateItem(item)._id;
+ this.topLevelItems[itemID] = item;
+ let props = this.browserWin.items[itemID];
+ props.domElt.classList.add(TOPLEVEL_ITEM_CLASS);
+ props.overflowDOMElt.classList.add(OVERFLOW_ITEM_CLASS);
+ this._insertItemInSortedOrder(item);
+ },
+
+ removeItem: function CMP_removeItem(item) {
+ let itemID = privateItem(item)._id;
+ delete this.topLevelItems[itemID];
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ domElt.classList.remove(TOPLEVEL_ITEM_CLASS);
+ overflowDOMElt.classList.remove(OVERFLOW_ITEM_CLASS);
+ this.popupDOMElt.removeChild(domElt);
+ this._overflowPopup.removeChild(overflowDOMElt);
+ },
+
+ // Call this after the item's label changes. This re-inserts the item into
+ // the popup so that it remains in sorted order.
+ itemLabelDidChange: function CMP_itemLabelDidChange(item) {
+ let itemID = privateItem(item)._id;
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ this.popupDOMElt.removeChild(domElt);
+ this._overflowPopup.removeChild(overflowDOMElt);
+ this._insertItemInSortedOrder(item);
+ },
+
+ destroy: function CMP_destroy() {
+ // If there are no more items from any instance of the module, remove the
+ // separator and overflow submenu, if they exist.
+ let elts = this._topLevelElts;
+ if (!elts.length) {
+ let submenu = this._overflowMenu;
+ if (submenu)
+ this.popupDOMElt.removeChild(submenu);
+
+ let sep = this._separator;
+ if (sep)
+ this.popupDOMElt.removeChild(sep);
+ }
+
+ this.popupDOMElt.removeEventListener("popupshowing", this, false);
+ this.popupDOMElt.removeEventListener("command", this, false);
+ },
+
+ handleEvent: function CMP_handleEvent(event) {
+ try {
+ if (event.type === "command")
+ this._handleClick(event.target);
+ else if (event.type === "popupshowing" &&
+ event.target === this.popupDOMElt)
+ this._handlePopupShowing();
+ }
+ catch (err) {
+ console.exception(err);
+ }
+ },
+
+ // command events bubble to the context menu's top-level xul:menupopup and are
+ // caught here.
+ _handleClick: function CMP__handleClick(clickedDOMElt) {
+ if (!clickedDOMElt.classList.contains(ITEM_CLASS))
+ return;
+ let itemID = itemIDFromDOMEltID(clickedDOMElt.id);
+ if (itemID < 0)
+ return;
+ let { item, domElt, overflowDOMElt } = this.browserWin.items[itemID];
+
+ // Bail if the DOM element was not created by this module instance. In
+ // real-world add-ons, the itemID < 0 check above is sufficient, but for the
+ // unit test the JID never changes, making this necessary.
+ if (clickedDOMElt != domElt && clickedDOMElt != overflowDOMElt)
+ return;
+
+ let topLevelItem = privateItem(item)._topLevelItem;
+ let popupNode = this.browserWin.adjustPopupNode(this.browserWin.popupNode,
+ topLevelItem);
+ this.browserWin.fireClick(topLevelItem, popupNode, item.data);
+ },
+
+ // popupshowing is used to show top-level items that match the browser
+ // window's current context and hide items that don't. Each module instance
+ // is responsible for showing and hiding the items it owns.
+ _handlePopupShowing: function CMP__handlePopupShowing() {
+ // If there are items queued up to finish initializing, let them go first.
+ // Otherwise the overflow submenu and menu separator may end up in an
+ // inappropriate state when those items are later added to the menu.
+ if (numItemsWithUnfinishedInit) {
+ const self = this;
+ timer.setTimeout(function popupShowingTryAgain() {
+ self._handlePopupShowing();
+ }, 0);
+ return;
+ }
+
+ // popupDOMElt.triggerNode was added in Gecko 2.0 by bug 383930. The || is
+ // to avoid a Spidermonkey strict warning on earlier versions.
+ let triggerNode = this.popupDOMElt.triggerNode || undefined;
+ let popupNode = this.browserWin.capturePopupNode(triggerNode);
+
+ // Show and hide items. Set a "jetpackContextCurrent" property on the
+ // DOM elements to signal which of our items match the current context.
+ for (let [itemID, item] in Iterator(this.topLevelItems)) {
+ let areContextsCurr =
+ this.browserWin.areAllContextsCurrent(item, popupNode);
+
+ // Change the item's label if the return value was a string.
+ if (typeof(areContextsCurr) === "string") {
+ item.label = areContextsCurr;
+ areContextsCurr = true;
+ }
+
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ domElt.jetpackContextCurrent = areContextsCurr;
+ domElt.hidden = !areContextsCurr;
+ overflowDOMElt.jetpackContextCurrent = areContextsCurr;
+ overflowDOMElt.hidden = !areContextsCurr;
+ }
+
+ // Get the total number of items that match the current context. It's a
+ // little tricky: There may be other instances of this module loaded,
+ // each hiding and showing their items. So we can't base this number on
+ // only our items, or on the hidden state of items. That's why we set
+ // the jetpackContextCurrent property above. The last instance to run
+ // will leave the menupopup in the correct state.
+ let elts = this._topLevelElts;
+ let numShown = Array.reduce(elts, function (total, elt) {
+ return total + (elt.jetpackContextCurrent ? 1 : 0);
+ }, 0);
+
+ // If too many items are shown, show the submenu and hide the top-level
+ // items. Otherwise, hide the submenu and show the top-level items.
+ let overflow = numShown > this._overflowThreshold;
+ if (overflow)
+ Array.forEach(elts, function (e) e.hidden = true);
+
+ let submenu = this._overflowMenu;
+ if (submenu)
+ submenu.hidden = !overflow;
+
+ // If no items are shown, hide the menu separator.
+ let sep = this._separator;
+ if (sep)
+ sep.hidden = numShown === 0;
+ },
+
+ // Adds the menu separator and overflow submenu if they don't exist.
+ _ensureStaticEltsExist: function CMP__ensureStaticEltsExist() {
+ let sep = this._separator;
+ if (!sep) {
+ sep = this._makeSeparator();
+ this.popupDOMElt.appendChild(sep);
+ }
+
+ let submenu = this._overflowMenu;
+ if (!submenu) {
+ submenu = this._makeOverflowMenu();
+ submenu.hidden = true;
+ this.popupDOMElt.insertBefore(submenu, sep.nextSibling);
+ }
+ },
+
+ // Inserts the given item's DOM element into the popup in sorted order.
+ _insertItemInSortedOrder: function CMP__insertItemInSortedOrder(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ this.popupDOMElt.insertBefore(
+ props.domElt, insertionPoint(item.label, this._topLevelElts));
+ this._overflowPopup.insertBefore(
+ props.overflowDOMElt, insertionPoint(item.label, this._overflowElts));
+ },
+
+ // Creates and returns the xul:menu that's shown when too many items are added
+ // to the popup.
+ _makeOverflowMenu: function CMP__makeOverflowMenu() {
+ let submenu = this.doc.createElement("menu");
+ submenu.id = OVERFLOW_MENU_ID;
+ submenu.setAttribute("label", OVERFLOW_MENU_LABEL);
+ let popup = this.doc.createElement("menupopup");
+ popup.id = OVERFLOW_POPUP_ID;
+ submenu.appendChild(popup);
+ return submenu;
+ },
+
+ // Creates and returns the xul:menuseparator that separates the standard
+ // context menu items from our items.
+ _makeSeparator: function CMP__makeSeparator() {
+ let elt = this.doc.createElement("menuseparator");
+ elt.id = SEPARATOR_ID;
+ return elt;
+ },
+
+ // Returns the item elements contained in the overflow menu, a NodeList.
+ get _overflowElts() {
+ return this._overflowPopup.getElementsByClassName(OVERFLOW_ITEM_CLASS);
+ },
+
+ // Returns the overflow xul:menu.
+ get _overflowMenu() {
+ return this.doc.getElementById(OVERFLOW_MENU_ID);
+ },
+
+ // Returns the overflow xul:menupopup.
+ get _overflowPopup() {
+ return this.doc.getElementById(OVERFLOW_POPUP_ID);
+ },
+
+ // Returns the OVERFLOW_THRESH_PREF pref value if it exists or
+ // OVERFLOW_THRESH_DEFAULT if it doesn't.
+ get _overflowThreshold() {
+ let prefs = require("api-utils/preferences-service");
+ return prefs.get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+ },
+
+ // Returns the xul:menuseparator.
+ get _separator() {
+ return this.doc.getElementById(SEPARATOR_ID);
+ },
+
+ // Returns the item elements contained in the top-level menu, a NodeList.
+ get _topLevelElts() {
+ return this.popupDOMElt.getElementsByClassName(TOPLEVEL_ITEM_CLASS);
+ }
+};
+
+
+// Init the browserManager only after setting prototypes and such above, because
+// it will cause browserManager.onTrack to be called immediately if there are
+// open windows.
+browserManager.init();
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/hotkeys.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/hotkeys.js
new file mode 100644
index 0000000..f8c6434
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/hotkeys.js
@@ -0,0 +1,71 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ * Henri Wiechers <hwiechers@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const INVALID_HOTKEY = "Hotkey must have at least one modifier.";
+
+const { toJSON: jsonify, toString: stringify,
+ isFunctionKey } = require("api-utils/keyboard/utils");
+const { register, unregister } = require("api-utils/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;
+ 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/tools/addon-sdk-1.4/packages/addon-kit/lib/notifications.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/notifications.js
new file mode 100644
index 0000000..f2a4ea2
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/notifications.js
@@ -0,0 +1,112 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci, Cr } = require("chrome");
+const apiUtils = require("api-utils/api-utils");
+const errors = require("api-utils/errors");
+
+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: function notificationClickObserved(subject, topic, data) {
+ if (topic === "alertclickcallback")
+ errors.catchAndLog(valOpts.onClick).call(exports, valOpts.data);
+ }
+ };
+ function notifyWithOpts(notifyFn) {
+ notifyFn(valOpts.iconURL, valOpts.title, valOpts.text, !!clickObserver,
+ valOpts.data, clickObserver);
+ }
+ 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 " + valOpts.iconURL +
+ " does not exist. A default icon will be used instead.");
+ delete valOpts.iconURL;
+ notifyWithOpts(notify);
+ }
+ catch (err) {
+ notifyWithOpts(notifyUsingConsole);
+ }
+};
+
+function notifyUsingConsole(iconURL, title, text) {
+ title = title ? "[" + title + "]" : "";
+ text = text || "";
+ let str = [title, text].filter(function (s) s).join(" ");
+ console.log(str);
+}
+
+function validateOptions(options) {
+ return apiUtils.validateOptions(options, {
+ data: {
+ is: ["string", "undefined"]
+ },
+ iconURL: {
+ is: ["string", "undefined"]
+ },
+ onClick: {
+ is: ["function", "undefined"]
+ },
+ text: {
+ is: ["string", "undefined"]
+ },
+ title: {
+ is: ["string", "undefined"]
+ }
+ });
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/page-mod.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/page-mod.js
new file mode 100644
index 0000000..d511464
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/page-mod.js
@@ -0,0 +1,229 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack Packages.
+ *
+ * The Initial Developer of the Original Code is Nickolay Ponomarev.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Nickolay Ponomarev <asqueella@gmail.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const observers = require("api-utils/observer-service");
+const { Worker, Loader } = require('api-utils/content');
+const { EventEmitter } = require('api-utils/events');
+const { List } = require('api-utils/list');
+const { Registry } = require('api-utils/utils/registry');
+const xulApp = require("api-utils/xul-app");
+const { MatchPattern } = require('api-utils/match-pattern');
+
+// Whether or not the host application dispatches a document-element-inserted
+// notification when the document element is inserted into the DOM of a page.
+// The notification was added in Gecko 2.0b6, it's a better time to attach
+// scripts with contentScriptWhen "start" than content-document-global-created,
+// since libraries like jQuery assume the presence of the document element.
+const HAS_DOCUMENT_ELEMENT_INSERTED =
+ xulApp.versionInRange(xulApp.platformVersion, "2.0b6", "*");
+const ON_CONTENT = HAS_DOCUMENT_ELEMENT_INSERTED ? 'document-element-inserted' :
+ 'content-document-global-created';
+
+// Workaround bug 642145: document-element-inserted is fired multiple times.
+// This bug is fixed in Firefox 4.0.1, but we want to keep FF 4.0 compatibility
+// Tracking bug 641457. To be removed when 4.0 has disappeared from earth.
+const HAS_BUG_642145_FIXED =
+ xulApp.versionInRange(xulApp.platformVersion, "2.0.1", "*");
+
+// rules registry
+const RULES = {};
+
+const Rules = EventEmitter.resolve({ toString: null }).compose(List, {
+ add: function() Array.slice(arguments).forEach(function onAdd(rule) {
+ if (this._has(rule))
+ return;
+ // registering rule to the rules registry
+ if (!(rule in RULES))
+ RULES[rule] = new MatchPattern(rule);
+ this._add(rule);
+ this._emit('add', rule);
+ }.bind(this)),
+ remove: function() Array.slice(arguments).forEach(function onRemove(rule) {
+ if (!this._has(rule))
+ return;
+ this._remove(rule);
+ this._emit('remove', rule);
+ }.bind(this)),
+});
+
+/**
+ * PageMod constructor (exported below).
+ * @constructor
+ */
+const PageMod = Loader.compose(EventEmitter, {
+ on: EventEmitter.required,
+ _listeners: EventEmitter.required,
+ contentScript: Loader.required,
+ contentScriptFile: Loader.required,
+ contentScriptWhen: Loader.required,
+ include: null,
+ constructor: function PageMod(options) {
+ this._onContent = this._onContent.bind(this);
+ options = options || {};
+
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ if ('onAttach' in options)
+ this.on('attach', options.onAttach);
+ if ('onError' in options)
+ this.on('error', options.onError);
+
+ let include = options.include;
+ let rules = this.include = Rules();
+ rules.on('add', this._onRuleAdd = this._onRuleAdd.bind(this));
+ rules.on('remove', this._onRuleRemove = this._onRuleRemove.bind(this));
+
+ if (Array.isArray(include))
+ rules.add.apply(null, include);
+ else
+ rules.add(include);
+
+ this.on('error', this._onUncaughtError = this._onUncaughtError.bind(this));
+ pageModManager.add(this._public);
+
+ this._loadingWindows = [];
+ },
+
+ destroy: function destroy() {
+ for each (let rule in this.include)
+ this.include.remove(rule);
+ pageModManager.remove(this._public);
+ this._loadingWindows = [];
+ },
+
+ _loadingWindows: [],
+
+ _onContent: function _onContent(window) {
+ // not registered yet
+ if (!pageModManager.has(this))
+ return;
+
+ if (!HAS_BUG_642145_FIXED) {
+ if (this._loadingWindows.indexOf(window) != -1)
+ return;
+ this._loadingWindows.push(window);
+ }
+
+ if ('start' == this.contentScriptWhen) {
+ this._createWorker(window);
+ return;
+ }
+
+ let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded';
+ let self = this;
+ window.addEventListener(eventName, function onReady(event) {
+ if (event.target.defaultView != window)
+ return;
+ window.removeEventListener(eventName, onReady, true);
+
+ self._createWorker(window);
+ }, true);
+ },
+ _createWorker: function _createWorker(window) {
+ let worker = Worker({
+ window: window,
+ contentScript: this.contentScript,
+ contentScriptFile: this.contentScriptFile,
+ onError: this._onUncaughtError
+ });
+ this._emit('attach', worker);
+ let self = this;
+ worker.once('detach', function detach() {
+ worker.destroy();
+
+ if (!HAS_BUG_642145_FIXED) {
+ let idx = self._loadingWindows.indexOf(window);
+ if (idx != -1)
+ self._loadingWindows.splice(idx, 1);
+ }
+ });
+ },
+ _onRuleAdd: function _onRuleAdd(url) {
+ pageModManager.on(url, this._onContent);
+ },
+ _onRuleRemove: function _onRuleRemove(url) {
+ pageModManager.off(url, this._onContent);
+ },
+ _onUncaughtError: function _onUncaughtError(e) {
+ if (this._listeners('error').length == 1)
+ console.exception(e);
+ }
+});
+exports.PageMod = function(options) PageMod(options)
+exports.PageMod.prototype = PageMod.prototype;
+
+const PageModManager = Registry.resolve({
+ constructor: '_init',
+ _destructor: '_registryDestructor'
+}).compose({
+ constructor: function PageModRegistry(constructor) {
+ this._init(PageMod);
+ observers.add(
+ ON_CONTENT, this._onContentWindow = this._onContentWindow.bind(this)
+ );
+ },
+ _destructor: function _destructor() {
+ observers.remove(ON_CONTENT, this._onContentWindow);
+ for (let rule in RULES) {
+ this._removeAllListeners(rule);
+ delete RULES[rule];
+ }
+ this._registryDestructor();
+ },
+ _onContentWindow: function _onContentWindow(domObj) {
+ let window = HAS_DOCUMENT_ELEMENT_INSERTED ? domObj.defaultView : domObj;
+ // XML documents don't have windows, and we don't yet support them.
+ if (!window)
+ return;
+ for (let rule in RULES)
+ if (RULES[rule].test(window.document.URL))
+ this._emit(rule, window);
+ },
+ off: function off(topic, listener) {
+ this.removeListener(topic, listener);
+ if (!this._listeners(topic).length)
+ delete RULES[topic];
+ }
+});
+const pageModManager = PageModManager();
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/page-worker.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/page-worker.js
new file mode 100644
index 0000000..93750a1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/page-worker.js
@@ -0,0 +1,101 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Felipe Gomes <felipc@gmail.com> (Original Author)
+ * Myk Melez <myk@mozilla.org>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Symbiont } = require("api-utils/content");
+const { Trait } = require("api-utils/traits");
+
+if (!require("api-utils/xul-app").isOneOf(["Firefox", "Thunderbird"])) {
+ throw new Error([
+ "The page-worker module currently supports only Firefox and Thunderbird. ",
+ "In the future, we would like it to support other applications, however. ",
+ "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
+ "information."
+ ].join(""));
+}
+
+const Page = Trait.compose(
+ Symbiont.resolve({
+ constructor: '_initSymbiont'
+ }),
+ {
+ _frame: Trait.required,
+ _initFrame: Trait.required,
+ postMessage: Symbiont.required,
+ on: Symbiont.required,
+ destroy: Symbiont.required,
+
+ constructor: function Page(options) {
+ options = options || {};
+
+ this.contentURL = 'contentURL' in options ? options.contentURL
+ : 'about:blank';
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('allow' in options)
+ this.allow = options.allow;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+
+ this.on('propertyChange', this._onChange.bind(this));
+
+ this._initSymbiont();
+ },
+
+ _onChange: function _onChange(e) {
+ if ('contentURL' in e && this._frame) {
+ // Cleanup the worker before injecting the content script in the new
+ // document
+ this._workerCleanup();
+ this._initFrame(this._frame);
+ }
+ }
+ }
+);
+exports.Page = function(options) Page(options);
+exports.Page.prototype = Page.prototype;
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/panel.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/panel.js
new file mode 100644
index 0000000..442f6a9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/panel.js
@@ -0,0 +1,403 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org> (Original Author)
+ * Irakli Gozalishvili <gozala@mazilla.com>
+ * Mihai Sucan <mihai.sucan@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The panel module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps ",
+ "for more information."
+ ].join(""));
+}
+
+const { Cc, Ci } = require("chrome");
+
+const { validateOptions: valid } = require("api-utils/api-utils");
+const { Symbiont } = require("api-utils/content");
+const { EventEmitter } = require('api-utils/events');
+const timer = require("api-utils/timer");
+const runtime = require("api-utils/runtime");
+
+const windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ ON_SHOW = 'popupshown',
+ ON_HIDE = 'popuphidden',
+ validNumber = { is: ['number', 'undefined', 'null'] };
+
+/**
+ * Emits show and hide events.
+ */
+const Panel = Symbiont.resolve({
+ constructor: '_init',
+ _onInit: '_onSymbiontInit',
+ destroy: '_symbiontDestructor',
+ _documentUnload: '_workerDocumentUnload'
+}).compose({
+ _frame: Symbiont.required,
+ _init: Symbiont.required,
+ _onSymbiontInit: Symbiont.required,
+ _symbiontDestructor: Symbiont.required,
+ _emit: Symbiont.required,
+ _asyncEmit: Symbiont.required,
+ on: Symbiont.required,
+ removeListener: Symbiont.required,
+
+ _inited: false,
+
+ /**
+ * If set to `true` frame loaders between xul panel frame and
+ * hidden frame are swapped. If set to `false` frame loaders are
+ * set back to normal. Setting the value that was already set will
+ * have no effect.
+ */
+ set _frameLoadersSwapped(value) {
+ if (this.__frameLoadersSwapped == value) return;
+ this._frame.QueryInterface(Ci.nsIFrameLoaderOwner)
+ .swapFrameLoaders(this._viewFrame);
+ this.__frameLoadersSwapped = value;
+ },
+ __frameLoadersSwapped: false,
+
+ constructor: function Panel(options) {
+ this._onShow = this._onShow.bind(this);
+ this._onHide = this._onHide.bind(this);
+ this.on('inited', this._onSymbiontInit.bind(this));
+
+ options = options || {};
+ if ('onShow' in options)
+ this.on('show', options.onShow);
+ if ('onHide' in options)
+ this.on('hide', options.onHide);
+ if ('width' in options)
+ this.width = options.width;
+ if ('height' in options)
+ this.height = options.height;
+ if ('contentURL' in options)
+ this.contentURL = options.contentURL;
+
+ this._init(options);
+ },
+ _destructor: function _destructor() {
+ this.hide();
+ this._removeAllListeners('show');
+ // defer cleanup to be performed after panel gets hidden
+ this._xulPanel = null;
+ this._symbiontDestructor(this);
+ this._removeAllListeners(this, 'hide');
+ },
+ destroy: function destroy() {
+ this._destructor();
+ },
+ /* Public API: Panel.width */
+ get width() this._width,
+ set width(value)
+ this._width = valid({ $: value }, { $: validNumber }).$ || this._width,
+ _width: 320,
+ /* Public API: Panel.height */
+ get height() this._height,
+ set height(value)
+ this._height = valid({ $: value }, { $: validNumber }).$ || this._height,
+ _height: 240,
+
+ /* Public API: Panel.isShowing */
+ get isShowing() !!this._xulPanel && this._xulPanel.state == "open",
+
+ /* Public API: Panel.show */
+ show: function show(anchor) {
+ anchor = anchor || null;
+ let document = getWindow(anchor).document;
+ let xulPanel = this._xulPanel;
+ if (!xulPanel) {
+ xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel');
+ xulPanel.setAttribute("type", "arrow");
+
+ // One anonymous node has a big padding that doesn't work well with
+ // Jetpack, as we would like to display an iframe that completely fills
+ // the panel.
+ // -> Use a XBL wrapper with inner stylesheet to remove this padding.
+ let css = ".panel-inner-arrowcontent, .panel-arrowcontent {padding: 0;}";
+ let originalXBL = "chrome://global/content/bindings/popup.xml#arrowpanel";
+ let binding =
+ '<bindings xmlns="http://www.mozilla.org/xbl">' +
+ '<binding id="id" extends="' + originalXBL + '">' +
+ '<resources>' +
+ '<stylesheet src="data:text/css,' +
+ document.defaultView.encodeURIComponent(css) + '"/>' +
+ '</resources>' +
+ '</binding>' +
+ '</bindings>';
+ xulPanel.style.MozBinding = 'url("data:text/xml,' +
+ document.defaultView.encodeURIComponent(binding) + '")';
+
+ let frame = document.createElementNS(XUL_NS, 'iframe');
+ frame.setAttribute('type', 'content');
+ frame.setAttribute('flex', '1');
+ frame.setAttribute('transparent', 'transparent');
+ if (runtime.OS === "Darwin") {
+ frame.style.borderRadius = "6px";
+ frame.style.padding = "1px";
+ }
+
+ // Load an empty document in order to have an immediatly loaded iframe,
+ // so swapFrameLoaders is going to work without having to wait for load.
+ frame.setAttribute("src","data:,");
+
+ xulPanel.appendChild(frame);
+ document.getElementById("mainPopupSet").appendChild(xulPanel);
+ }
+ let { width, height } = this, x, y, position;
+
+ if (!anchor) {
+ // Open the popup in the middle of the window.
+ x = document.documentElement.clientWidth / 2 - width / 2;
+ y = document.documentElement.clientHeight / 2 - height / 2;
+ position = null;
+ }
+ else {
+ // Open the popup by the anchor.
+ let rect = anchor.getBoundingClientRect();
+
+ let window = anchor.ownerDocument.defaultView;
+
+ let zoom = window.mozScreenPixelsPerCSSPixel;
+ 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";
+ position = 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)
+ xulPanel.setAttribute("flip","both");
+ }
+
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ xulPanel.firstChild.style.width = width + "px";
+ xulPanel.firstChild.style.height = height + "px";
+
+ // Wait for the XBL binding to be constructed
+ function waitForBinding() {
+ if (!xulPanel.openPopup) {
+ timer.setTimeout(waitForBinding, 50);
+ return;
+ }
+ xulPanel.openPopup(anchor, position, x, y);
+ }
+ waitForBinding();
+
+ return this._public;
+ },
+ /* Public API: Panel.hide */
+ hide: function hide() {
+ // The popuphiding handler takes care of swapping back the frame loaders
+ // and removing the XUL panel from the application window, we just have to
+ // trigger it by hiding the popup.
+ // XXX Sometimes I get "TypeError: xulPanel.hidePopup is not a function"
+ // when quitting the host application while a panel is visible. To suppress
+ // them, this now checks for "hidePopup" in xulPanel before calling it.
+ // It's not clear if there's an actual issue or the error is just normal.
+ let xulPanel = this._xulPanel;
+ if (xulPanel && "hidePopup" in xulPanel)
+ xulPanel.hidePopup();
+ return this._public;
+ },
+
+ /* Public API: Panel.resize */
+ resize: function resize(width, height) {
+ this.width = width;
+ this.height = height;
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ let xulPanel = this._xulPanel;
+ if (xulPanel) {
+ xulPanel.firstChild.style.width = width + "px";
+ xulPanel.firstChild.style.height = height + "px";
+ }
+ },
+
+ // While the panel is visible, this is the XUL <panel> we use to display it.
+ // Otherwise, it's null.
+ get _xulPanel() this.__xulPanel,
+ set _xulPanel(value) {
+ let xulPanel = this.__xulPanel;
+ if (value === xulPanel) return;
+ if (xulPanel) {
+ xulPanel.removeEventListener(ON_HIDE, this._onHide, false);
+ xulPanel.removeEventListener(ON_SHOW, this._onShow, false);
+ xulPanel.parentNode.removeChild(xulPanel);
+ }
+ if (value) {
+ value.addEventListener(ON_HIDE, this._onHide, false);
+ value.addEventListener(ON_SHOW, this._onShow, false);
+ }
+ this.__xulPanel = value;
+ },
+ __xulPanel: null,
+ get _viewFrame() this.__xulPanel.children[0],
+ /**
+ * When the XUL panel becomes hidden, we swap frame loaders back to move
+ * the content of the panel to the hidden frame & remove panel element.
+ */
+ _onHide: function _onHide() {
+ try {
+ this._frameLoadersSwapped = false;
+ this._xulPanel = null;
+ this._emit('hide');
+ } catch(e) {
+ this._emit('error', e);
+ }
+ },
+ /**
+ * When the XUL panel becomes shown, we swap frame loaders between panel
+ * frame and hidden frame to preserve state of the content dom.
+ */
+ _onShow: function _onShow() {
+ try {
+ if (!this._inited) { // defer if not initialized yet
+ this.on('inited', this._onShow.bind(this));
+ } else {
+ this._frameLoadersSwapped = true;
+
+ // Retrieve computed text color style in order to apply to the iframe
+ // document. As MacOS background is dark gray, we need to use skin's
+ // text color.
+ let win = this._xulPanel.ownerDocument.defaultView;
+ let node = win.document.getAnonymousElementByAttribute(this._xulPanel,
+ "class", "panel-inner-arrowcontent");
+ let textColor = win.getComputedStyle(node).getPropertyValue("color");
+ let doc = this._xulPanel.firstChild.contentDocument;
+ let style = doc.createElement("style");
+ style.textContent = "body { color: " + textColor + "; }";
+ let container = doc.head ? doc.head : doc.documentElement;
+
+ if (container.firstChild)
+ container.insertBefore(style, container.firstChild);
+ else
+ container.appendChild(style);
+
+ this._emit('show');
+ }
+ } catch(e) {
+ this._emit('error', e);
+ }
+ },
+ /**
+ * Notification that panel was fully initialized.
+ */
+ _onInit: function _onInit() {
+ this._inited = true;
+
+ // Avoid panel document from resizing the browser window
+ // New platform capability added through bug 635673
+ if ("allowWindowControl" in this._frame.docShell)
+ this._frame.docShell.allowWindowControl = false;
+
+ // perform all deferred tasks like initSymbiont, show, hide ...
+ // TODO: We're publicly exposing a private event here; this
+ // 'inited' event should really be made private, somehow.
+ this._emit('inited');
+ },
+
+ // Catch document unload event in order to rebind load event listener with
+ // Symbiont._initFrame if Worker._documentUnload destroyed the worker
+ _documentUnload: function(subject, topic, data) {
+ if (this._workerDocumentUnload(subject, topic, data)) {
+ this._initFrame(this._frame);
+ return true;
+ }
+ return false;
+ }
+});
+exports.Panel = function(options) Panel(options)
+exports.Panel.prototype = Panel.prototype;
+
+function getWindow(anchor) {
+ let window;
+
+ if (anchor) {
+ let anchorWindow = anchor.ownerDocument.defaultView.top;
+ let anchorDocument = anchorWindow.document;
+
+ let enumerator = windowMediator.getEnumerator("navigator:browser");
+ while (enumerator.hasMoreElements()) {
+ let enumWindow = enumerator.getNext();
+
+ // Check if the anchor is in this browser window.
+ if (enumWindow == anchorWindow) {
+ window = anchorWindow;
+ break;
+ }
+
+ // Check if the anchor is in a browser tab in this browser window.
+ let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
+ if (browser) {
+ window = enumWindow;
+ break;
+ }
+
+ // Look in other subdocuments (sidebar, etc.)?
+ }
+ }
+
+ // If we didn't find the anchor's window (or we have no anchor),
+ // return the most recent browser window.
+ if (!window)
+ window = windowMediator.getMostRecentWindow("navigator:browser");
+
+ return window;
+}
+
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/passwords.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/passwords.js
new file mode 100644
index 0000000..8225da9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/passwords.js
@@ -0,0 +1,92 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+const { Trait } = require("api-utils/light-traits");
+const utils = require("api-utils/passwords/utils");
+const defer = require("api-utils/utils/function").Enqueued;
+
+/**
+ * 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) {
+ defer(function() {
+ try {
+ onComplete(value);
+ } catch (exception) {
+ onError(exception);
+ }
+ })();
+ }
+ } catch (exception) {
+ onError(exception);
+ }
+ };
+}
+
+exports.search = createWrapperMethod(utils.search);
+exports.store = createWrapperMethod(utils.store);
+exports.remove = createWrapperMethod(utils.remove);
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/private-browsing.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/private-browsing.js
new file mode 100644
index 0000000..8336e7b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/private-browsing.js
@@ -0,0 +1,102 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Paul O’Shannessy <paul@oshannessy.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const observers = require("api-utils/observer-service");
+const { EventEmitter } = require("api-utils/events");
+const { setTimeout } = require("api-utils/timer");
+const unload = require("api-utils/unload");
+
+const ON_START = "start";
+const ON_STOP = "stop";
+const ON_TRANSITION = "private-browsing-transition-complete";
+
+let pbService;
+// Currently, only Firefox implements the private browsing service.
+if (require("api-utils/xul-app").is("Firefox")) {
+ pbService = Cc["@mozilla.org/privatebrowsing;1"].
+ getService(Ci.nsIPrivateBrowsingService);
+}
+
+function toggleMode(value) pbService.privateBrowsingEnabled = !!value
+
+const privateBrowsing = EventEmitter.compose({
+ constructor: function PrivateBrowsing() {
+ // Binding method to instance since it will be used with `setTimeout`.
+ this._emitOnObject = this._emitOnObject.bind(this);
+ this.unload = this.unload.bind(this);
+ // Report unhandled errors from listeners
+ this.on("error", console.exception.bind(console));
+ unload.ensure(this);
+ // We only need to add observers if `pbService` exists.
+ if (pbService) {
+ observers.add(ON_TRANSITION, this.onTransition.bind(this));
+ this._isActive = pbService.privateBrowsingEnabled;
+ }
+ },
+ unload: function _destructor() {
+ this._removeAllListeners(ON_START);
+ this._removeAllListeners(ON_STOP);
+ },
+ // We don't need to do anything with cancel here.
+ onTransition: function onTransition() {
+ let isActive = this._isActive = pbService.privateBrowsingEnabled;
+ setTimeout(this._emitOnObject, 0, exports, isActive ? ON_START : ON_STOP);
+ },
+ get isActive() this._isActive,
+ set isActive(value) {
+ if (pbService)
+ // We toggle private browsing mode asynchronously in order to work around
+ // bug 659629. Since private browsing transitions are asynchronous
+ // anyway, this doesn't significantly change the behavior of the API.
+ setTimeout(toggleMode, 0, value);
+ },
+ _isActive: false
+})()
+
+Object.defineProperty(exports, "isActive", {
+ get: function() privateBrowsing.isActive
+});
+exports.activate = function activate() privateBrowsing.isActive = true;
+exports.deactivate = function deactivate() privateBrowsing.isActive = false;
+exports.on = privateBrowsing.on;
+exports.once = privateBrowsing.once;
+exports.removeListener = privateBrowsing.removeListener;
+
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/request.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/request.js
new file mode 100644
index 0000000..a72b28c
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/request.js
@@ -0,0 +1,309 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Paul O’Shannessy <paul@oshannessy.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+const xpcom = require("api-utils/xpcom");
+const xhr = require("api-utils/xhr");
+const errors = require("api-utils/errors");
+const apiUtils = require("api-utils/api-utils");
+
+// Ugly but will fix with: https://bugzilla.mozilla.org/show_bug.cgi?id=596248
+const EventEmitter = require('api-utils/events').EventEmitter.compose({
+ constructor: function EventEmitter() this
+});
+
+// Instead of creating a new validator for each request, just make one and reuse it.
+const validator = new OptionsValidator({
+ url: {
+ //XXXzpao should probably verify that url is a valid url as well
+ is: ["string"]
+ },
+ headers: {
+ map: function (v) v || {},
+ is: ["object"],
+ },
+ content: {
+ map: function (v) v || null,
+ is: ["string", "object", "null"],
+ },
+ contentType: {
+ map: function (v) v || "application/x-www-form-urlencoded",
+ is: ["string"],
+ },
+ overrideMimeType: {
+ map: function(v) v || null,
+ is: ["string", "null"],
+ }
+});
+
+const REUSE_ERROR = "This request object has been used already. You must " +
+ "create a new one to make a new request."
+
+function Request(options) {
+ const self = EventEmitter(),
+ _public = self._public;
+ // request will hold the actual XHR object
+ let request;
+ let response;
+
+ if ('onComplete' in options)
+ self.on('complete', options.onComplete)
+ options = validator.validateOptions(options);
+
+ // function to prep the request since it's the same between GET and POST
+ function makeRequest(mode) {
+ // If this request has already been used, then we can't reuse it. Throw an error.
+ if (request) {
+ throw new Error(REUSE_ERROR);
+ }
+
+ request = new xhr.XMLHttpRequest();
+
+ let url = options.url;
+ // Build the data to be set. For GET requests, we want to append that to
+ // the URL before opening the request.
+ let data = makeQueryString(options.content);
+ if (mode == "GET" && data) {
+ // If the URL already has ? in it, then we want to just use &
+ url = url + (/\?/.test(url) ? "&" : "?") + data;
+ }
+
+ // open the request
+ request.open(mode, url);
+
+ // request header must be set after open, but before send
+ request.setRequestHeader("Content-Type", options.contentType);
+
+ // set other headers
+ for (let k in options.headers) {
+ request.setRequestHeader(k, options.headers[k]);
+ }
+
+ // set overrideMimeType
+ if (options.overrideMimeType) {
+ request.overrideMimeType(options.overrideMimeType);
+ }
+
+ // handle the readystate, create the response, and call the callback
+ request.onreadystatechange = function () {
+ if (request.readyState == 4) {
+ response = new Response(request);
+ errors.catchAndLog(function () {
+ self._emit('complete', response);
+ })();
+ }
+ }
+
+ // actually send the request. we only want to send data on POST requests
+ request.send(mode == "POST" ? data : null);
+ }
+
+ // Map these setters/getters to the options
+ ["url", "headers", "content", "contentType"].forEach(function (k) {
+ _public.__defineGetter__(k, function () options[k]);
+ _public.__defineSetter__(k, function (v) {
+ // This will automatically rethrow errors from apiUtils.validateOptions.
+ return options[k] = validator.validateSingleOption(k, v);
+ });
+ });
+
+ // response should be available as a getter
+ _public.__defineGetter__("response", function () response);
+
+ _public.get = function () {
+ makeRequest("GET");
+ return this;
+ };
+
+ _public.post = function () {
+ makeRequest("POST");
+ return this;
+ };
+
+ return _public;
+}
+exports.Request = Request;
+
+// Converts an object of unordered key-vals to a string that can be passed
+// as part of a request
+function makeQueryString(content) {
+ // Explicitly return null if we have null, and empty string, or empty object.
+ if (!content) {
+ return null;
+ }
+
+ // If content is already a string, just return it as is.
+ if (typeof(content) == "string") {
+ return content;
+ }
+
+ // 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
+ // fixedEncodeURIComponent 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(fixedEncodeURIComponent(key) + "=" +
+ fixedEncodeURIComponent(val));
+ }
+
+ function make(key, val) {
+ if (typeof(val) === "object" && val !== null) {
+ for ([k, v] in Iterator(val)) {
+ make(key + "[" + k + "]", v);
+ }
+ }
+ else {
+ add(key, val)
+ }
+ }
+ for ([k, v] in Iterator(content)) {
+ make(k, v);
+ }
+ return encodedContent.join("&");
+
+ //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;
+}
+
+
+// encodes a string safely for application/x-www-form-urlencoded
+// adheres to RFC 3986
+// see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Functions/encodeURIComponent
+function fixedEncodeURIComponent (str) {
+ return encodeURIComponent(str).replace(/%20/g, "+").replace(/!/g, "%21").
+ replace(/'/g, "%27").replace(/\(/g, "%28").
+ replace(/\)/g, "%29").replace(/\*/g, "%2A");
+}
+
+function Response(request) {
+ // Define the straight mappings of our value to original request value
+ xpcom.utils.defineLazyGetter(this, "text", function () request.responseText);
+ xpcom.utils.defineLazyGetter(this, "xml", function () {
+ throw new Error("Sorry, the 'xml' property is no longer available. " +
+ "see bug 611042 for more information.");
+ });
+ xpcom.utils.defineLazyGetter(this, "status", function () request.status);
+ xpcom.utils.defineLazyGetter(this, "statusText", function () request.statusText);
+
+ // this.json should be the JS object, so we need to attempt to parse it.
+ xpcom.utils.defineLazyGetter(this, "json", function () {
+ let _json = null;
+ try {
+ _json = JSON.parse(this.text);
+ }
+ catch (e) {}
+ return _json;
+ });
+
+ // this.headers also should be a JS object, so we need to split up the raw
+ // headers string provided by the request.
+ xpcom.utils.defineLazyGetter(this, "headers", function () {
+ let _headers = {};
+ let lastKey;
+ // Since getAllResponseHeaders() will return null if there are no headers,
+ // defend against it by defaulting to ""
+ let rawHeaders = request.getAllResponseHeaders() || "";
+ rawHeaders.split("\n").forEach(function (h) {
+ // According to the HTTP spec, the header string is terminated by an empty
+ // line, so we can just skip it.
+ if (!h.length) {
+ return;
+ }
+
+ let index = h.indexOf(":");
+ // The spec allows for leading spaces, so instead of assuming a single
+ // leading space, just trim the values.
+ let key = h.substring(0, index).trim(),
+ val = h.substring(index + 1).trim();
+
+ // For empty keys, that means that the header value spanned multiple lines.
+ // In that case we should append the value to the value of lastKey with a
+ // new line. We'll assume lastKey will be set because there should never
+ // be an empty key on the first pass.
+ if (key) {
+ _headers[key] = val;
+ lastKey = key;
+ }
+ else {
+ _headers[lastKey] += "\n" + val;
+ }
+ });
+ return _headers;
+ })
+}
+
+// apiUtils.validateOptions doesn't give the ability to easily validate single
+// options, so this is a wrapper that provides that ability.
+function OptionsValidator(rules) {
+ this.rules = rules;
+
+ this.validateOptions = function (options) {
+ return apiUtils.validateOptions(options, this.rules);
+ }
+
+ this.validateSingleOption = function (field, value) {
+ // We need to create a single rule object from our listed rules. To avoid
+ // JavaScript String warnings, check for the field & default to an empty object.
+ let singleRule = {};
+ if (field in this.rules) {
+ singleRule[field] = this.rules[field];
+ }
+ let singleOption = {};
+ singleOption[field] = value;
+ // This should throw if it's invalid, which will bubble up & out.
+ return apiUtils.validateOptions(singleOption, singleRule)[field];
+ }
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/selection.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/selection.js
new file mode 100644
index 0000000..48db7fc
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/selection.js
@@ -0,0 +1,448 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Eric H. Jung <eric.jung@yahoo.com>
+ * Irakli Gozalishivili <gozala@mozilla.com>
+ * Matteo Ferretti <zer0@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The selection module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+let { Ci } = require("chrome"),
+ { setTimeout } = require("api-utils/timer"),
+ { EventEmitter } = require("api-utils/events");
+
+// The selection type HTML
+const HTML = 0x01;
+
+// The selection type TEXT
+const TEXT = 0x02;
+
+// The selection type DOM (internal use only)
+const DOM = 0x03;
+
+// A more developer-friendly message than the caught exception when is not
+// possible change a selection.
+const ERR_CANNOT_CHANGE_SELECTION =
+ "It isn't possible to change the selection, as there isn't currently a selection";
+
+/**
+ * Creates an object from which a selection can be set, get, etc. Each
+ * object has an associated with a range number. Range numbers are the
+ * 0-indexed counter of selection ranges as explained at
+ * https://developer.mozilla.org/en/DOM/Selection.
+ *
+ * @param rangeNumber
+ * The zero-based range index into the selection
+ */
+function Selection(rangeNumber) {
+
+ // In order to hide the private rangeNumber argument from API consumers while
+ // still enabling Selection getters/setters to access it, the getters/setters
+ // are defined as lexical closures in the Selector constructor.
+
+ this.__defineGetter__("text", function () getSelection(TEXT, rangeNumber));
+ this.__defineSetter__("text", function (str) setSelection(str, rangeNumber));
+
+ this.__defineGetter__("html", function () getSelection(HTML, rangeNumber));
+ this.__defineSetter__("html", function (str) setSelection(str, rangeNumber));
+
+ this.__defineGetter__("isContiguous", function () {
+ let sel = getSelection(DOM);
+
+ // If there are multiple ranges, the selection is definitely discontiguous.
+ // It returns `false` also if there are no selection; and `true` if there is
+ // a single non empty range, or a selection in a text field - contiguous or
+ // not (text field selection APIs doesn't support multiple selections).
+
+ if (sel.rangeCount > 1)
+ return false;
+
+ return !!(safeGetRange(sel, 0) || getElementWithSelection());
+ });
+}
+
+require("api-utils/xpcom").utils.defineLazyServiceGetter(this, "windowMediator",
+ "@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator");
+
+/**
+ * Returns the most recent content window
+ */
+function context() {
+ // Overlay names should probably go into the xul-app module instead of here
+ return windowMediator.getMostRecentWindow("navigator:browser").document.
+ commandDispatcher.focusedWindow;
+}
+
+/**
+ * Returns the current selection from most recent content window. Depending on
+ * the specified |type|, the value returned can be a string of text, stringified
+ * HTML, or a DOM selection object as described at
+ * https://developer.mozilla.org/en/DOM/Selection.
+ *
+ * @param type
+ * Specifies the return type of the selection. Valid values are the one
+ * of the constants HTML, TEXT, or DOM.
+ *
+ * @param rangeNumber
+ * Specifies the zero-based range index of the returned selection.
+ */
+function getSelection(type, rangeNumber) {
+ let window, selection;
+ try {
+ window = context();
+ selection = window.getSelection();
+ }
+ catch (e) {
+ return null;
+ }
+
+ // Get the selected content as the specified type
+ if (type == DOM)
+ return selection;
+ else if (type == TEXT) {
+ let range = safeGetRange(selection, rangeNumber);
+
+ if (range)
+ return range.toString();
+
+ let node = getElementWithSelection(window);
+
+ if (!node)
+ return null;
+
+ return node.value.substring(node.selectionStart, node.selectionEnd);
+ }
+ else if (type == HTML) {
+ let range = safeGetRange(selection, rangeNumber);
+ // Another way, but this includes the xmlns attribute for all elements in
+ // Gecko 1.9.2+ :
+ // return Cc["@mozilla.org/xmlextras/xmlserializer;1"].
+ // createInstance(Ci.nsIDOMSerializer).serializeToSTring(range.
+ // cloneContents());
+ if (!range)
+ return null;
+ let node = window.document.createElement("span");
+ node.appendChild(range.cloneContents());
+ return node.innerHTML;
+ }
+ throw new Error("Type " + type + " is unrecognized.");
+}
+
+/**
+ * Returns the specified range in a selection without throwing an exception.
+ *
+ * @param selection
+ * A selection object as described at
+ * https://developer.mozilla.org/en/DOM/Selection
+ *
+ * @param rangeNumber
+ * Specifies the zero-based range index of the returned selection.
+ */
+function safeGetRange(selection, rangeNumber) {
+ try {
+ let range = selection.getRangeAt(rangeNumber);
+ if (!range || range.toString() == "")
+ return null;
+ return range;
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+/**
+ * Returns a reference of the DOM's active element for the window given, if it
+ * supports the text field selection API and has a text selected.
+ *
+ * Note:
+ * we need this method because window.getSelection doesn't return a selection
+ * for text selected in a form field (see bug 85686)
+ *
+ * @param {nsIWindow} [window]
+ * A reference to a window
+ */
+function getElementWithSelection(window) {
+ let element;
+
+ try {
+ element = (window || context()).document.activeElement;
+ }
+ catch (e) {
+ element = null;
+ }
+
+ if (!element)
+ return null;
+
+ let { value, selectionStart, selectionEnd } = element;
+
+ let hasSelection = typeof value === "string" &&
+ !isNaN(selectionStart) &&
+ !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+
+ return hasSelection ? element : null;
+}
+/**
+ * Sets the current selection of the most recent content document by changing
+ * the existing selected text/HTML range to the specified value.
+ *
+ * @param val
+ * The value for the new selection
+ *
+ * @param rangeNumber
+ * The zero-based range index of the selection to be set
+ *
+ */
+function setSelection(val, rangeNumber) {
+ // Make sure we have a window context & that there is a current selection.
+ // Selection cannot be set unless there is an existing selection.
+ let window, selection;
+
+ try {
+ window = context();
+ selection = window.getSelection();
+ }
+ catch (e) {
+ throw new Error(ERR_CANNOT_CHANGE_SELECTION);
+ }
+
+ let range = safeGetRange(selection, rangeNumber);
+
+ if (range) {
+ // Get rid of the current selection and insert our own
+ range.deleteContents();
+ let node = window.document.createElement("span");
+ range.surroundContents(node);
+
+ // Some relevant JEP-111 requirements:
+
+ // Setting the text property replaces the selection with the value to
+ // which the property is set and sets the html property to the same value
+ // to which the text property is being set.
+
+ // Setting the html property replaces the selection with the value to
+ // which the property is set and sets the text property to the text version
+ // of the HTML value.
+
+ // This sets both the HTML and text properties.
+ node.innerHTML = val;
+ } else {
+ let node = getElementWithSelection(window);
+
+ if (!node)
+ throw new Error(ERR_CANNOT_CHANGE_SELECTION);
+
+ let { value, selectionStart, selectionEnd } = node;
+
+ let newSelectionEnd = selectionStart + val.length;
+
+ node.value = value.substring(0, selectionStart) +
+ val +
+ value.substring(selectionEnd, value.length);
+
+ node.setSelectionRange(selectionStart, newSelectionEnd);
+ }
+}
+
+function onLoad(event) {
+ SelectionListenerManager.onLoad(event);
+}
+
+function onUnload(event) {
+ SelectionListenerManager.onUnload(event);
+}
+
+function onSelect() {
+ SelectionListenerManager.onSelect();
+}
+
+let SelectionListenerManager = {
+ QueryInterface: require("api-utils/xpcom").utils.
+ generateQI([Ci.nsISelectionListener]),
+
+ // The collection of listeners wanting to be notified of selection changes
+ listeners: EventEmitter.compose({
+ emit: function emit(type) this._emitOnObject(exports, type),
+ off: function() this._removeAllListeners.apply(this, arguments)
+ })(),
+ /**
+ * This is the nsISelectionListener implementation. This function is called
+ * by Gecko when a selection is changed interactively.
+ *
+ * We only pay attention to the SELECTALL, KEYPRESS, and MOUSEUP selection
+ * reasons. All reasons are listed here:
+ *
+ * http://mxr.mozilla.org/mozilla1.9.2/source/content/base/public/
+ * nsISelectionListener.idl
+ *
+ * The other reasons (NO_REASON, DRAG_REASON, MOUSEDOWN_REASON) aren't
+ * applicable to us.
+ */
+ notifySelectionChanged: function notifySelectionChanged(document, selection,
+ reason) {
+ if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(function(type) reason &
+ Ci.nsISelectionListener[type + "_REASON"]) || selection.toString() == "")
+ return;
+
+ this.onSelect();
+ },
+
+ onSelect : function onSelect() {
+ setTimeout(this.listeners.emit, 0, "select");
+ },
+
+ /**
+ * Part of the Tracker implementation. This function is called by the
+ * tabs module when a browser is being tracked. Often, that means a new tab
+ * has been opened, but it can also mean an addon has been installed while
+ * tabs are already opened. In that case, this function is called for those
+ * already-opened tabs.
+ *
+ * @param browser
+ * The browser being tracked
+ */
+ onTrack: function onTrack(browser) {
+ browser.addEventListener("load", onLoad, true);
+ browser.addEventListener("unload", onUnload, true);
+ },
+
+ onLoad: function onLoad(event) {
+ // Nothing to do without a useful window
+ let window = event.target.defaultView;
+ if (!window)
+ return;
+
+ // Wrap the add selection call with some number of setTimeout 0 because some
+ // reason it's possible to add a selection listener "too early". 2 sometimes
+ // works for gmail, and more consistently with 3, so make it 5 to be safe.
+ let count = 0;
+ let self = this;
+ function wrap(count, func) {
+ if (count-- > 0)
+ require("api-utils/timer").setTimeout(wrap, 0);
+ else
+ self.addSelectionListener(window);
+ }
+ wrap();
+ },
+
+ addSelectionListener: function addSelectionListener(window) {
+ if (window.jetpack_core_selection_listener)
+ return;
+ let selection = window.getSelection();
+ if (selection instanceof Ci.nsISelectionPrivate)
+ selection.addSelectionListener(this);
+
+ // nsISelectionListener implementation seems not fire a notification if
+ // a selection is in a text field, therefore we need to add a listener to
+ // window.onselect, that is fired only for text fields.
+ // https://developer.mozilla.org/en/DOM/window.onselect
+ window.addEventListener("select", onSelect, true);
+
+ window.jetpack_core_selection_listener = true;
+ },
+
+ onUnload: function onUnload(event) {
+ // Nothing to do without a useful window
+ let window = event.target.defaultView;
+ if (!window)
+ return;
+ this.removeSelectionListener(window);
+ this.listeners.off('error');
+ this.listeners.off('selection');
+ },
+
+ removeSelectionListener: function removeSelectionListener(window) {
+ if (!window.jetpack_core_selection_listener)
+ return;
+ let selection = window.getSelection();
+ if (selection instanceof Ci.nsISelectionPrivate)
+ selection.removeSelectionListener(this);
+
+ window.removeEventListener("select", onSelect);
+
+ window.jetpack_core_selection_listener = false;
+ },
+
+ /**
+ * Part of the TabTracker implementation. This function is called by the
+ * tabs module when a browser is being untracked. Usually, that means a tab
+ * has been closed.
+ *
+ * @param browser
+ * The browser being untracked
+ */
+ onUntrack: function onUntrack(browser) {
+ browser.removeEventListener("load", onLoad, true);
+ browser.removeEventListener("unload", onUnload, true);
+ }
+};
+SelectionListenerManager.listeners.on('error', console.error);
+
+/**
+ * Install |SelectionListenerManager| as tab tracker in order to watch
+ * tab opening/closing
+ */
+require("api-utils/tab-browser").Tracker(SelectionListenerManager);
+
+/**
+ * Exports an iterator so that discontiguous selections can be iterated.
+ *
+ * If discontiguous selections are in a text field, only the first one
+ * is returned because the text field selection APIs doesn't support
+ * multiple selections.
+ */
+exports.__iterator__ = function __iterator__() {
+ let sel = getSelection(DOM);
+ let rangeCount = sel.rangeCount || (getElementWithSelection() ? 1 : 0);
+
+ for (let i = 0; i < rangeCount; i++)
+ yield new Selection(i);
+};
+
+exports.on = SelectionListenerManager.listeners.on;
+exports.removeListener = SelectionListenerManager.listeners.removeListener;
+
+// Export the Selection singleton. Its rangeNumber is always zero.
+Selection.call(exports, 0);
+
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-prefs.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-prefs.js
new file mode 100644
index 0000000..c7a059b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-prefs.js
@@ -0,0 +1,107 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Hernan Rodriguez Colmeiro <colmeiro@gmail.com> (Original Author)
+ * Erik Vold <erikvvold@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const { Cc, Ci } = require("chrome");
+const observers = require("observer-service");
+const { EventEmitter } = require("events");
+const unload = require("unload");
+const prefService = require("preferences-service");
+const { jetpackID } = require("@packaging");
+
+const ADDON_BRANCH = "extensions." + jetpackID + ".";
+const BUTTON_PRESSED = jetpackID + "-cmdPressed";
+
+// XXX Currently, only Firefox implements the inline preferences.
+if (!require("xul-app").is("Firefox"))
+ throw Error("This API is only supported in Firefox");
+
+let branch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(ADDON_BRANCH).
+ QueryInterface(Ci.nsIPrefBranch2);
+
+const events = EventEmitter.compose({
+ constructor: function Prefs() {
+ // Log unhandled errors.
+ this.on("error", console.exception.bind(console));
+
+ // Make sure we remove all the listeners
+ unload.ensure(this);
+
+ this._prefObserver = this._prefObserver.bind(this);
+ this._buttonObserver = this._buttonObserver.bind(this);
+
+ // Listen to changes in the preferences
+ branch.addObserver("", this._prefObserver, false);
+
+ // Listen to clicks on buttons
+ observers.add(BUTTON_PRESSED, this._buttonObserver, this);
+ },
+ _prefObserver: function PrefsPrefObserver(subject, topic, prefName) {
+ if (topic == "nsPref:changed") {
+ this._emit(prefName, prefName);
+ }
+ },
+ _buttonObserver: function PrefsButtonObserver(subject, data) {
+ this._emit(data);
+ },
+ unload: function manager_unload() {
+ this._removeAllListeners("error");
+ branch.removeObserver("", this._prefObserver);
+ },
+})();
+
+const simple = Proxy.create({
+ get: function(receiver, pref) {
+ return prefService.get(ADDON_BRANCH + pref);
+ },
+ set: function(receiver, pref, val) {
+ prefService.set(ADDON_BRANCH + pref, val);
+ },
+ delete: function(pref) {
+ prefService.reset(ADDON_BRANCH + pref);
+ return true;
+ },
+ has: function(pref) {
+ return prefService.has(ADDON_BRANCH + pref);
+ }
+});
+
+exports.on = events.on;
+exports.removeListener = events.removeListener;
+exports.prefs = simple;
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js
new file mode 100644
index 0000000..b719e27
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js
@@ -0,0 +1,263 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const file = require("api-utils/file");
+const prefs = require("api-utils/preferences-service");
+const jpSelf = require("self");
+const timer = require("api-utils/timer");
+const unload = require("api-utils/unload");
+const { EventEmitter } = require("api-utils/events");
+const { Trait } = require("api-utils/traits");
+
+const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
+const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+const QUOTA_DEFAULT = 5242880; // 5 MiB
+
+const JETPACK_DIR_BASENAME = "jetpack";
+
+
+// simpleStorage.storage
+exports.__defineGetter__("storage", function () manager.root);
+exports.__defineSetter__("storage", function (val) manager.root = val);
+
+// simpleStorage.quotaUsage
+exports.__defineGetter__("quotaUsage", function () manager.quotaUsage);
+
+// A generic JSON store backed by a file on disk. This should be isolated
+// enough to move to its own module if need be...
+function JsonStore(options) {
+ this.filename = options.filename;
+ this.quota = options.quota;
+ this.writePeriod = options.writePeriod;
+ this.onOverQuota = options.onOverQuota;
+ this.onWrite = options.onWrite;
+
+ unload.ensure(this);
+
+ this.writeTimer = timer.setInterval(this.write.bind(this),
+ this.writePeriod);
+}
+
+JsonStore.prototype = {
+ // The store's root.
+ get root() {
+ return this.isRootInited ? this._root : {};
+ },
+
+ // Performs some type checking.
+ set root(val) {
+ let types = ["array", "boolean", "null", "number", "object", "string"];
+ if (types.indexOf(typeof(val)) < 0) {
+ throw new Error("storage must be one of the following types: " +
+ types.join(", "));
+ }
+ this._root = val;
+ return val;
+ },
+
+ // True if the root has ever been set (either via the root setter or by the
+ // backing file's having been read).
+ get isRootInited() {
+ return this._root !== undefined;
+ },
+
+ // Percentage of quota used, as a number [0, Inf). > 1 implies over quota.
+ // Undefined if there is no quota.
+ get quotaUsage() {
+ return this.quota > 0 ?
+ JSON.stringify(this.root).length / this.quota :
+ undefined;
+ },
+
+ // Removes the backing file and all empty subdirectories.
+ purge: function JsonStore_purge() {
+ try {
+ // This'll throw if the file doesn't exist.
+ file.remove(this.filename);
+ let parentPath = this.filename;
+ do {
+ parentPath = file.dirname(parentPath);
+ // This'll throw if the dir isn't empty.
+ file.rmdir(parentPath);
+ } while (file.basename(parentPath) !== JETPACK_DIR_BASENAME);
+ }
+ catch (err) {}
+ },
+
+ // Initializes the root by reading the backing file.
+ read: function JsonStore_read() {
+ try {
+ let str = file.read(this.filename);
+
+ // Ideally we'd log the parse error with console.error(), but logged
+ // errors cause tests to fail. Supporting "known" errors in the test
+ // harness appears to be non-trivial. Maybe later.
+ this.root = JSON.parse(str);
+ }
+ catch (err) {
+ this.root = {};
+ }
+ },
+
+ // If the store is under quota, writes the root to the backing file.
+ // Otherwise quota observers are notified and nothing is written.
+ write: function JsonStore_write() {
+ if (this.quotaUsage > 1)
+ this.onOverQuota(this);
+ else
+ this._write();
+ },
+
+ // Cleans up on unload. If unloading because of uninstall, the store is
+ // purged; otherwise it's written.
+ unload: function JsonStore_unload(reason) {
+ timer.clearInterval(this.writeTimer);
+ this.writeTimer = null;
+
+ if (reason === "uninstall")
+ this.purge();
+ else
+ this._write();
+ },
+
+ // True if the root is an empty object.
+ get _isEmpty() {
+ if (this.root && typeof(this.root) === "object") {
+ let empty = true;
+ for (let key in this.root) {
+ empty = false;
+ break;
+ }
+ return empty;
+ }
+ return false;
+ },
+
+ // Writes the root to the backing file, notifying write observers when
+ // complete. If the store is over quota or if it's empty and the store has
+ // never been written, nothing is written and write observers aren't notified.
+ _write: function JsonStore__write() {
+ // Don't write if the root is uninitialized or if the store is empty and the
+ // backing file doesn't yet exist.
+ if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
+ return;
+
+ // If the store is over quota, don't write. The current under-quota state
+ // should persist.
+ if (this.quotaUsage > 1)
+ return;
+
+ // Finally, write.
+ let stream = file.open(this.filename, "w");
+ try {
+ stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
+ if (err)
+ console.error("Error writing simple storage file: " + this.filename);
+ else if (this.onWrite)
+ this.onWrite(this);
+ }.bind(this));
+ }
+ catch (err) {
+ // writeAsync closes the stream after it's done, so only close on error.
+ stream.close();
+ }
+ }
+};
+
+
+// This manages a JsonStore singleton and tailors its use to simple storage.
+// The root of the JsonStore is lazy-loaded: The backing file is only read the
+// first time the root's gotten.
+let manager = Trait.compose(EventEmitter, Trait.compose({
+ jsonStore: null,
+
+ // The filename of the store, based on the profile dir and extension ID.
+ get filename() {
+ let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ storeFile.append(JETPACK_DIR_BASENAME);
+ storeFile.append(jpSelf.id);
+ storeFile.append("simple-storage");
+ file.mkpath(storeFile.path);
+ storeFile.append("store.json");
+ return storeFile.path;
+ },
+
+ get quotaUsage() {
+ return this.jsonStore.quotaUsage;
+ },
+
+ get root() {
+ if (!this.jsonStore.isRootInited)
+ this.jsonStore.read();
+ return this.jsonStore.root;
+ },
+
+ set root(val) {
+ return this.jsonStore.root = val;
+ },
+
+ unload: function manager_unload() {
+ this._removeAllListeners("OverQuota");
+ this._removeAllListeners("error");
+ },
+
+ constructor: function manager_constructor() {
+ // Log unhandled errors.
+ this.on("error", console.exception.bind(console));
+ unload.ensure(this);
+
+ this.jsonStore = new JsonStore({
+ filename: this.filename,
+ writePeriod: prefs.get(WRITE_PERIOD_PREF, WRITE_PERIOD_DEFAULT),
+ quota: prefs.get(QUOTA_PREF, QUOTA_DEFAULT),
+ onOverQuota: this._emitOnObject.bind(this, exports, "OverQuota")
+ });
+ }
+}))();
+
+exports.on = manager.on;
+exports.removeListener = manager.removeListener;
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/tabs.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/tabs.js
new file mode 100644
index 0000000..81681d6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/tabs.js
@@ -0,0 +1,62 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Dietrich Ayala <dietrich@mozilla.com> (Original author)
+ * Felipe Gomes <felipc@gmail.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The tabs module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+const { browserWindows } = require("./windows");
+const { tabs } = require("api-utils/windows/tabs");
+
+Object.defineProperties(tabs, {
+ open: { value: function open(options) {
+ if (options.inNewWindow)
+ // `tabs` option is under review and may be removed.
+ return browserWindows.open({ tabs: [ options ] });
+ // Open in active window if new window was not required.
+ return browserWindows.activeWindow.tabs.open(options);
+ }}
+});
+// It's a hack but we will be able to remove it once will implement CommonJS
+// feature that would allow us to override exports.
+exports.__proto__ = tabs;
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/timers.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/timers.js
new file mode 100644
index 0000000..9373dea
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/timers.js
@@ -0,0 +1,40 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+// This module just proxies to the low level equivalent "timer" in "api-utils".
+module.exports = require("api-utils/timer");
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/widget.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/widget.js
new file mode 100644
index 0000000..9a169f4
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/widget.js
@@ -0,0 +1,947 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Dietrich Ayala <dietrich@mozilla.com> (Original Author)
+ * Drew Willcoxon <adw@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Alexandre Poirot <apoirot@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc, Ci} = require("chrome");
+
+// Widget content types
+const CONTENT_TYPE_URI = 1;
+const CONTENT_TYPE_HTML = 2;
+const CONTENT_TYPE_IMAGE = 3;
+
+const ERR_CONTENT = "No content or contentURL property found. Widgets must "
+ + "have one or the other.",
+ ERR_LABEL = "The widget must have a non-empty label property.",
+ ERR_ID = "You have to specify a unique value for the id property of " +
+ "your widget in order for the application to remember its " +
+ "position.",
+ ERR_DESTROYED = "The widget has been destroyed and can no longer be used.";
+
+// Supported events, mapping from DOM event names to our event names
+const EVENTS = {
+ "click": "click",
+ "mouseover": "mouseover",
+ "mouseout": "mouseout",
+};
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The widget module currently supports only Firefox. In the future ",
+ "it will support other applications. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+const { validateOptions } = require("api-utils/api-utils");
+const panels = require("./panel");
+const { EventEmitter, EventEmitterTrait } = require("api-utils/events");
+const { Trait } = require("api-utils/traits");
+const LightTrait = require('api-utils/light-traits').Trait;
+const { Loader, Symbiont } = require("api-utils/content");
+const timer = require("api-utils/timer");
+const { Cortex } = require('api-utils/cortex');
+const windowsAPI = require("./windows");
+const unload = require("api-utils/unload");
+
+// Data types definition
+const valid = {
+ number: { is: ["null", "undefined", "number"] },
+ string: { is: ["null", "undefined", "string"] },
+ id: {
+ is: ["string"],
+ ok: function (v) v.length > 0,
+ msg: ERR_ID,
+ readonly: true
+ },
+ label: {
+ is: ["string"],
+ ok: function (v) v.length > 0,
+ msg: ERR_LABEL
+ },
+ panel: {
+ is: ["null", "undefined", "object"],
+ ok: function(v) !v || v instanceof panels.Panel
+ },
+ width: {
+ is: ["null", "undefined", "number"],
+ map: function (v) {
+ if (null === v || undefined === v) v = 16;
+ return v;
+ },
+ defaultValue: 16
+ },
+ allow: {
+ is: ["null", "undefined", "object"],
+ map: function (v) {
+ if (!v) v = { script: true };
+ return v;
+ },
+ get defaultValue() ({ script: true })
+ },
+};
+
+// Widgets attributes definition
+let widgetAttributes = {
+ label: valid.label,
+ id: valid.id,
+ tooltip: valid.string,
+ width: valid.width,
+ content: valid.string,
+ panel: valid.panel,
+ allow: valid.allow
+};
+
+// Import data definitions from loader, but don't compose with it as Model
+// functions allow us to recreate easily all Loader code.
+let loaderAttributes = require("api-utils/content/loader").validationAttributes;
+for (let i in loaderAttributes)
+ widgetAttributes[i] = loaderAttributes[i];
+
+widgetAttributes.contentURL.optional = true;
+
+// Widgets public events list, that are automatically binded in options object
+const WIDGET_EVENTS = [
+ "click",
+ "mouseover",
+ "mouseout",
+ "error",
+ "message",
+ "attach"
+];
+
+// `Model` utility functions that help creating these various Widgets objects
+let model = {
+
+ // Validate one attribute using api-utils.js:validateOptions function
+ _validate: function _validate(name, suspect, validation) {
+ let $1 = {};
+ $1[name] = suspect;
+ let $2 = {};
+ $2[name] = validation;
+ return validateOptions($1, $2)[name];
+ },
+
+ /**
+ * This method has two purposes:
+ * 1/ Validate and define, on a given object, a set of attribute
+ * 2/ Emit a "change" event on this object when an attribute is changed
+ *
+ * @params {Object} object
+ * Object on which we can bind attributes on and watch for their changes.
+ * This object must have an EventEmitter interface, or, at least `_emit`
+ * method
+ * @params {Object} attrs
+ * Dictionary of attributes definition following api-utils:validateOptions
+ * scheme
+ * @params {Object} values
+ * Dictionary of attributes default values
+ */
+ setAttributes: function setAttributes(object, attrs, values) {
+ let properties = {};
+ for (let name in attrs) {
+ let value = values[name];
+ let req = attrs[name];
+
+ // Retrieve default value from typedef if the value is not defined
+ if ((typeof value == "undefined" || value == null) && req.defaultValue)
+ value = req.defaultValue;
+
+ // Check for valid value if value is defined or mandatory
+ if (!req.optional || typeof value != "undefined")
+ value = model._validate(name, value, req);
+
+ // In any case, define this property on `object`
+ let property = null;
+ if (req.readonly) {
+ property = {
+ value: value,
+ writable: false,
+ enumerable: true,
+ configurable: false
+ };
+ }
+ else {
+ property = model._createWritableProperty(name, value);
+ }
+
+ properties[name] = property;
+ }
+ Object.defineProperties(object, properties);
+ },
+
+ // Generate ES5 property definition for a given attribute
+ _createWritableProperty: function _createWritableProperty(name, value) {
+ return {
+ get: function () {
+ return value;
+ },
+ set: function (newValue) {
+ value = newValue;
+ // The main goal of all this Model stuff is here:
+ // We want to forward all changes to some listeners
+ this._emit("change", name, value);
+ },
+ enumerable: true,
+ configurable: false
+ };
+ },
+
+ /**
+ * Automagically register listeners in options dictionary
+ * by detecting listener attributes with name starting with `on`
+ *
+ * @params {Object} object
+ * Target object that need to follow EventEmitter interface, or, at least,
+ * having `on` method.
+ * @params {Array} events
+ * List of events name to automatically bind.
+ * @params {Object} listeners
+ * Dictionary of event listener functions to register.
+ */
+ setEvents: function setEvents(object, events, listeners) {
+ for (let i = 0, l = events.length; i < l; i++) {
+ let name = events[i];
+ let onName = "on" + name[0].toUpperCase() + name.substr(1);
+ if (!listeners[onName])
+ continue;
+ object.on(name, listeners[onName].bind(object));
+ }
+ }
+
+};
+
+
+/**
+ * Main Widget class: entry point of the widget API
+ *
+ * Allow to control all widget across all existing windows with a single object.
+ * Widget.getView allow to retrieve a WidgetView instance to control a widget
+ * specific to one window.
+ */
+const WidgetTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
+
+ _initWidget: function _initWidget(options) {
+ model.setAttributes(this, widgetAttributes, options);
+
+ browserManager.validate(this);
+
+ // We must have at least content or contentURL defined
+ if (!(this.content || this.contentURL))
+ throw new Error(ERR_CONTENT);
+
+ this._views = [];
+
+ // Set tooltip to label value if we don't have tooltip defined
+ if (!this.tooltip)
+ this.tooltip = this.label;
+
+ model.setEvents(this, WIDGET_EVENTS, options);
+
+ this.on('change', this._onChange.bind(this));
+
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () {
+ let args = arguments;
+ self._views.forEach(function(v) v.port.emit.apply(v.port, args));
+ }
+ });
+ // expose wrapped port, that exposes only public properties.
+ this._port._public = Cortex(this._port);
+
+ // Register this widget to browser manager in order to create new widget on
+ // all new windows
+ browserManager.addItem(this);
+ },
+
+ _onChange: function _onChange(name, value) {
+ // Set tooltip to label value if we don't have tooltip defined
+ if (name == 'tooltip' && !value) {
+ // we need to change tooltip again in order to change the value of the
+ // attribute itself
+ this.tooltip = this.label;
+ return;
+ }
+
+ // Forward attributes changes to WidgetViews
+ if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) {
+ this._views.forEach(function(v) v[name] = value);
+ }
+ },
+
+ _onEvent: function _onEvent(type, eventData) {
+ this._emit(type, eventData);
+ },
+
+ _createView: function _createView() {
+ // Create a new WidgetView instance
+ let view = WidgetView(this);
+
+ // Keep a reference to it
+ this._views.push(view);
+
+ // Emit an `attach` event with a WidgetView instance without private attrs
+ this._emit("attach", view._public);
+
+ return view;
+ },
+
+ // a WidgetView instance is destroyed
+ _onViewDestroyed: function _onViewDestroyed(view) {
+ let idx = this._views.indexOf(view);
+ this._views.splice(idx, 1);
+ },
+
+ /**
+ * Called on browser window closed, to destroy related WidgetViews
+ * @params {ChromeWindow} window
+ * Window that has been closed
+ */
+ _onWindowClosed: function _onWindowClosed(window) {
+ for each (let view in this._views) {
+ if (view._isInChromeWindow(window)) {
+ view.destroy();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Get the WidgetView instance related to a BrowserWindow instance
+ * @params {BrowserWindow} window
+ * BrowserWindow reference from "windows" module
+ */
+ getView: function getView(window) {
+ for each (let view in this._views) {
+ if (view._isInWindow(window)) {
+ return view._public;
+ }
+ }
+ return null;
+ },
+
+ get port() this._port._public,
+ set port(v) {}, // Work around Cortex failure with getter without setter
+ // See bug 653464
+ _port: null,
+
+ postMessage: function postMessage(message) {
+ this._views.forEach(function(v) v.postMessage(message));
+ },
+
+ destroy: function destroy() {
+ if (this.panel)
+ this.panel.destroy();
+
+ // Dispatch destroy calls to views
+ // we need to go backward as we remove items from this array in
+ // _onViewDestroyed
+ for (let i = this._views.length - 1; i >= 0; i--)
+ this._views[i].destroy();
+
+ // Unregister widget to stop creating it over new windows
+ // and allow creation of new widget with same id
+ browserManager.removeItem(this);
+ }
+
+}));
+
+// Widget constructor
+const Widget = function Widget(options) {
+ let w = WidgetTrait.create(Widget.prototype);
+ w._initWidget(options);
+
+ // Return a Cortex of widget in order to hide private attributes like _onEvent
+ let _public = Cortex(w);
+ unload.ensure(_public, "destroy");
+ return _public;
+}
+exports.Widget = Widget;
+
+
+
+/**
+ * WidgetView is an instance of a widget for a specific window.
+ *
+ * This is an external API that can be retrieved by calling Widget.getView or
+ * by watching `attach` event on Widget.
+ */
+const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
+
+ // Reference to the matching WidgetChrome
+ // set right after constructor call
+ _chrome: null,
+
+ // Public interface of the WidgetView, passed in `attach` event or in
+ // Widget.getView
+ _public: null,
+
+ _initWidgetView: function WidgetView__initWidgetView(baseWidget) {
+ this._baseWidget = baseWidget;
+
+ model.setAttributes(this, widgetAttributes, baseWidget);
+
+ this.on('change', this._onChange.bind(this));
+
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () {
+ if (!self._chrome)
+ throw new Error(ERR_DESTROYED);
+ self._chrome.update(self._baseWidget, "emit", arguments);
+ }
+ });
+ // expose wrapped port, that exposes only public properties.
+ this._port._public = Cortex(this._port);
+
+ this._public = Cortex(this);
+ },
+
+ _onChange: function WidgetView__onChange(name, value) {
+ if (name == 'tooltip' && !value) {
+ this.tooltip = this.label;
+ return;
+ }
+
+ // Forward attributes changes to WidgetChrome instance
+ if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) {
+ this._chrome.update(this._baseWidget, name, value);
+ }
+ },
+
+ _onEvent: function WidgetView__onEvent(type, eventData, domNode) {
+ // Dispatch event in view
+ this._emit(type, eventData);
+
+ // And forward it to the main Widget object
+ if ("click" == type || type.indexOf("mouse") == 0)
+ this._baseWidget._onEvent(type, this._public);
+ else
+ this._baseWidget._onEvent(type, eventData);
+
+ // Special case for click events: if the widget doesn't have a click
+ // handler, but it does have a panel, display the panel.
+ if ("click" == type && !this._listeners("click").length && this.panel)
+ this.panel.show(domNode);
+ },
+
+ _isInWindow: function WidgetView__isInWindow(window) {
+ return windowsAPI.BrowserWindow({
+ window: this._chrome.window
+ }) == window;
+ },
+
+ _isInChromeWindow: function WidgetView__isInChromeWindow(window) {
+ return this._chrome.window == window;
+ },
+
+ _onPortEvent: function WidgetView__onPortEvent(args) {
+ let port = this._port;
+ port._emit.apply(port, args);
+ let basePort = this._baseWidget._port;
+ basePort._emit.apply(basePort, args);
+ },
+
+ get port() this._port._public,
+ set port(v) {}, // Work around Cortex failure with getter without setter
+ // See bug 653464
+ _port: null,
+
+ postMessage: function WidgetView_postMessage(message) {
+ if (!this._chrome)
+ throw new Error(ERR_DESTROYED);
+ this._chrome.update(this._baseWidget, "postMessage", message);
+ },
+
+ destroy: function WidgetView_destroy() {
+ this._chrome.destroy();
+ delete this._chrome;
+ this._baseWidget._onViewDestroyed(this);
+ this._emit("detach");
+ }
+
+}));
+
+const WidgetView = function WidgetView(baseWidget) {
+ let w = WidgetViewTrait.create(WidgetView.prototype);
+ w._initWidgetView(baseWidget);
+ return w;
+}
+
+
+
+/**
+ * Keeps track of all browser windows.
+ * Exposes methods for adding/removing widgets
+ * across all open windows (and future ones).
+ * Create a new instance of BrowserWindow per window.
+ */
+let browserManager = {
+ items: [],
+ windows: [],
+
+ // Registers the manager to listen for window openings and closings. Note
+ // that calling this method can cause onTrack to be called immediately if
+ // there are open windows.
+ init: function () {
+ let windowTracker = new (require("api-utils/window-utils").WindowTracker)(this);
+ unload.ensure(windowTracker);
+ },
+
+ // Registers a window with the manager. This is a WindowTracker callback.
+ onTrack: function browserManager_onTrack(window) {
+ if (this._isBrowserWindow(window)) {
+ let win = new BrowserWindow(window);
+ win.addItems(this.items);
+ this.windows.push(win);
+ }
+ },
+
+ // Unregisters a window from the manager. It's told to undo all
+ // modifications. This is a WindowTracker callback. Note that when
+ // WindowTracker is unloaded, it calls onUntrack for every currently opened
+ // window. The browserManager therefore doesn't need to specially handle
+ // unload itself, since unloading the browserManager means untracking all
+ // currently opened windows.
+ onUntrack: function browserManager_onUntrack(window) {
+ if (this._isBrowserWindow(window)) {
+ this.items.forEach(function(i) i._onWindowClosed(window));
+ for (let i = 0; i < this.windows.length; i++) {
+ if (this.windows[i].window == window) {
+ this.windows.splice(i, 1)[0];
+ return;
+ }
+ }
+
+ }
+ },
+
+ // Used to validate widget by browserManager before adding it,
+ // in order to check input very early in widget constructor
+ validate : function (item) {
+ let idx = this.items.indexOf(item);
+ if (idx > -1)
+ throw new Error("The widget " + item + " has already been added.");
+ if (item.id) {
+ let sameId = this.items.filter(function(i) i.id == item.id);
+ if (sameId.length > 0)
+ throw new Error("This widget ID is already used: " + item.id);
+ } else {
+ item.id = this.items.length;
+ }
+ },
+
+ // Registers an item with the manager. It's added to all currently registered
+ // windows, and when new windows are registered it will be added to them, too.
+ addItem: function browserManager_addItem(item) {
+ this.items.push(item);
+ this.windows.forEach(function (w) w.addItems([item]));
+ },
+
+ // Unregisters an item from the manager. It's removed from all windows that
+ // are currently registered.
+ removeItem: function browserManager_removeItem(item) {
+ let idx = this.items.indexOf(item);
+ if (idx > -1)
+ this.items.splice(idx, 1);
+ },
+
+ _isBrowserWindow: function browserManager__isBrowserWindow(win) {
+ let winType = win.document.documentElement.getAttribute("windowtype");
+ return winType === "navigator:browser";
+ }
+};
+
+
+
+/**
+ * Keeps track of a single browser window.
+ *
+ * This is where the core of how a widget's content is added to a window lives.
+ */
+function BrowserWindow(window) {
+ this.window = window;
+ this.doc = window.document;
+}
+
+BrowserWindow.prototype = {
+
+ // Adds an array of items to the window.
+ addItems: function BW_addItems(items) {
+ items.forEach(this._addItemToWindow, this);
+ },
+
+ _addItemToWindow: function BW__addItemToWindow(baseWidget) {
+ // Create a WidgetView instance
+ let widget = baseWidget._createView();
+
+ // Create a WidgetChrome instance
+ let item = new WidgetChrome({
+ widget: widget,
+ doc: this.doc,
+ window: this.window
+ });
+
+ widget._chrome = item;
+
+ this._insertNodeInToolbar(item.node);
+
+ // We need to insert Widget DOM Node before finishing widget view creation
+ // (because fill creates an iframe and tries to access its docShell)
+ item.fill();
+ },
+
+ _insertNodeInToolbar: function BW__insertNodeInToolbar(node) {
+ // Add to the customization palette
+ let toolbox = this.doc.getElementById("navigator-toolbox");
+ let palette = toolbox.palette;
+ palette.appendChild(node);
+
+ // Search for widget toolbar by reading toolbar's currentset attribute
+ let container = null;
+ let toolbars = this.doc.getElementsByTagName("toolbar");
+ let id = node.getAttribute("id");
+ for (let i = 0, l = toolbars.length; i < l; i++) {
+ let toolbar = toolbars[i];
+ if (toolbar.getAttribute("currentset").indexOf(id) == -1)
+ continue;
+ container = toolbar;
+ }
+
+ // if widget isn't in any toolbar, add it to the addon-bar
+ // TODO: we may want some "first-launch" module to do this only on very
+ // first execution
+ if (!container) {
+ container = this.doc.getElementById("addon-bar");
+ // TODO: find a way to make the following code work when we use "cfx run":
+ // http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#8586
+ // until then, force display of addon bar directly from sdk code
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=627484
+ if (container.collapsed)
+ this.window.toggleAddonBar();
+ }
+
+ // Now retrieve a reference to the next toolbar item
+ // by reading currentset attribute on the toolbar
+ let nextNode = null;
+ let currentSet = container.getAttribute("currentset");
+ let ids = (currentSet == "__empty") ? [] : currentSet.split(",");
+ let idx = ids.indexOf(id);
+ if (idx != -1) {
+ for (let i = idx; i < ids.length; i++) {
+ nextNode = this.doc.getElementById(ids[i]);
+ if (nextNode)
+ break;
+ }
+ }
+
+ // Finally insert our widget in the right toolbar and in the right position
+ container.insertItem(id, nextNode, null, false);
+
+ // Update DOM in order to save position if we remove/readd the widget
+ container.setAttribute("currentset", container.currentSet);
+ // Save DOM attribute in order to save position on new window opened
+ this.window.document.persist(container.id, "currentset");
+ }
+}
+
+
+/**
+ * Final Widget class that handles chrome DOM Node:
+ * - create initial DOM nodes
+ * - receive instruction from WidgetView through update method and update DOM
+ * - watch for DOM events and forward them to WidgetView
+ */
+function WidgetChrome(options) {
+ this.window = options.window;
+ this._doc = options.doc;
+ this._widget = options.widget;
+ this._symbiont = null; // set later
+ this.node = null; // set later
+
+ this._createNode();
+}
+
+// Update a property of a widget.
+WidgetChrome.prototype.update = function WC_update(updatedItem, property, value) {
+ switch(property) {
+ case "contentURL":
+ case "content":
+ this.setContent();
+ break;
+ case "width":
+ this.node.style.minWidth = value + "px";
+ this.node.querySelector("iframe").style.width = value + "px";
+ break;
+ case "tooltip":
+ this.node.setAttribute("tooltiptext", value);
+ break;
+ case "postMessage":
+ this._symbiont.postMessage(value);
+ break;
+ case "emit":
+ let port = this._symbiont.port;
+ port.emit.apply(port, value);
+ break;
+ }
+}
+
+// Add a widget to this window.
+WidgetChrome.prototype._createNode = function WC__createNode() {
+ // XUL element container for widget
+ let node = this._doc.createElement("toolbaritem");
+ let guid = require("api-utils/xpcom").makeUuid().toString();
+
+ // Temporary work around require("self") failing on unit-test execution ...
+ let jetpackID = "testID";
+ try {
+ jetpackID = require("self").id;
+ } catch(e) {}
+
+ // Compute an unique and stable widget id with jetpack id and widget.id
+ let id = "widget:" + jetpackID + "-" + this._widget.id;
+ node.setAttribute("id", id);
+ node.setAttribute("label", this._widget.label);
+ node.setAttribute("tooltiptext", this._widget.tooltip);
+ node.setAttribute("align", "center");
+
+ // TODO move into a stylesheet, configurable by consumers.
+ // Either widget.style, exposing the style object, or a URL
+ // (eg, can load local stylesheet file).
+ node.setAttribute("style", [
+ "overflow: hidden; margin: 1px 2px 1px 2px; padding: 0px;",
+ "min-height: 16px;",
+ ].join(""));
+
+ node.style.minWidth = this._widget.width + "px";
+
+ this.node = node;
+}
+
+// Initial population of a widget's content.
+WidgetChrome.prototype.fill = function WC_fill() {
+ // Create element
+ var iframe = this._doc.createElement("iframe");
+ iframe.setAttribute("type", "content");
+ iframe.setAttribute("transparent", "transparent");
+ iframe.style.overflow = "hidden";
+ iframe.style.height = "16px";
+ iframe.style.maxHeight = "16px";
+ iframe.style.width = this._widget.width + "px";
+ iframe.setAttribute("flex", "1");
+ iframe.style.border = "none";
+ iframe.style.padding = "0px";
+
+ // Do this early, because things like contentWindow are null
+ // until the node is attached to a document.
+ this.node.appendChild(iframe);
+
+ // add event handlers
+ this.addEventHandlers();
+
+ // set content
+ this.setContent();
+}
+
+// Get widget content type.
+WidgetChrome.prototype.getContentType = function WC_getContentType() {
+ if (this._widget.content)
+ return CONTENT_TYPE_HTML;
+ return (this._widget.contentURL && /\.(jpg|gif|png|ico)$/.test(this._widget.contentURL))
+ ? CONTENT_TYPE_IMAGE : CONTENT_TYPE_URI;
+}
+
+// Set widget content.
+WidgetChrome.prototype.setContent = function WC_setContent() {
+ let type = this.getContentType();
+ let contentURL = null;
+
+ switch (type) {
+ case CONTENT_TYPE_HTML:
+ contentURL = "data:text/html," + encodeURIComponent(this._widget.content);
+ break;
+ case CONTENT_TYPE_URI:
+ contentURL = this._widget.contentURL;
+ break;
+ case CONTENT_TYPE_IMAGE:
+ let imageURL = this._widget.contentURL;
+ contentURL = "data:text/html,<html><body><img src='" +
+ encodeURI(imageURL) + "'></body></html>";
+ break;
+ default:
+ throw new Error("The widget's type cannot be determined.");
+ }
+
+ let iframe = this.node.firstElementChild;
+
+ let self = this;
+ // Cleanup previously created symbiont (in case we are update content)
+ if (this._symbiont)
+ this._symbiont.destroy();
+
+ this._symbiont = Trait.compose(Symbiont.resolve({
+ _onContentScriptEvent: "_onContentScriptEvent-not-used"
+ }), {
+ _onContentScriptEvent: function () {
+ // Redirect events to WidgetView
+ self._widget._onPortEvent(arguments);
+ }
+ })({
+ frame: iframe,
+ contentURL: contentURL,
+ contentScriptFile: this._widget.contentScriptFile,
+ contentScript: this._widget.contentScript,
+ contentScriptWhen: this._widget.contentScriptWhen,
+ allow: this._widget.allow,
+ onMessage: function(message) {
+ timer.setTimeout(function() {
+ self._widget._onEvent("message", message);
+ }, 0);
+ }
+ });
+}
+
+// Detect if document consists of a single image.
+WidgetChrome._isImageDoc = function WC__isImageDoc(doc) {
+ return doc.body.childNodes.length == 1 &&
+ doc.body.firstElementChild &&
+ doc.body.firstElementChild.tagName == "IMG";
+}
+
+// Set up all supported events for a widget.
+WidgetChrome.prototype.addEventHandlers = function WC_addEventHandlers() {
+ let contentType = this.getContentType();
+
+ let self = this;
+ let listener = function(e) {
+ // Ignore event firings that target the iframe.
+ if (e.target == self.node.firstElementChild)
+ return;
+
+ // The widget only supports left-click for now,
+ // so ignore right-clicks.
+ if (e.type == "click" && e.button == 2)
+ return;
+
+ // Proxy event to the widget
+ timer.setTimeout(function() {
+ self._widget._onEvent(EVENTS[e.type], null, self.node);
+ }, 0);
+ };
+
+ this.eventListeners = {};
+ let iframe = this.node.firstElementChild;
+ for (let [type, method] in Iterator(EVENTS)) {
+ iframe.addEventListener(type, listener, true, true);
+
+ // Store listeners for later removal
+ this.eventListeners[type] = listener;
+ }
+
+ // On document load, make modifications required for nice default
+ // presentation.
+ let self = this;
+ function loadListener(e) {
+ // Ignore event firings that target the iframe
+ if (e.target == iframe)
+ return;
+ // Ignore about:blank loads
+ if (e.type == "load" && e.target.location == "about:blank")
+ return;
+
+ // We may have had an unload event before that cleaned up the symbiont
+ if (!self._symbiont)
+ self.setContent();
+
+ let doc = e.target;
+ if (contentType == CONTENT_TYPE_IMAGE || WidgetChrome._isImageDoc(doc)) {
+ // Force image content to size.
+ // Add-on authors must size their images correctly.
+ doc.body.firstElementChild.style.width = self._widget.width + "px";
+ doc.body.firstElementChild.style.height = "16px";
+ }
+
+ // Allow all content to fill the box by default.
+ doc.body.style.margin = "0";
+ }
+ iframe.addEventListener("load", loadListener, true);
+ this.eventListeners["load"] = loadListener;
+
+ // Register a listener to unload symbiont if the toolbaritem is moved
+ // on user toolbars customization
+ function unloadListener(e) {
+ if (e.target.location == "about:blank")
+ return;
+ self._symbiont.destroy();
+ self._symbiont = null;
+ // This may fail but not always, it depends on how the node is
+ // moved or removed
+ try {
+ self.setContent();
+ } catch(e) {}
+
+ }
+
+ iframe.addEventListener("unload", unloadListener, true);
+ this.eventListeners["unload"] = unloadListener;
+}
+
+// Remove and unregister the widget from everything
+WidgetChrome.prototype.destroy = function WC_destroy(removedItems) {
+ // remove event listeners
+ for (let [type, listener] in Iterator(this.eventListeners))
+ this.node.firstElementChild.removeEventListener(type, listener, true);
+ // remove dom node
+ this.node.parentNode.removeChild(this.node);
+ // cleanup symbiont
+ this._symbiont.destroy();
+ // cleanup itself
+ this.eventListeners = null;
+ this._widget = null;
+ this._symbiont = null;
+}
+
+// Init the browserManager only after setting prototypes and such above, because
+// it will cause browserManager.onTrack to be called immediately if there are
+// open windows.
+browserManager.init();
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/windows.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/windows.js
new file mode 100644
index 0000000..e9e097b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/windows.js
@@ -0,0 +1,238 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Felipe Gomes <felipc@gmail.com> (Original author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+if (!require("api-utils/xul-app").is("Firefox")) {
+ throw new Error([
+ "The windows module currently supports only Firefox. In the future",
+ " we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=571449 for more information."
+ ].join(""));
+}
+
+const { Cc, Ci } = require('chrome'),
+ { Trait } = require('api-utils/traits'),
+ { List } = require('api-utils/list'),
+ { EventEmitter } = require('api-utils/events'),
+ { WindowTabs, WindowTabTracker } = require('api-utils/windows/tabs'),
+ { WindowDom } = require('api-utils/windows/dom'),
+ { WindowLoader } = require('api-utils/windows/loader'),
+ { WindowTrackerTrait } = require('api-utils/window-utils'),
+ { Options } = require('api-utils/tabs/tab'),
+ { utils } = require('api-utils/xpcom'),
+ apiUtils = require('api-utils/api-utils'),
+ unload = require('api-utils/unload'),
+
+ WM = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator),
+
+ BROWSER = 'navigator:browser';
+
+/**
+ * Window trait composes safe wrappers for browser window that are E10S
+ * compatible.
+ */
+const BrowserWindowTrait = Trait.compose(
+ EventEmitter,
+ WindowDom.resolve({ close: '_close' }),
+ WindowTabs,
+ WindowTabTracker,
+ WindowLoader,
+ /* WindowSidebars, */
+ Trait.compose({
+ _emit: Trait.required,
+ _close: Trait.required,
+ _load: Trait.required,
+ /**
+ * Constructor returns wrapper of the specified chrome window.
+ * @param {nsIWindow} window
+ */
+ constructor: function BrowserWindow(options) {
+ // Register this window ASAP, in order to avoid loop that would try
+ // to create this window instance over and over (see bug 648244)
+ windows.push(this);
+
+ // make sure we don't have unhandled errors
+ this.on('error', console.exception.bind(console));
+
+ if ('onOpen' in options)
+ this.on('open', options.onOpen);
+ if ('onClose' in options)
+ this.on('close', options.onClose);
+ if ('window' in options)
+ this._window = options.window;
+ if ('tabs' in options) {
+ this._tabOptions = Array.isArray(options.tabs) ?
+ options.tabs.map(Options) :
+ [ Options(options.tabs) ];
+ }
+ else if ('url' in options) {
+ this._tabOptions = [ Options(options.url) ];
+ }
+ this._load();
+ return this;
+ },
+ _tabOptions: [],
+ _onLoad: function() {
+ try {
+ this._initWindowTabTracker();
+ } catch(e) {
+ this._emit('error', e)
+ }
+ this._emitOnObject(browserWindows, 'open', this._public);
+ },
+ _onUnload: function() {
+ this._destroyWindowTabTracker();
+ this._emitOnObject(browserWindows, 'close', this._public);
+ this._window = null;
+ // Removing reference from the windows array.
+ windows.splice(windows.indexOf(this), 1);
+ this._removeAllListeners('close');
+ this._removeAllListeners('open');
+ this._removeAllListeners('ready');
+ },
+ close: function close(callback) {
+ // maybe we should deprecate this with message ?
+ if (callback) this.on('close', callback);
+ return this._close();
+ }
+ })
+);
+/**
+ * Wrapper for `BrowserWindowTrait`. Creates new instance if wrapper for
+ * window doesn't exists yet. If wrapper already exists then returns it
+ * instead.
+ * @params {Object} options
+ * Options that are passed to the the `BrowserWindowTrait`
+ * @returns {BrowserWindow}
+ * @see BrowserWindowTrait
+ */
+function BrowserWindow(options) {
+ let chromeWindow = options.window;
+ for each (let window in windows) {
+ if (chromeWindow == window._window)
+ return window._public
+ }
+ let window = BrowserWindowTrait(options);
+ return window._public;
+}
+// to have proper `instanceof` behavior will go away when #596248 is fixed.
+BrowserWindow.prototype = BrowserWindowTrait.prototype;
+exports.BrowserWindow = BrowserWindow
+const windows = [];
+/**
+ * `BrowserWindows` trait is composed out of `List` trait and it represents
+ * "live" list of currently open browser windows. Instance mutates itself
+ * whenever new browser window gets opened / closed.
+ */
+// Very stupid to resolve all `toStrings` but this will be fixed by #596248
+const browserWindows = Trait.resolve({ toString: null }).compose(
+ List.resolve({ constructor: '_initList' }),
+ EventEmitter.resolve({ toString: null }),
+ WindowTrackerTrait.resolve({ constructor: '_initTracker', toString: null }),
+ Trait.compose({
+ _emit: Trait.required,
+ _add: Trait.required,
+ _remove: Trait.required,
+
+ // public API
+
+ /**
+ * Constructor creates instance of `Windows` that represents live list of open
+ * windows.
+ */
+ constructor: function BrowserWindows() {
+ this._trackedWindows = [];
+ this._initList();
+ this._initTracker();
+ unload.ensure(this, "_destructor");
+ },
+ _destructor: function _destructor() {
+ this._removeAllListeners('open');
+ this._removeAllListeners('close');
+ },
+ /**
+ * This property represents currently active window.
+ * Property is non-enumerable, in order to preserve array like enumeration.
+ * @type {Window|null}
+ */
+ get activeWindow() {
+ let window = WM.getMostRecentWindow(BROWSER);
+ return this._isBrowser(window) ? BrowserWindow({ window: window }) : null;
+ },
+ open: function open(options) {
+ if (typeof options === "string")
+ // `tabs` option is under review and may be removed.
+ options = { tabs: [Options(options)] };
+ return BrowserWindow(options);
+ },
+ /**
+ * Returns true if specified window is a browser window.
+ * @param {nsIWindow} window
+ * @returns {Boolean}
+ */
+ _isBrowser: function _isBrowser(window)
+ BROWSER === window.document.documentElement.getAttribute("windowtype")
+ ,
+ /**
+ * Internal listener which is called whenever new window gets open.
+ * Creates wrapper and adds to this list.
+ * @param {nsIWindow} chromeWindow
+ */
+ _onTrack: function _onTrack(chromeWindow) {
+ if (!this._isBrowser(chromeWindow)) return;
+ let window = BrowserWindow({ window: chromeWindow });
+ this._add(window);
+ this._emit('open', window);
+ },
+ /**
+ * Internal listener which is called whenever window gets closed.
+ * Cleans up references and removes wrapper from this list.
+ * @param {nsIWindow} window
+ */
+ _onUntrack: function _onUntrack(chromeWindow) {
+ if (!this._isBrowser(chromeWindow)) return;
+ let window = BrowserWindow({ window: chromeWindow });
+ // `_onUnload` method of the `BrowserWindow` will remove `chromeWindow`
+ // from the `windows` array.
+ this._remove(window);
+ this._emit('close', window);
+ }
+ }).resolve({ toString: null })
+)();
+exports.browserWindows = browserWindows;
+
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/package.json b/tools/addon-sdk-1.4/packages/addon-kit/package.json
new file mode 100644
index 0000000..9c62ae8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "addon-kit",
+ "description": "Add-on development made easy.",
+ "keywords": ["javascript", "engine", "platform", "xulrunner"],
+ "author": "Atul Varma (http://toolness.com/) <atul@mozilla.com>",
+ "contributors": [
+ "Myk Melez (http://melez.com/) <myk@mozilla.org>",
+ "Daniel Aquino <mr.danielaquino@gmail.com>"
+ ],
+ "license": "MPL 1.1/GPL 2.0/LGPL 2.1",
+ "dependencies": ["api-utils"]
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/helpers.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/helpers.js
new file mode 100644
index 0000000..bbfe911
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/helpers.js
@@ -0,0 +1,19 @@
+"use strict";
+
+const { Loader } = require("@loader");
+
+exports.Loader = function(module, globals) {
+ var options = JSON.parse(JSON.stringify(require("@packaging")));
+ options.globals = globals;
+ let loader = Loader.new(options);
+ return Object.create(loader, {
+ require: { value: Loader.require.bind(loader, module.path) },
+ sandbox: { value: function sandbox(id) {
+ let path = options.manifest[module.path].requirements[id].path;
+ return loader.sandboxes[path].sandbox;
+ }},
+ unload: { value: function unload(reason, callback) {
+ loader.unload(reason, callback);
+ }}
+ })
+};
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/pagemod-test-helpers.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/pagemod-test-helpers.js
new file mode 100644
index 0000000..7e87a85
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/pagemod-test-helpers.js
@@ -0,0 +1,63 @@
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const timer = require("timer");
+const xulApp = require("xul-app");
+const { Loader } = require('./helpers');
+
+/**
+ * A helper function that creates a PageMod, then opens the specified URL
+ * and checks the effect of the page mod on 'onload' event via testCallback.
+ */
+exports.testPageMod = function testPageMod(test, testURL, pageModOptions,
+ testCallback, timeout) {
+ if (!xulApp.versionInRange(xulApp.platformVersion, "1.9.3a3", "*") &&
+ !xulApp.versionInRange(xulApp.platformVersion, "1.9.2.7", "1.9.2.*")) {
+ test.pass("Note: not testing PageMod, as it doesn't work on this platform version");
+ return null;
+ }
+
+ var wm = Cc['@mozilla.org/appshell/window-mediator;1']
+ .getService(Ci.nsIWindowMediator);
+ var browserWindow = wm.getMostRecentWindow("navigator:browser");
+ if (!browserWindow) {
+ test.pass("page-mod tests: could not find the browser window, so " +
+ "will not run. Use -a firefox to run the pagemod tests.")
+ return null;
+ }
+
+ if (timeout !== undefined)
+ test.waitUntilDone(timeout);
+ else
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let pageMod = loader.require("page-mod");
+
+ var pageMods = [new pageMod.PageMod(opts) for each(opts in pageModOptions)];
+
+ var tabBrowser = browserWindow.gBrowser;
+ var newTab = tabBrowser.addTab(testURL);
+ tabBrowser.selectedTab = newTab;
+ var b = tabBrowser.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.
+ timer.setTimeout(testCallback, 0,
+ b.contentWindow.wrappedJSObject,
+ function done() {
+ pageMods.forEach(function(mod) mod.destroy());
+ // XXX leaks reported if we don't close the tab?
+ tabBrowser.removeTab(newTab);
+ loader.unload();
+ test.done();
+ });
+ }
+ b.addEventListener("load", onPageLoad, true);
+
+ return pageMods;
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-clipboard.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-clipboard.js
new file mode 100644
index 0000000..5f61d48
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-clipboard.js
@@ -0,0 +1,60 @@
+
+// Test the typical use case, setting & getting with no flavors specified
+exports.testWithNoFlavor = function(test) {
+ var contents = "hello there";
+ var flavor = "text";
+ var fullFlavor = "text/unicode";
+ var clip = require("clipboard");
+ // Confirm we set the clipboard
+ test.assert(clip.set(contents));
+ // Confirm flavor is set
+ test.assertEqual(clip.currentFlavors[0], flavor);
+ // Confirm we set the clipboard
+ test.assertEqual(clip.get(), contents);
+ // Confirm we can get the clipboard using the flavor
+ test.assertEqual(clip.get(flavor), contents);
+ // Confirm we can still get the clipboard using the full flavor
+ test.assertEqual(clip.get(fullFlavor), contents);
+};
+
+// Test the slightly less common case where we specify the flavor
+exports.testWithFlavor = function(test) {
+ var contents = "<b>hello there</b>";
+ var contentsText = "hello there";
+ var flavor = "html";
+ var fullFlavor = "text/html";
+ var unicodeFlavor = "text";
+ var unicodeFullFlavor = "text/unicode";
+ var clip = require("clipboard");
+ test.assert(clip.set(contents, flavor));
+ test.assertEqual(clip.currentFlavors[0], unicodeFlavor);
+ test.assertEqual(clip.currentFlavors[1], flavor);
+ test.assertEqual(clip.get(), contentsText);
+ test.assertEqual(clip.get(flavor), contents);
+ test.assertEqual(clip.get(fullFlavor), contents);
+ test.assertEqual(clip.get(unicodeFlavor), contentsText);
+ test.assertEqual(clip.get(unicodeFullFlavor), contentsText);
+};
+
+// Test that the typical case still works when we specify the flavor to set
+exports.testWithRedundantFlavor = function(test) {
+ var contents = "<b>hello there</b>";
+ var flavor = "text";
+ var fullFlavor = "text/unicode";
+ var clip = require("clipboard");
+ test.assert(clip.set(contents, flavor));
+ test.assertEqual(clip.currentFlavors[0], flavor);
+ test.assertEqual(clip.get(), contents);
+ test.assertEqual(clip.get(flavor), contents);
+ test.assertEqual(clip.get(fullFlavor), contents);
+};
+
+exports.testNotInFlavor = function(test) {
+ var contents = "hello there";
+ var flavor = "html";
+ var clip = require("clipboard");
+ test.assert(clip.set(contents));
+ // If there's nothing on the clipboard with this flavor, should return null
+ test.assertEqual(clip.get(flavor), null);
+};
+// TODO: Test error cases.
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.html b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.html
new file mode 100644
index 0000000..a0fb5cb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.html
@@ -0,0 +1,78 @@
+<!-- ***** BEGIN LICENSE BLOCK *****
+ - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ -
+ - The contents of this file are subject to the Mozilla Public License Version
+ - 1.1 (the "License"); you may not use this file except in compliance with
+ - the License. You may obtain a copy of the License at
+ - http://www.mozilla.org/MPL/
+ -
+ - Software distributed under the License is distributed on an "AS IS" basis,
+ - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ - for the specific language governing rights and limitations under the
+ - License.
+ -
+ - The Original Code is Jetpack.
+ -
+ - The Initial Developer of the Original Code is
+ - the Mozilla Foundation.
+ - Portions created by the Initial Developer are Copyright (C) 2010
+ - the Initial Developer. All Rights Reserved.
+ -
+ - Contributor(s):
+ - Drew Willcoxon <adw@mozilla.com> (Original Author)
+ -
+ - Alternatively, the contents of this file may be used under the terms of
+ - either the GNU General Public License Version 2 or later (the "GPL"), or
+ - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ - in which case the provisions of the GPL or the LGPL are applicable instead
+ - of those above. If you wish to allow use of your version of this file only
+ - under the terms of either the GPL or the LGPL, and not to allow others to
+ - use your version of this file under the terms of the MPL, indicate your
+ - decision by deleting the provisions above and replace them with the notice
+ - and other provisions required by the LGPL or the GPL. If you do not delete
+ - the provisions above, a recipient may use your version of this file under
+ - the terms of any one of the MPL, the GPL or the LGPL.
+ -
+ - ***** END LICENSE BLOCK ***** -->
+
+<html>
+ <head>
+ <title>Context menu test</title>
+ </head>
+ <body>
+ <p>
+ <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==">
+ </p>
+
+ <p>
+ <a id="link" href="">
+ A simple link.
+ </a>
+ </p>
+
+ <p>
+ <a href="">
+ <span id="span-link">
+ A span inside a link.
+ </span>
+ </a>
+ </p>
+
+ <p id="text">
+ Some text.
+ </p>
+
+ <p>
+ <textarea id="textfield">
+ A text field,
+ with some text.
+ </textarea>
+ </p>
+
+ <p>
+ <iframe id="iframe" src="data:text/html,An iframe."
+ width="200" height="100">
+ </iframe>
+ </p>
+ </body>
+</html>
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js
new file mode 100644
index 0000000..157edf1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js
@@ -0,0 +1,2075 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ * Matteo Ferretti <zer0@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let {Cc,Ci} = require("chrome");
+const { Loader } = require('./helpers');
+
+// These should match the same constants in the module.
+const ITEM_CLASS = "jetpack-context-menu-item";
+const SEPARATOR_ID = "jetpack-context-menu-separator";
+const OVERFLOW_THRESH_DEFAULT = 10;
+const OVERFLOW_THRESH_PREF =
+ "extensions.addon-sdk.context-menu.overflowThreshold";
+const OVERFLOW_MENU_ID = "jetpack-content-menu-overflow-menu";
+const OVERFLOW_POPUP_ID = "jetpack-content-menu-overflow-popup";
+
+const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html");
+
+
+// Destroying items that were previously created should cause them to be absent
+// from the menu.
+exports.testConstructDestroy = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ // Create an item.
+ let item = new loader.cm.Item({ label: "item" });
+ test.assertEqual(item.parentMenu, null, "item's parent menu should be null");
+
+ 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]);
+ test.done();
+ });
+ });
+};
+
+
+// Destroying an item twice should not cause an error.
+exports.testDestroyTwice = function (test) {
+ test = new TestHelper(test);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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(doc.getElementById("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 (test) {
+ test = new TestHelper(test);
+ 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(doc.getElementById("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 (test) {
+ test = new TestHelper(test);
+ 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], []);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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(doc.getElementById("image"), function (popup) {
+ test.checkMenu([], items, []);
+ test.done();
+ });
+ });
+};
+
+
+// Selection contexts should cause items to appear when a selection exists.
+exports.testSelectionContextMatch = function (test) {
+ test = new TestHelper(test);
+ 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 (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({
+ label: "item",
+ context: loader.cm.SelectionContext()
+ });
+
+ test.withTestDoc(function (window, doc) {
+ let textfield = doc.getElementById("textfield");
+ textfield.setSelectionRange(0, textfield.value.length);
+ 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 (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({
+ label: "item",
+ context: loader.cm.SelectionContext()
+ });
+
+ test.withTestDoc(function (window, doc) {
+ let textfield = doc.getElementById("textfield");
+ textfield.setSelectionRange(0, 0);
+ test.showMenu(textfield, function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+ });
+};
+
+
+// Selection contexts should not cause items to appear when a selection does
+// not exist.
+exports.testSelectionContextNoMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({
+ label: "item",
+ context: loader.cm.SelectionContext()
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+};
+
+
+// URL contexts should cause items to appear on pages that match.
+exports.testURLContextMatch = function (test) {
+ test = new TestHelper(test);
+ 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"])
+ })
+ ];
+
+ 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 (test) {
+ test = new TestHelper(test);
+ 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"])
+ })
+ ];
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], items, []);
+ test.done();
+ });
+ });
+};
+
+
+// Removing a non-matching URL context after its item is created and the page is
+// loaded should cause the item's content script to be evaluated.
+exports.testURLContextRemove = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let shouldBeEvaled = false;
+ let context = loader.cm.URLContext("*.bogus.com");
+ let item = loader.cm.Item({
+ label: "item",
+ context: context,
+ contentScript: 'self.postMessage("ok");',
+ onMessage: function (msg) {
+ test.assert(shouldBeEvaled,
+ "content script should be evaluated when expected");
+ shouldBeEvaled = false;
+ test.done();
+ }
+ });
+
+ test.withTestDoc(function (window, doc) {
+ shouldBeEvaled = true;
+ item.context.remove(context);
+ });
+};
+
+
+// Adding a non-matching URL context after its item is created and the page is
+// loaded should cause the item's worker to be destroyed.
+exports.testURLContextAdd = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({ label: "item" });
+
+ test.withTestDoc(function (window, doc) {
+ let privatePropsKey = loader.globalScope.PRIVATE_PROPS_KEY;
+ let workerReg = item.valueOf(privatePropsKey)._workerReg;
+
+ let found = false;
+ for each (let winWorker in workerReg.winWorkers) {
+ if (winWorker.win === window) {
+ found = true;
+ break;
+ }
+ }
+ this.test.assert(found, "window should be present in worker registry");
+
+ item.context.add(loader.cm.URLContext("*.bogus.com"));
+
+ for each (let winWorker in workerReg.winWorkers)
+ this.test.assertNotEqual(winWorker.win, window,
+ "window should not be present in worker registry");
+
+ test.done();
+ });
+};
+
+
+// Content contexts that return true should cause their items to be present
+// in the menu.
+exports.testContentContextMatch = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript: 'self.on("context", function () 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 (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript: 'self.on("context", function () false);'
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], [item], []);
+ 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 (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "first label",
+ contentScript: 'self.on("context", function () "second label");'
+ });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item], [], []);
+ test.assertEqual(item.label, "second label",
+ "item's label should be updated");
+ test.done();
+ });
+};
+
+
+// The args passed to context listeners should be correct.
+exports.testContentContextArgs = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+ let callbacks = 0;
+
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript: 'self.on("context", function (node) {' +
+ ' let Ci = Components.interfaces;' +
+ ' self.postMessage(node instanceof Ci.nsIDOMHTMLElement);' +
+ ' return false;' +
+ '});',
+ onMessage: function (isElt) {
+ test.assert(isElt, "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, and content context
+// listeners should not be called if all declarative contexts are not current.
+exports.testMultipleContexts = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()],
+ contentScript: 'self.on("context", function () self.postMessage());',
+ onMessage: function () {
+ test.fail("Context listener should not be called");
+ }
+ });
+
+ test.withTestDoc(function (window, doc) {
+ test.showMenu(doc.getElementById("span-link"), function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+ });
+};
+
+
+// Once a context is removed, it should no longer cause its item to appear.
+exports.testRemoveContext = function (test) {
+ test = new TestHelper(test);
+ 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(doc.getElementById("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(doc.getElementById("image"), function (popup) {
+ test.checkMenu([], [item], []);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Lots of items should overflow into the overflow submenu.
+exports.testOverflow = function (test) {
+ test = new TestHelper(test);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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]);
+ test.done();
+ });
+ });
+};
+
+
+// Using multiple module instances to add items without causing overflow should
+// work OK. Assumes OVERFLOW_THRESH_DEFAULT <= 2.
+exports.testMultipleModulesAdd = function (test) {
+ test = new TestHelper(test);
+ 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([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]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Using multiple module instances to add items causing overflow should work OK.
+exports.testMultipleModulesAddOverflow = function (test) {
+ test = new TestHelper(test);
+ 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([item1], [], 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);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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([item1], [item0], []);
+ popup.hidePopup();
+
+ // Unload module 0.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // item0 should be removed from the menu.
+ test.checkMenu([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]);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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([item1], [item0], []);
+ popup.hidePopup();
+
+ // Unload module 0.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // item0 should be removed from the menu.
+ test.checkMenu([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]);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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([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]);
+ popup.hidePopup();
+
+ // Unload module 0.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // Both items should be removed from the menu.
+ test.checkMenu([], [], [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 (test) {
+ test = new TestHelper(test);
+ 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([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]);
+ popup.hidePopup();
+
+ // Unload module 0.
+ loader0.unload();
+ test.showMenu(null, function (popup) {
+
+ // Both items should be removed from the menu.
+ test.checkMenu([], [], [item0, item1]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Test interactions between a loaded module, unloading another module, and the
+// menu separator and overflow submenu.
+exports.testMultipleModulesAddRemove = function (test) {
+ test = new TestHelper(test);
+ 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]);
+ 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]);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// An item's click listener should work.
+exports.testItemClick = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "item",
+ data: "item data",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' let Ci = Components.interfaces;' +
+ ' self.postMessage({' +
+ ' isElt: node instanceof Ci.nsIDOMHTMLElement,' +
+ ' data: data' +
+ ' });' +
+ '});',
+ onMessage: function (data) {
+ test.assertEqual(this, item, "`this` inside onMessage should be item");
+ test.assert(data.isElt, "node should be an HTML element");
+ test.assertEqual(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 (test) {
+ // Create a top-level menu, submenu, and item, like this:
+ // topMenu -> submenu -> item
+ // Click the item and make sure the click bubbles.
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = new loader.cm.Item({
+ label: "submenu item",
+ data: "submenu item data"
+ });
+
+ let submenu = new loader.cm.Menu({
+ label: "submenu",
+ items: [item]
+ });
+
+ let topMenu = new loader.cm.Menu({
+ label: "top menu",
+ contentScript: 'self.on("click", function (node, data) {' +
+ ' let Ci = Components.interfaces;' +
+ ' self.postMessage({' +
+ ' isAnchor: node instanceof Ci.nsIDOMHTMLAnchorElement,' +
+ ' data: data' +
+ ' });' +
+ '});',
+ onMessage: function (data) {
+ test.assertEqual(this, topMenu, "`this` inside top menu should be menu");
+ test.assert(data.isAnchor, "Clicked node should be anchor");
+ test.assertEqual(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(doc.getElementById("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 (test) {
+ test = new TestHelper(test);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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();
+ });
+};
+
+
+// Existing context menu modifications should apply to new windows.
+exports.testNewWindow = function (test) {
+ test = new TestHelper(test);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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([], [], []);
+ test.done();
+ });
+ });
+ });
+};
+
+
+// Items in the context menu should be sorted according to locale.
+exports.testSorting = function (test) {
+ test = new TestHelper(test);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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 (test) {
+ test = new TestHelper(test);
+ 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);
+ test.done();
+ });
+ });
+};
+
+
+// The binary search of insertionPoint should work OK.
+exports.testInsertionPoint = function (test) {
+ function mockElts(labels) {
+ return labels.map(function (label) {
+ return { label: label, getAttribute: function (l) label };
+ });
+ }
+
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+ let insertionPoint = loader.globalScope.insertionPoint;
+
+ let ip = insertionPoint("a", []);
+ test.assertStrictEqual(ip, null, "Insertion point should be null");
+
+ ip = insertionPoint("a", mockElts(["b"]));
+ test.assertEqual(ip.label, "b", "Insertion point should be 'b'");
+
+ ip = insertionPoint("c", mockElts(["b"]));
+ test.assertStrictEqual(ip, null, "Insertion point should be null");
+
+ ip = insertionPoint("b", mockElts(["a", "c"]));
+ test.assertEqual(ip.label, "c", "Insertion point should be 'c'");
+
+ ip = insertionPoint("c", mockElts(["a", "b", "d"]));
+ test.assertEqual(ip.label, "d", "Insertion point should be 'd'");
+
+ ip = insertionPoint("a", mockElts(["b", "c", "d"]));
+ test.assertEqual(ip.label, "b", "Insertion point should be 'b'");
+
+ ip = insertionPoint("d", mockElts(["a", "b", "c"]));
+ test.assertStrictEqual(ip, null, "Insertion point should be null");
+
+ 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 (test) {
+ test = new TestHelper(test);
+ 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) {
+ test.assertEqual(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 (test) {
+ test = new TestHelper(test);
+ test.withTestDoc(function (window, doc) {
+ let loader = test.newLoader();
+ let item = new loader.cm.Item({
+ label: "item",
+ contentScript:
+ 'self.on("click", function () self.postMessage("click"));',
+ onMessage: function (msg) {
+ if (msg === "click")
+ test.done();
+ }
+ });
+ test.showMenu(null, 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 and, if necessary, its order within the menu.
+exports.testSetLabelBeforeShow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let items = [
+ new loader.cm.Item({ label: "a" }),
+ new loader.cm.Item({ label: "b" })
+ ]
+ items[0].label = "z";
+ test.assertEqual(items[0].label, "z");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([items[1], items[0]], [], []);
+ test.done();
+ });
+};
+
+
+// Setting an item's label after the menu is shown should correctly change its
+// label and, if necessary, its order within the menu.
+exports.testSetLabelAfterShow = function (test) {
+ test = new TestHelper(test);
+ 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";
+ test.assertEqual(items[0].label, "z");
+ test.showMenu(null, function (popup) {
+ test.checkMenu([items[1], items[0]], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// Setting an item's label before the menu is ever shown should correctly change
+// its label and, if necessary, its order within the menu if the item is in the
+// overflow submenu.
+exports.testSetLabelBeforeShowOverflow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let prefs = loader.loader.require("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";
+ test.assertEqual(items[0].label, "z");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([items[1], items[0]], [], []);
+ prefs.set(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+ test.done();
+ });
+};
+
+
+// Setting an item's label after the menu is shown should correctly change its
+// label and, if necessary, its order within the menu if the item is in the
+// overflow submenu.
+exports.testSetLabelAfterShowOverflow = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let prefs = loader.loader.require("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";
+ test.assertEqual(items[0].label, "z");
+ test.showMenu(null, function (popup) {
+ test.checkMenu([items[1], items[0]], [], []);
+ prefs.set(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+ test.done();
+ });
+ });
+};
+
+
+// Setting the label of an item in a Menu should work.
+exports.testSetLabelMenuItem = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: [loader.cm.Item({ label: "a" })]
+ });
+ menu.items[0].label = "z";
+
+ test.assertEqual(menu.items[0].label, "z");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Menu.addItem() should work.
+exports.testMenuAddItem = function (test) {
+ test = new TestHelper(test);
+ 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" }));
+
+ test.assertEqual(menu.items.length, 3,
+ "menu should have correct number of items");
+ for (let i = 0; i < 3; i++) {
+ test.assertEqual(menu.items[i].label, "item " + i,
+ "item label should be correct");
+ test.assertEqual(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 (test) {
+ test = new TestHelper(test);
+ 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);
+
+ test.assertEqual(menu.items.length, 2,
+ "menu should have correct number of items");
+ for (let i = 0; i < 2; i++) {
+ test.assertEqual(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 (test) {
+ test = new TestHelper(test);
+ 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);
+
+ test.assertEqual(subitem.parentMenu, null,
+ "item's parent menu should be correct");
+
+ test.assertEqual(menu.items.length, 2,
+ "menu should have correct number of items");
+ test.assertEqual(menu.items[0].label, "item 0",
+ "item label should be correct");
+ test.assertEqual(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 (test) {
+ test = new TestHelper(test);
+ 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);
+
+ test.assertEqual(menu0.items.length, 0,
+ "menu should have correct number of items");
+
+ test.assertEqual(menu1.items.length, 1,
+ "menu should have correct number of items");
+ test.assertEqual(menu1.items[0].label, "item",
+ "item label should be correct");
+
+ test.assertEqual(subitem.parentMenu, menu1,
+ "item's parent menu should be correct");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu0, menu1], [], []);
+ test.done();
+ });
+};
+
+
+// Destroying an item should remove it from its parent menu.
+exports.testMenuItemDestroy = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let subitem = loader.cm.Item({ label: "item" });
+ let menu = loader.cm.Menu({
+ label: "menu",
+ items: [subitem]
+ });
+ subitem.destroy();
+
+ test.assertEqual(menu.items.length, 0,
+ "menu should have correct number of items");
+ test.assertEqual(subitem.parentMenu, null,
+ "item's parent menu should be correct");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([menu], [], []);
+ test.done();
+ });
+};
+
+
+// Setting Menu.items should work.
+exports.testMenuItemsSetter = function (test) {
+ test = new TestHelper(test);
+ 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" })
+ ];
+
+ test.assertEqual(menu.items.length, 3,
+ "menu should have correct number of items");
+ for (let i = 0; i < 3; i++) {
+ test.assertEqual(menu.items[i].label, "new item " + i,
+ "item label should be correct");
+ test.assertEqual(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 (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let item = loader.cm.Item({ label: "old item 0", data: "old" });
+ item.data = "new";
+
+ test.assertEqual(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 (test) {
+ test = new TestHelper(test);
+ test.withTestDoc(function (window, doc) {
+ let loader = test.newLoader();
+ let item = new loader.cm.Item({
+ label: "item"
+ });
+ test.showMenu(doc.getElementById("iframe"), function (popup) {
+ test.checkMenu([item], [], []);
+ test.done();
+ });
+ });
+};
+
+
+// Test image support.
+exports.testItemImage = function (test) {
+ test = new TestHelper(test);
+ let loader = test.newLoader();
+
+ let imageURL = require("self").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: [] });
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([item, menu], [], []);
+
+ let imageURL2 = require("self").data.url("dummy.ico");
+ item.image = imageURL2;
+ menu.image = imageURL2;
+ test.checkMenu([item, menu], [], []);
+
+ item.image = null;
+ menu.image = null;
+ test.checkMenu([item, menu], [], []);
+
+ test.done();
+ });
+};
+
+
+// Menu.destroy should destroy the item tree rooted at that menu.
+exports.testMenuDestroy = function (test) {
+ test = new TestHelper(test);
+ 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++;
+ });
+ test.assertEqual(numRegistryEntries, 0, "All items should be unregistered.");
+
+ test.showMenu(null, function (popup) {
+ test.checkMenu([], [], [menu]);
+ test.done();
+ });
+};
+
+
+// NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////////
+
+// Run only a dummy test if context-menu doesn't support the host app.
+if (!require("xul-app").is("Firefox")) {
+ for (let [prop, val] in Iterator(exports))
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ exports.testAppNotSupported = function (test) {
+ test.pass("context-menu does not support this application.");
+ };
+}
+
+
+// This makes it easier to run tests by handling things like opening the menu,
+// opening new windows, making assertions, etc. Methods on |test| can be called
+// on instances of this class. Don't forget to call done() to end the test!
+// WARNING: This looks up items in popups by comparing labels, so don't give two
+// items the same label.
+function TestHelper(test) {
+ // default waitUntilDone timeout is 10s, which is too short on the win7
+ // buildslave
+ test.waitUntilDone(30*1000);
+ this.test = test;
+ this.loaders = [];
+ this.browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+}
+
+TestHelper.prototype = {
+ get contextMenuPopup() {
+ return this.browserWindow.document.getElementById("contentAreaContextMenu");
+ },
+
+ get contextMenuSeparator() {
+ return this.browserWindow.document.getElementById(SEPARATOR_ID);
+ },
+
+ get overflowPopup() {
+ return this.browserWindow.document.getElementById(OVERFLOW_POPUP_ID);
+ },
+
+ get overflowSubmenu() {
+ return this.browserWindow.document.getElementById(OVERFLOW_MENU_ID);
+ },
+
+ get tabBrowser() {
+ return this.browserWindow.gBrowser;
+ },
+
+ // Methods on the wrapped test can be called on this object.
+ __noSuchMethod__: function (methodName, args) {
+ this.test[methodName].apply(this.test, args);
+ },
+
+ // Asserts that absentItems -- an array of items that should not match the
+ // current context -- aren't present in the menu.
+ checkAbsentItems: function (presentItems, absentItems) {
+ for (let i = 0; i < absentItems.length; i++) {
+ let item = absentItems[i];
+ let elt = this.getItemElt(this.contextMenuPopup, item);
+
+ // The implementation actually hides items rather than removing or not
+ // adding them in the first place, but that's an implementation detail.
+ this.test.assert(!elt || elt.hidden,
+ "Item should not be present in top-level menu");
+
+ if (this.shouldOverflow(presentItems)) {
+ elt = getItemElt(this.overflowPopup, item);
+ this.test.assert(!elt || elt.hidden,
+ "Item should not be present in overflow submenu");
+ }
+ }
+ },
+
+ // Asserts that elt, a DOM element representing item, looks OK.
+ checkItemElt: function (elt, item) {
+ let itemType = this.getItemType(item);
+
+ switch (itemType) {
+ case "Item":
+ this.test.assertEqual(elt.localName, "menuitem",
+ "Item DOM element should be a xul:menuitem");
+ if (typeof(item.data) === "string") {
+ this.test.assertEqual(elt.getAttribute("value"), item.data,
+ "Item should have correct data");
+ }
+ break
+ case "Menu":
+ this.test.assertEqual(elt.localName, "menu",
+ "Menu DOM element should be a xul:menu");
+ let subPopup = elt.firstChild;
+ this.test.assert(subPopup, "xul:menu should have a child");
+ this.test.assertEqual(subPopup.localName, "menupopup",
+ "xul:menu's first child should be a menupopup");
+ break;
+ case "Separator":
+ this.test.assertEqual(elt.localName, "menuseparator",
+ "Separator DOM element should be a xul:menuseparator");
+ break;
+ }
+
+ if (itemType === "Item" || itemType === "Menu") {
+ this.test.assertEqual(elt.getAttribute("label"), item.label,
+ "Item should have correct title");
+ if (typeof(item.image) === "string")
+ this.test.assertEqual(elt.getAttribute("image"), item.image,
+ "Item should have correct image");
+ else
+ this.test.assert(!elt.hasAttribute("image"),
+ "Item should not have image");
+ }
+ },
+
+ // Asserts that the context menu looks OK given the arguments. presentItems
+ // are items that should match the current context. absentItems are items
+ // that shouldn't. removedItems are items that have been removed from the
+ // menu.
+ checkMenu: function (presentItems, absentItems, removedItems) {
+ this.checkSeparator(presentItems);
+ this.checkOverflow(presentItems);
+ this.checkPresentItems(presentItems);
+ this.checkAbsentItems(presentItems, absentItems);
+ this.checkRemovedItems(removedItems);
+ this.checkSort(presentItems);
+ },
+
+ // Asserts that the overflow submenu is present or absent as appropriate for
+ // presentItems.
+ checkOverflow: function (presentItems) {
+ let submenu = this.overflowSubmenu;
+ if (this.shouldOverflow(presentItems)) {
+ this.test.assert(submenu && !submenu.hidden,
+ "Overflow submenu should be present");
+ this.test.assert(submenu.localName, "menu",
+ "Overflow submenu should be a <menu>");
+ let overflowPopup = this.overflowPopup;
+ this.test.assert(overflowPopup,
+ "Overflow submenu popup should be present");
+ this.test.assert(overflowPopup.localName, "menupopup",
+ "Overflow submenu popup should be a <menupopup>");
+ }
+ else {
+ this.test.assert(!submenu || submenu.hidden,
+ "Overflow submenu should be absent");
+ }
+ },
+
+ // Asserts that the items that are present in the menu because they match the
+ // current context look OK.
+ checkPresentItems: function (presentItems) {
+ function recurse(popup, items, isTopLevel) {
+ items.forEach(function (item) {
+ let elt = this.getItemElt(popup, item);
+
+ if (isTopLevel) {
+ if (this.shouldOverflow(items)) {
+ this.test.assert(!elt || elt.hidden,
+ "Item should not be present in top-level menu");
+
+ let overflowPopup = this.overflowPopup;
+ this.test.assert(overflowPopup,
+ "Overflow submenu should be present");
+
+ elt = this.getItemElt(overflowPopup, item);
+ this.test.assert(elt && !elt.hidden,
+ "Item should be present in overflow submenu");
+ }
+ else {
+ this.test.assert(elt && !elt.hidden,
+ "Item should be present in top-level menu");
+ }
+ }
+ else {
+ this.test.assert(elt && !elt.hidden,
+ "Item should be present in menu");
+ }
+
+ this.checkItemElt(elt, item);
+ if (this.getItemType(item) === "Menu")
+ recurse.call(this, elt.firstChild, item.items, false);
+ }, this);
+ }
+
+ recurse.call(this, this.contextMenuPopup, presentItems, true);
+ },
+
+ // Asserts that items that have been removed from the menu are really removed.
+ checkRemovedItems: function (removedItems) {
+ for (let i = 0; i < removedItems.length; i++) {
+ let item = removedItems[i];
+
+ let elt = this.getItemElt(this.contextMenuPopup, item);
+ this.test.assert(!elt, "Item should be removed from top-level menu");
+
+ let overflowPopup = this.overflowPopup;
+ if (overflowPopup) {
+ elt = this.getItemElt(overflowPopup, item);
+ this.test.assert(!elt, "Item should be removed from overflow submenu");
+ }
+ }
+ },
+
+ // Asserts that the menu separator separating standard items from our items
+ // looks OK.
+ checkSeparator: function (presentItems) {
+ let sep = this.contextMenuSeparator;
+ if (presentItems.length) {
+ this.test.assert(sep && !sep.hidden, "Menu separator should be present");
+ this.test.assertEqual(sep.localName, "menuseparator",
+ "Menu separator should be a <menuseparator>");
+ }
+ else {
+ this.test.assert(!sep || sep.hidden, "Menu separator should be absent");
+ }
+ },
+
+ // Asserts that our items are sorted.
+ checkSort: function (presentItems) {
+ // Get the first item in sorted order, get its elt, walk the nextSibling
+ // chain, making sure each is greater than the previous.
+ if (presentItems.length) {
+ let sorted = presentItems.slice(0).
+ sort(function (a, b) a.label.localeCompare(b.label));
+ let elt = this.shouldOverflow(presentItems) ?
+ this.getItemElt(this.overflowPopup, sorted[0]) :
+ this.getItemElt(this.contextMenuPopup, sorted[0]);
+ let numElts = 1;
+ while (elt.nextSibling &&
+ elt.nextSibling.className.split(/\s+/).indexOf(ITEM_CLASS) >= 0) {
+ let eltLabel = elt.getAttribute("label");
+ let nextLabel = elt.nextSibling.getAttribute("label");
+ this.test.assert(eltLabel.localeCompare(nextLabel) < 0,
+ "Item label should be < next item's label");
+ elt = elt.nextSibling;
+ numElts++;
+ }
+ this.test.assertEqual(numElts, presentItems.length,
+ "The first item in sorted order should have the " +
+ "first element in sorted order");
+ }
+ },
+
+ // Attaches an event listener to node. The listener is automatically removed
+ // when it's fired (so it's assumed it will fire), and callback is called
+ // after a short delay. Since the module we're testing relies on the same
+ // event listeners to do its work, this is to give them a little breathing
+ // room before callback runs. Inside callback |this| is this object.
+ delayedEventListener: function (node, event, callback, useCapture) {
+ const self = this;
+ node.addEventListener(event, function handler(evt) {
+ node.removeEventListener(event, handler, useCapture);
+ require("timer").setTimeout(function () {
+ try {
+ callback.call(self, evt);
+ }
+ catch (err) {
+ self.test.exception(err);
+ self.test.done();
+ }
+ }, 20);
+ }, useCapture);
+ },
+
+ // Call to finish the test.
+ done: function () {
+ function commonDone() {
+ if (this.tab) {
+ this.tabBrowser.removeTab(this.tab);
+ this.tabBrowser.selectedTab = this.oldSelectedTab;
+ }
+ while (this.loaders.length) {
+ let browserManager = this.loaders[0].globalScope.browserManager;
+ let topLevelItems = browserManager.topLevelItems.slice();
+ let privatePropsKey = this.loaders[0].globalScope.PRIVATE_PROPS_KEY;
+ let workerRegs = topLevelItems.map(function (item) {
+ return item.valueOf(privatePropsKey)._workerReg;
+ });
+
+ this.loaders[0].unload();
+
+ // Make sure the browser manager is cleaned up.
+ this.test.assertEqual(browserManager.browserWins.length, 0,
+ "browserManager should have no windows left");
+ this.test.assertEqual(browserManager.topLevelItems.length, 0,
+ "browserManager should have no items left");
+ this.test.assert(!("contentWins" in browserManager),
+ "browserManager should have no content windows left");
+
+ // Make sure the items' worker registries are cleaned up.
+ topLevelItems.forEach(function (item) {
+ this.test.assert(!("_workerReg" in item.valueOf(privatePropsKey)),
+ "item's worker registry should be removed");
+ }, this);
+ workerRegs.forEach(function (workerReg) {
+ this.test.assertEqual(Object.keys(workerReg.winWorkers).length, 0,
+ "worker registry should be empty");
+ this.test.assertEqual(
+ Object.keys(workerReg.winsWithoutWorkers).length, 0,
+ "worker registry list of windows without workers should be empty");
+ }, this);
+ }
+ this.test.done();
+ }
+
+ function closeBrowserWindow() {
+ if (this.oldBrowserWindow) {
+ this.delayedEventListener(this.browserWindow, "unload", commonDone,
+ false);
+ this.browserWindow.close();
+ this.browserWindow = this.oldBrowserWindow;
+ delete this.oldBrowserWindow;
+ }
+ else {
+ commonDone.call(this);
+ }
+ };
+
+ if (this.contextMenuPopup.state == "closed") {
+ closeBrowserWindow.call(this);
+ }
+ else {
+ this.delayedEventListener(this.contextMenuPopup, "popuphidden",
+ function () closeBrowserWindow.call(this),
+ false);
+ this.contextMenuPopup.hidePopup();
+ }
+ },
+
+ // Returns the DOM element in popup corresponding to item.
+ // WARNING: The element is found by comparing labels, so don't give two items
+ // the same label.
+ getItemElt: function (popup, item) {
+ let nodes = popup.childNodes;
+ for (let i = nodes.length - 1; i >= 0; i--) {
+ if (this.getItemType(item) === "Separator") {
+ if (nodes[i].localName === "menuseparator")
+ return nodes[i];
+ }
+ else if (nodes[i].getAttribute("label") === item.label)
+ return nodes[i];
+ }
+ return null;
+ },
+
+ // Returns "Item", "Menu", or "Separator".
+ getItemType: function (item) {
+ // Could use instanceof here, but that would require accessing the loader
+ // that created the item, and I don't want to A) somehow search through the
+ // this.loaders list to find it, and B) assume there are any live loaders at
+ // all.
+ return /^\[object (Item|Menu|Separator)/.exec(item.toString())[1];
+ },
+
+ // Returns a wrapper around a new loader: { loader, cm, unload, globalScope }.
+ // loader is a Cuddlefish sandboxed loader, cm is the context menu module,
+ // globalScope is the context menu module's global scope, and unload is a
+ // function that unloads the loader and associated resources.
+ newLoader: function () {
+ const self = this;
+ let loader = Loader(module);
+ let wrapper = {
+ loader: loader,
+ cm: loader.require("context-menu"),
+ globalScope: loader.sandbox("context-menu"),
+ unload: function () {
+ loader.unload();
+ let idx = self.loaders.indexOf(wrapper);
+ if (idx < 0)
+ throw new Error("Test error: tried to unload nonexistent loader");
+ self.loaders.splice(idx, 1);
+ }
+ };
+ this.loaders.push(wrapper);
+ return wrapper;
+ },
+
+ // Returns true if the number of presentItems crosses the overflow threshold.
+ shouldOverflow: function (presentItems) {
+ return presentItems.length >
+ (this.loaders.length ?
+ this.loaders[0].loader.require("preferences-service").
+ get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) :
+ OVERFLOW_THRESH_DEFAULT);
+ },
+
+ // Opens the context menu on the current page. If targetNode is null, the
+ // menu is opened in the top-left corner. onShowncallback is passed the
+ // popup.
+ showMenu: function(targetNode, onshownCallback) {
+ function sendEvent() {
+ this.delayedEventListener(this.browserWindow, "popupshowing",
+ function (e) {
+ let popup = e.target;
+ onshownCallback.call(this, popup);
+ }, false);
+
+ let rect = targetNode ?
+ targetNode.getBoundingClientRect() :
+ { left: 0, top: 0, width: 0, height: 0 };
+ let contentWin = this.browserWindow.content;
+ contentWin.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ sendMouseEvent("contextmenu",
+ rect.left + (rect.width / 2),
+ rect.top + (rect.height / 2),
+ 2, 1, 0);
+ }
+
+ // If a new tab or window has not yet been opened, open a new tab now. For
+ // some reason using the tab already opened when the test starts causes
+ // leaks. See bug 566351 for details.
+ if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) {
+ this.oldSelectedTab = this.tabBrowser.selectedTab;
+ this.tab = this.tabBrowser.addTab("about:blank");
+ let browser = this.tabBrowser.getBrowserForTab(this.tab);
+
+ this.delayedEventListener(browser, "load", function () {
+ this.tabBrowser.selectedTab = this.tab;
+ sendEvent.call(this);
+ }, true);
+ }
+ else
+ sendEvent.call(this);
+ },
+
+ // Opens a new browser window. The window will be closed automatically when
+ // done() is called.
+ withNewWindow: function (onloadCallback) {
+ let win = this.browserWindow.OpenBrowserWindow();
+ this.delayedEventListener(win, "load", onloadCallback, true);
+ this.oldBrowserWindow = this.browserWindow;
+ this.browserWindow = win;
+ },
+
+ // Opens a new tab with our test page in the current window. The tab will
+ // be closed automatically when done() is called.
+ withTestDoc: function (onloadCallback) {
+ this.oldSelectedTab = this.tabBrowser.selectedTab;
+ this.tab = this.tabBrowser.addTab(TEST_DOC_URL);
+ let browser = this.tabBrowser.getBrowserForTab(this.tab);
+
+ this.delayedEventListener(browser, "load", function () {
+ this.tabBrowser.selectedTab = this.tab;
+ onloadCallback.call(this, browser.contentWindow, browser.contentDocument);
+ }, true);
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-hotkeys.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-hotkeys.js
new file mode 100644
index 0000000..3b69a28
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-hotkeys.js
@@ -0,0 +1,156 @@
+"use strict";
+
+const { Hotkey } = require("hotkeys");
+const { keyDown } = require("dom/events/keys");
+const { Loader } = require('./helpers');
+
+exports["test hotkey: function key"] = function(assert, done) {
+ var element = require("window-utils").activeBrowserWindow.document.documentElement;
+ var showHotKey = Hotkey({
+ combo: "f1",
+ onPress: function() {
+ assert.pass("first callback is called");
+ 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 element = require("window-utils").activeBrowserWindow.document.documentElement;
+ 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 element = require("window-utils").activeBrowserWindow.document.documentElement;
+ 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 element = require("window-utils").activeBrowserWindow.document.documentElement;
+ 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 element = require("window-utils").activeBrowserWindow.document.documentElement;
+ var someHotkey = Hotkey({
+ combo: "control-alt-1",
+ onPress: function() {
+ }
+ });
+ keyDown(element, "a");
+ assert.pass("No exception throw, unmodified keypress passed");
+};
+
+exports["test hotkey: automatic destroy"] = function(assert, done) {
+ // Hacky way to be able to create unloadable modules via makeSandboxedLoader.
+ let loader = Loader(module);
+
+ var called = false;
+ var element = loader.require("window-utils").activeBrowserWindow.document.documentElement;
+ var hotkey = loader.require("hotkeys").Hotkey({
+ combo: "accel-shift-x",
+ onPress: function() {
+ 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");
+
+ require("timer").setTimeout(function () {
+ assert.ok(!called, "Hotkey is destroyed and not called.");
+ done();
+ }, 0);
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-module.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-module.js
new file mode 100644
index 0000000..c62f923
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-module.js
@@ -0,0 +1,33 @@
+"use strict";
+
+/** Disabled because of Bug 672199
+exports["test module exports are frozen"] = function(assert) {
+ assert.ok(Object.isFrozen(require("addon-kit/hotkeys")),
+ "module exports are frozen");
+};
+
+exports["test redefine exported property"] = function(assert) {
+ let hotkeys = require("addon-kit/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("addon-kit/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("addon-kit/hotkeys");
+ let { Hotkey } = hotkeys;
+
+ try { hotkeys.Hotkey = Object } catch(e) {}
+ assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be overriden");
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-notifications.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-notifications.js
new file mode 100644
index 0000000..1eb6190
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-notifications.js
@@ -0,0 +1,79 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const { Loader } = require('./helpers');
+
+exports.testOnClick = function (test) {
+ let [loader, mockAlertServ] = makeLoader(module);
+ let notifs = loader.require("notifications");
+ let data = "test data";
+ let opts = {
+ onClick: function (clickedData) {
+ test.assertEqual(this, notifs, "|this| should be notifications module");
+ test.assertEqual(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();
+};
+
+// Returns [loader, mockAlertService].
+function makeLoader(test) {
+ 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("notifications");
+ let scope = loader.sandbox("notifications");
+ scope.notify = mockAlertServ.showAlertNotification.bind(mockAlertServ);
+ return [loader, mockAlertServ];
+};
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-page-mod.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-page-mod.js
new file mode 100644
index 0000000..2b38946
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-page-mod.js
@@ -0,0 +1,416 @@
+"use strict";
+
+var pageMod = require("page-mod");
+var testPageMod = require("pagemod-test-helpers").testPageMod;
+const { Loader } = require('./helpers');
+const tabs = require("tabs");
+
+/* XXX This can be used to delay closing the test Firefox instance for interactive
+ * testing or visual inspection. This test is registered first so that it runs
+ * the last. */
+exports.delay = function(test) {
+ if (false) {
+ test.waitUntilDone(60000);
+ require("timer").setTimeout(function() {test.done();}, 4000);
+ } else
+ test.pass();
+}
+
+/* Tests for the PageMod APIs */
+
+exports.testPageMod1 = function(test) {
+ let mods = testPageMod(test, "about:", [{
+ include: /about:/,
+ contentScriptWhen: 'end',
+ contentScript: 'new ' + function WorkerScope() {
+ window.document.body.setAttribute("JEP-107", "worked");
+ },
+ onAttach: function() {
+ test.assertEqual(this, mods[0], "The 'this' object is the page mod.");
+ }
+ }],
+ function(win, done) {
+ test.assertEqual(
+ win.document.body.getAttribute("JEP-107"),
+ "worked",
+ "PageMod.onReady test"
+ );
+ done();
+ }
+ );
+};
+
+exports.testPageMod2 = function(test) {
+ testPageMod(test, "about:", [{
+ include: "about:*",
+ 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");
+ }
+ ]
+ }], function(win, done) {
+ test.assertEqual(win.document.documentElement.getAttribute("first"),
+ "true",
+ "PageMod test #2: first script has run");
+ test.assertEqual(win.document.documentElement.getAttribute("second"),
+ "true",
+ "PageMod test #2: second script has run");
+ test.assertEqual("AUQLUE" in win, false,
+ "PageMod test #2: scripts get a wrapped window");
+ done();
+ });
+};
+
+exports.testPageModIncludes = function(test) {
+ var asserts = [];
+ function createPageModTest(include, expectedMatch) {
+ // Create an 'onload' test function...
+ asserts.push(function(test, win) {
+ var matches = include in win.localStorage;
+ test.assert(expectedMatch ? matches : !matches,
+ "'" + include + "' match test, expected: " + expectedMatch);
+ });
+ // ...and corresponding PageMod options
+ return {
+ include: include,
+ contentScript: 'new ' + function() {
+ self.on("message", function(msg) {
+ window.localStorage[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]);
+ }
+ };
+ }
+
+ testPageMod(test, "about:buildconfig", [
+ createPageModTest("*", false),
+ createPageModTest("*.google.com", false),
+ createPageModTest("about:*", true),
+ createPageModTest("about:", false),
+ createPageModTest("about:buildconfig", true)
+ ],
+ function (win, done) {
+ test.waitUntil(function () win.localStorage["about:buildconfig"],
+ "about:buildconfig page-mod to be executed")
+ .then(function () {
+ asserts.forEach(function(fn) {
+ fn(test, win);
+ });
+ done();
+ });
+ }
+ );
+};
+
+exports.testPageModErrorHandling = function(test) {
+ test.assertRaises(function() {
+ new pageMod.PageMod();
+ },
+ 'pattern is undefined',
+ "PageMod() throws when 'include' option is not specified.");
+};
+
+/* Tests for internal functions. */
+exports.testCommunication1 = function(test) {
+ let workerDone = false,
+ callbackDone = null;
+
+ testPageMod(test, "about:", [{
+ 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'));
+ })
+ },
+ onAttach: function(worker) {
+ worker.on('error', function(e) {
+ test.fail('Errors where reported');
+ });
+ worker.on('message', function(value) {
+ test.assertEqual(
+ "worked",
+ value,
+ "test comunication"
+ );
+ workerDone = true;
+ if (callbackDone)
+ callbackDone();
+ });
+ worker.postMessage('do it!')
+ }
+ }],
+ function(win, done) {
+ (callbackDone = function() {
+ if (workerDone) {
+ test.assertEqual(
+ 'worked',
+ win.document.body.getAttribute('JEP-107'),
+ 'attribute should be modified'
+ );
+ done();
+ }
+ })();
+ }
+ );
+};
+
+exports.testCommunication2 = function(test) {
+ let callbackDone = null,
+ window;
+
+ testPageMod(test, "about:", [{
+ include: "about:*",
+ contentScriptWhen: 'start',
+ contentScript: 'new ' + function WorkerScope() {
+ document.documentElement.setAttribute('AUQLUE', 42);
+ window.addEventListener('load', function listener() {
+ self.postMessage('onload');
+ }, false);
+ self.on("message", function() {
+ self.postMessage(document.documentElement.getAttribute("test"))
+ });
+ },
+ onAttach: function(worker) {
+ worker.on('error', function(e) {
+ test.fail('Errors where reported');
+ });
+ worker.on('message', function(msg) {
+ if ('onload' == msg) {
+ test.assertEqual(
+ '42',
+ window.document.documentElement.getAttribute('AUQLUE'),
+ 'PageMod scripts executed in order'
+ );
+ window.document.documentElement.setAttribute('test', 'changes in window');
+ worker.postMessage('get window.test')
+ } else {
+ test.assertEqual(
+ 'changes in window',
+ msg,
+ 'PageMod test #2: second script has run'
+ )
+ callbackDone();
+ }
+ });
+ }
+ }],
+ function(win, done) {
+ window = win;
+ callbackDone = done;
+ }
+ );
+};
+
+exports.testEventEmitter = function(test) {
+ let workerDone = false,
+ callbackDone = null;
+
+ testPageMod(test, "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) {
+ test.fail('Errors were reported : '+e);
+ });
+ worker.port.on('content-to-addon', function(value) {
+ test.assertEqual(
+ "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(test) {
+ let doneCallback = null;
+ let messages = 0;
+ let modObject = {
+ include: "data:text/html,",
+ 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) {
+ test.fail("Page mod contexts are mixed.");
+ doneCallback();
+ }
+ else if (++messages == 2) {
+ test.pass("Page mod contexts are different.");
+ doneCallback();
+ }
+ });
+ }
+ };
+ testPageMod(test, "data:text/html,", [modObject, modObject],
+ function(win, done) {
+ doneCallback = done;
+ }
+ );
+};
+
+exports.testHistory = function(test) {
+ // 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 = require("self").data.url("test-page-mod.html");
+ let callbackDone = null;
+ testPageMod(test, 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) {
+ test.assertEqual(JSON.stringify(data), JSON.stringify({foo: "bar"}),
+ "History API works!");
+ callbackDone();
+ });
+ }
+ }],
+ function(win, done) {
+ callbackDone = done;
+ }
+ );
+};
+
+exports.testRelatedTab = function(test) {
+ test.waitUntilDone();
+
+ let tab;
+ let { PageMod } = require("page-mod");
+ let pageMod = new PageMod({
+ include: "about:*",
+ onAttach: function(worker) {
+ test.assertEqual(tab, worker.tab, "Worker.tab is valid");
+ pageMod.destroy();
+ tab.close();
+ test.done();
+ }
+ });
+
+ tabs.open({
+ url: "about:",
+ onOpen: function onOpen(t) {
+ tab = t;
+ }
+ });
+
+};
+
+exports['test tab worker on message'] = function(test) {
+ test.waitUntilDone();
+
+ let { browserWindows } = require("windows");
+ let tabs = require("tabs");
+ let { PageMod } = require("page-mod");
+
+ let url1 = "data:text/html,<title>tab1</title><h1>worker1.tab</h1>";
+ let url2 = "data:text/html,<title>tab2</title><h1>worker2.tab</h1>";
+ let worker1 = null;
+
+ let mod = PageMod({
+ include: "data:text/html,*",
+ contentScriptWhen: "ready",
+ contentScript: "self.postMessage('#1');",
+ onAttach: function onAttach(worker) {
+ worker.on("message", function onMessage() {
+ this.tab.attach({
+ contentScriptWhen: "ready",
+ contentScript: "self.postMessage({ url: window.location.href, title: document.title });",
+ onMessage: function onMessage(data) {
+ test.assertEqual(this.tab.url, data.url, "location is correct");
+ test.assertEqual(this.tab.title, data.title, "title is correct");
+ if (this.tab.url === url1) {
+ worker1 = this;
+ tabs.open({ url: url2, inBackground: true });
+ }
+ else if (this.tab.url === url2) {
+ mod.destroy();
+ worker1.tab.close();
+ worker1.destroy();
+ worker.tab.close();
+ worker.destroy();
+ test.done();
+ }
+ }
+ });
+ });
+ }
+ });
+
+ tabs.open(url1);
+};
+
+exports.testAutomaticDestroy = function(test) {
+ test.waitUntilDone();
+ let loader = Loader(module);
+
+ let pageMod = loader.require("page-mod").PageMod({
+ include: "about:*",
+ contentScriptWhen: "start",
+ onAttach: function(w) {
+ test.fail("Page-mod should have been detroyed during module unload");
+ }
+ });
+
+ // Unload the page-mod module so that our page mod is destroyed
+ loader.unload();
+
+ // Then create a second tab to ensure that it is correctly destroyed
+ let tabs = require("tabs");
+ tabs.open({
+ url: "about:",
+ onReady: function onReady(tab) {
+ test.pass("check automatic destroy");
+ tab.close();
+ test.done();
+ }
+ });
+
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-page-worker.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-page-worker.js
new file mode 100644
index 0000000..89cdf45
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-page-worker.js
@@ -0,0 +1,362 @@
+let tests = {}, Pages, Page;
+const { Loader } = require('./helpers');
+
+const ERR_DESTROYED =
+ "The page has been destroyed and can no longer be used.";
+
+tests.testSimplePageCreation = function(test) {
+ test.waitUntilDone();
+
+ let page = new Page({
+ contentScript: "self.postMessage(window.location.href)",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(message, "about:blank",
+ "Page Worker should start with a blank page by default");
+ test.assertEqual(this, page, "The 'this' object is the page itself.");
+ test.done();
+ }
+ });
+}
+
+/*
+ * Tests that we can't be tricked by document overloads as we have access
+ * to wrapped nodes
+ */
+tests.testWrappedDOM = function(test) {
+ test.waitUntilDone();
+
+ let page = Page({
+ allow: { script: true },
+ contentURL: "data:text/html,<script>document.getElementById=3;window.scrollTo=3;</script>",
+ contentScript: "window.addEventListener('load', function () " +
+ "self.postMessage([typeof(document.getElementById), " +
+ "typeof(window.scrollTo)]), true)",
+ onMessage: function (message) {
+ test.assertEqual(message[0],
+ "function",
+ "getElementById from content script is the native one");
+
+ test.assertEqual(message[1],
+ "function",
+ "scrollTo from content script is the native one");
+
+ test.done();
+ }
+ });
+}
+
+/*
+// We do not offer unwrapped access to DOM since bug 601295 landed
+// See 660780 to track progress of unwrap feature
+tests.testUnwrappedDOM = function(test) {
+ test.waitUntilDone();
+
+ let page = Page({
+ allow: { script: true },
+ contentURL: "data:text/html,<script>document.getElementById=3;window.scrollTo=3;</script>",
+ contentScript: "window.addEventListener('load', function () " +
+ "self.postMessage([typeof(unsafeWindow.document.getElementById), " +
+ "typeof(unsafeWindow.scrollTo)]), true)",
+ onMessage: function (message) {
+ test.assertEqual(message[0],
+ "number",
+ "document inside page is free to be changed");
+
+ test.assertEqual(message[1],
+ "number",
+ "window inside page is free to be changed");
+
+ test.done();
+ }
+ });
+}
+*/
+
+tests.testPageProperties = function(test) {
+ let page = new Page();
+
+ for each (let prop in ['contentURL', 'allow', 'contentScriptFile',
+ 'contentScript', 'contentScriptWhen', 'on',
+ 'postMessage', 'removeListener']) {
+ test.assert(prop in page, prop + " property is defined on page.");
+ }
+
+ test.assert(function () page.postMessage("foo") || true,
+ "postMessage doesn't throw exception on page.");
+}
+
+tests.testConstructorAndDestructor = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let Pages = loader.require("page-worker");
+ let global = loader.sandbox("page-worker");
+
+ let pagesReady = 0;
+
+ let page1 = Pages.Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: pageReady
+ });
+ let page2 = Pages.Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: pageReady
+ });
+
+ test.assertNotEqual(page1, page2,
+ "Page 1 and page 2 should be different objects.");
+
+ function pageReady() {
+ if (++pagesReady == 2) {
+ page1.destroy();
+ page2.destroy();
+
+ test.assert(isDestroyed(page1), "page1 correctly unloaded.");
+ test.assert(isDestroyed(page2), "page2 correctly unloaded.");
+
+ loader.unload();
+ test.done();
+ }
+ }
+}
+
+tests.testAutoDestructor = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let Pages = loader.require("page-worker");
+
+ let page = Pages.Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: function() {
+ loader.unload();
+ test.assert(isDestroyed(page), "Page correctly unloaded.");
+ test.done();
+ }
+ });
+}
+
+tests.testValidateOptions = function(test) {
+ test.assertRaises(
+ function () Page({ contentURL: 'home' }),
+ "The `contentURL` option must be a valid URL.",
+ "Validation correctly denied a non-URL contentURL"
+ );
+
+ test.assertRaises(
+ function () Page({ onMessage: "This is not a function."}),
+ "The event listener must be a function.",
+ "Validation correctly denied a non-function onMessage."
+ );
+
+ test.pass("Options validation is working.");
+}
+
+tests.testContentAndAllowGettersAndSetters = function(test) {
+ test.waitUntilDone();
+ let content = "data:text/html,<script>window.localStorage.allowScript=3;</script>";
+ let page = Page({
+ contentURL: content,
+ contentScript: "self.postMessage(window.localStorage.allowScript)",
+ contentScriptWhen: "end",
+ onMessage: step0
+ });
+
+ function step0(message) {
+ test.assertEqual(message, "3",
+ "Correct value expected for allowScript - 3");
+ test.assertEqual(page.contentURL, content,
+ "Correct content expected");
+ page.removeListener('message', step0);
+ page.on('message', step1);
+ page.allow = { script: false };
+ page.contentURL = content =
+ "data:text/html,<script>window.localStorage.allowScript='f'</script>";
+ }
+
+ function step1(message) {
+ test.assertEqual(message, "3",
+ "Correct value expected for allowScript - 3");
+ test.assertEqual(page.contentURL, content, "Correct content expected");
+ page.removeListener('message', step1);
+ page.on('message', step2);
+ page.allow = { script: true };
+ page.contentURL = content =
+ "data:text/html,<script>window.localStorage.allowScript='g'</script>";
+ }
+
+ function step2(message) {
+ test.assertEqual(message, "g",
+ "Correct value expected for allowScript - g");
+ test.assertEqual(page.contentURL, content, "Correct content expected");
+ page.removeListener('message', step2);
+ page.on('message', step3);
+ page.allow.script = false;
+ page.contentURL = content =
+ "data:text/html,<script>window.localStorage.allowScript=3</script>";
+ }
+
+ function step3(message) {
+ test.assertEqual(message, "g",
+ "Correct value expected for allowScript - g");
+ test.assertEqual(page.contentURL, content, "Correct content expected");
+ page.removeListener('message', step3);
+ page.on('message', step4);
+ page.allow.script = true;
+ page.contentURL = content =
+ "data:text/html,<script>window.localStorage.allowScript=4</script>";
+ }
+
+ function step4(message) {
+ test.assertEqual(message, "4",
+ "Correct value expected for allowScript - 4");
+ test.assertEqual(page.contentURL, content, "Correct content expected");
+ test.done();
+ }
+
+}
+
+tests.testOnMessageCallback = function(test) {
+ test.waitUntilDone();
+
+ Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: function() {
+ test.pass("onMessage callback called");
+ test.done();
+ }
+ });
+}
+
+tests.testMultipleOnMessageCallbacks = function(test) {
+ test.waitUntilDone();
+
+ let count = 0;
+ let page = Page({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: function() count += 1
+ });
+ page.on('message', function() count += 2);
+ page.on('message', function() count *= 3);
+ page.on('message', function()
+ test.assertEqual(count, 9, "All callbacks were called, in order."));
+ page.on('message', function() test.done());
+
+}
+
+tests.testLoadContentPage = function(test) {
+
+ test.waitUntilDone();
+
+ let page = Page({
+ onMessage: function(message) {
+ // The message is an array whose first item is the test method to call
+ // and the rest of whose items are arguments to pass it.
+ test[message.shift()].apply(test, message);
+ },
+ contentURL: require("self").data.url("test-page-worker.html"),
+ contentScriptFile: require("self").data.url("test-page-worker.js"),
+ contentScriptWhen: "ready"
+ });
+
+}
+
+tests.testAllowScriptDefault = function(test) {
+
+ test.waitUntilDone();
+
+ let page = Page({
+ onMessage: function(message) {
+ test.assert(message, "Script is allowed to run by default.");
+ test.done();
+ },
+ contentURL: "data:text/html,<script>document.documentElement.setAttribute('foo', 3);</script>",
+ contentScript: "self.postMessage(document.documentElement.getAttribute('foo'))",
+ contentScriptWhen: "ready"
+ });
+}
+
+tests.testAllowScript = function(test) {
+
+ test.waitUntilDone();
+
+ let page = Page({
+ onMessage: function(message) {
+ test.assert(message, "Script runs when allowed to do so.");
+ test.done();
+ },
+ allow: { script: true },
+ contentURL: "data:text/html,<script>document.documentElement.setAttribute('foo', 3);</script>",
+ contentScript: "self.postMessage(document.documentElement.hasAttribute('foo') && " +
+ " document.documentElement.getAttribute('foo') == 3)",
+ contentScriptWhen: "ready"
+ });
+}
+
+tests.testPingPong = function(test) {
+ test.waitUntilDone();
+ let page = Page({
+ contentURL: 'data:text/html,ping-pong',
+ contentScript: 'self.on("message", function(message) self.postMessage("pong"));'
+ + 'self.postMessage("ready");',
+ onMessage: function(message) {
+ if ('ready' == message) {
+ page.postMessage('ping');
+ }
+ else {
+ test.assert(message, 'pong', 'Callback from contentScript');
+ test.done();
+ }
+ }
+ });
+};
+
+tests.testMultipleDestroys = function(test) {
+ let page = Page();
+ page.destroy();
+ page.destroy();
+ test.pass("Multiple destroys should not cause an error");
+};
+
+
+function isDestroyed(page) {
+ try {
+ page.postMessage("foo");
+ }
+ catch (err if err.message == ERR_DESTROYED) {
+ return true;
+ }
+ return false;
+}
+
+
+let pageWorkerSupported = true;
+
+try {
+ Pages = require("page-worker");
+ Page = Pages.Page;
+}
+catch (ex if ex.message == [
+ "The page-worker module currently supports only Firefox and Thunderbird. ",
+ "In the future, we would like it to support other applications, however. ",
+ "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
+ "information."
+ ].join("")) {
+ pageWorkerSupported = false;
+}
+
+if (pageWorkerSupported) {
+ for (let test in tests) {
+ exports[test] = tests[test];
+ }
+} else {
+ exports.testPageWorkerNotSupported = function(test) {
+ test.pass("The page-worker module is not supported on this app.");
+ }
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-panel.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-panel.js
new file mode 100644
index 0000000..3e240db
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-panel.js
@@ -0,0 +1,437 @@
+let { Cc, Ci } = require("chrome");
+let panels = require('panel');
+let tests = {}, panels, Panel;
+const { Loader } = require('./helpers');
+
+tests.testPanel = function(test) {
+ test.waitUntilDone();
+ let panel = Panel({
+ contentURL: "about:buildconfig",
+ contentScript: "self.postMessage(1); self.on('message', function() self.postMessage(2));",
+ onMessage: function (message) {
+ test.assertEqual(this, panel, "The 'this' object is the panel.");
+ switch(message) {
+ case 1:
+ test.pass("The panel was loaded.");
+ panel.postMessage('');
+ break;
+ case 2:
+ test.pass("The panel posted a message and received a response.");
+ panel.destroy();
+ test.done();
+ break;
+ }
+ }
+ });
+};
+
+tests.testPanelEmit = function(test) {
+ test.waitUntilDone();
+ let panel = Panel({
+ contentURL: "about:buildconfig",
+ contentScript: "self.port.emit('loaded');" +
+ "self.port.on('addon-to-content', " +
+ " function() self.port.emit('received'));",
+ });
+ panel.port.on("loaded", function () {
+ test.pass("The panel was loaded and sent a first event.");
+ panel.port.emit("addon-to-content");
+ });
+ panel.port.on("received", function () {
+ test.pass("The panel posted a message and received a response.");
+ panel.destroy();
+ test.done();
+ });
+};
+
+tests.testPanelEmitEarly = function(test) {
+ test.waitUntilDone();
+ let panel = Panel({
+ contentURL: "about:buildconfig",
+ contentScript: "self.port.on('addon-to-content', " +
+ " function() self.port.emit('received'));",
+ });
+ panel.port.on("received", function () {
+ test.pass("The panel posted a message early and received a response.");
+ panel.destroy();
+ test.done();
+ });
+ panel.port.emit("addon-to-content");
+};
+
+tests.testShowHidePanel = function(test) {
+ test.waitUntilDone();
+ let panel = Panel({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ panel.show();
+ },
+ onShow: function () {
+ test.pass("The panel was shown.");
+ test.assertEqual(this, panel, "The 'this' object is the panel.");
+ test.assertEqual(this.isShowing, true, "panel.isShowing == true.");
+ panel.hide();
+ },
+ onHide: function () {
+ test.pass("The panel was hidden.");
+ test.assertEqual(this, panel, "The 'this' object is the panel.");
+ test.assertEqual(this.isShowing, false, "panel.isShowing == false.");
+ panel.destroy();
+ test.done();
+ }
+ });
+};
+
+tests.testDocumentReload = function(test) {
+ test.waitUntilDone();
+ let content =
+ "<script>" +
+ "setTimeout(function () {" +
+ " window.location = 'about:blank';" +
+ "}, 250);" +
+ "</script>";
+ let messageCount = 0;
+ let panel = Panel({
+ contentURL: "data:text/html," + encodeURIComponent(content),
+ contentScript: "self.postMessage(window.location.href)",
+ onMessage: function (message) {
+ messageCount++;
+ if (messageCount == 1) {
+ test.assertMatches(message, /data:text\/html,/, "First document had a content script");
+ }
+ else if (messageCount == 2) {
+ test.assertEqual(message, "about:blank", "Second document too");
+ panel.destroy();
+ test.done();
+ }
+ }
+ });
+};
+
+tests.testParentResizeHack = function(test) {
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+ let docShell = browserWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ if (!("allowWindowControl" in docShell)) {
+ // bug 635673 is not fixed in this firefox build
+ test.pass("allowWindowControl attribute that allow to fix browser window " +
+ "resize is not available on this build.");
+ return;
+ }
+
+ test.waitUntilDone(30000);
+
+ let previousWidth = browserWindow.outerWidth, previousHeight = browserWindow.outerHeight;
+
+ let content = "<script>" +
+ "function contentResize() {" +
+ " resizeTo(200,200);" +
+ " resizeBy(200,200);" +
+ "}" +
+ "</script>" +
+ "Try to resize browser window";
+ let panel = Panel({
+ contentURL: "data:text/html," + encodeURIComponent(content),
+ contentScript: "self.on('message', function(message){" +
+ " if (message=='resize') " +
+ " unsafeWindow.contentResize();" +
+ "});",
+ contentScriptWhen: "ready",
+ onMessage: function (message) {
+
+ },
+ onShow: function () {
+ panel.postMessage('resize');
+ require("timer").setTimeout(function () {
+ test.assertEqual(previousWidth,browserWindow.outerWidth,"Size doesn't change by calling resizeTo/By/...");
+ test.assertEqual(previousHeight,browserWindow.outerHeight,"Size doesn't change by calling resizeTo/By/...");
+ panel.destroy();
+ test.done();
+ },0);
+ }
+ });
+ panel.show();
+}
+
+tests.testResizePanel = function(test) {
+ test.waitUntilDone();
+
+ // These tests fail on Linux if the browser window in which the panel
+ // is displayed is not active. And depending on what other tests have run
+ // before this one, it might not be (the untitled window in which the test
+ // runner executes is often active). So we make sure the browser window
+ // is focused by focusing it before running the tests. Then, to be the best
+ // possible test citizen, we refocus whatever window was focused before we
+ // started running these tests.
+
+ let activeWindow = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher).
+ activeWindow;
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+
+
+ function onFocus() {
+ browserWindow.removeEventListener("focus", onFocus, true);
+
+ let panel = Panel({
+ contentScript: "self.postMessage('')",
+ contentScriptWhen: "end",
+ height: 10,
+ width: 10,
+ onMessage: function (message) {
+ panel.show();
+ },
+ onShow: function () {
+ panel.resize(100,100);
+ panel.hide();
+ },
+ onHide: function () {
+ test.assert((panel.width == 100) && (panel.height == 100),
+ "The panel was resized.");
+ if (activeWindow)
+ activeWindow.focus();
+ test.done();
+ }
+ });
+ }
+
+ if (browserWindow === activeWindow) {
+ onFocus();
+ }
+ else {
+ browserWindow.addEventListener("focus", onFocus, true);
+ browserWindow.focus();
+ }
+};
+
+tests.testHideBeforeShow = function(test) {
+ test.waitUntilDone();
+ let showCalled = false;
+ let panel = Panel({
+ onShow: function () {
+ showCalled = true;
+ },
+ onHide: function () {
+ test.assert(!showCalled, 'must not emit show if was hidden before');
+ test.done();
+ }
+ });
+ panel.show();
+ panel.hide();
+};
+
+tests.testSeveralShowHides = function(test) {
+ test.waitUntilDone();
+ let hideCalled = 0;
+ let panel = panels.Panel({
+ contentURL: "about:buildconfig",
+ onShow: function () {
+ panel.hide();
+ },
+ onHide: function () {
+ hideCalled++;
+ if (hideCalled < 3)
+ panel.show();
+ else {
+ test.pass("onHide called three times as expected");
+ test.done();
+ }
+ }
+ });
+ panel.on('error', function(e) {
+ test.fail('error was emitted:' + e.message + '\n' + e.stack);
+ });
+ panel.show();
+};
+
+tests.testAnchorAndArrow = function(test) {
+ test.waitUntilDone(20000);
+ let count = 0;
+ function newPanel(tab, anchor) {
+ let panel = panels.Panel({
+ contentURL: "data:text/html,<html><body style='padding: 0; margin: 0; " +
+ "background: gray; text-align: center;'>Anchor: " +
+ anchor.id + "</body></html>",
+ width: 200,
+ height: 100,
+ onShow: function () {
+ count++;
+ panel.destroy();
+ if (count==5) {
+ test.pass("All anchored panel test displayed");
+ tab.close(function () {
+ test.done();
+ });
+ }
+ }
+ });
+ panel.show(anchor);
+ }
+
+ let tabs= require("tabs");
+ let url = 'data:text/html,' +
+ '<html><head><title>foo</title></head><body>' +
+ '<style>div {background: gray; position: absolute; width: 300px; ' +
+ 'border: 2px solid black;}</style>' +
+ '<div id="tl" style="top: 0px; left: 0px;">Top Left</div>' +
+ '<div id="tr" style="top: 0px; right: 0px;">Top Right</div>' +
+ '<div id="bl" style="bottom: 0px; left: 0px;">Bottom Left</div>' +
+ '<div id="br" style="bottom: 0px; right: 0px;">Bottom right</div>' +
+ '</body></html>';
+
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+ let window = browserWindow.content;
+ newPanel(tab, window.document.getElementById('tl'));
+ newPanel(tab, window.document.getElementById('tr'));
+ newPanel(tab, window.document.getElementById('bl'));
+ newPanel(tab, window.document.getElementById('br'));
+ let anchor = browserWindow.document.getElementById("identity-box");
+ newPanel(tab, anchor);
+ }
+ });
+
+
+
+};
+
+tests.testPanelTextColor = function(test) {
+ test.waitUntilDone();
+ let html = "<html><head><style>body {color: yellow}</style></head>" +
+ "<body><p>Foo</p></body></html>";
+ let panel = Panel({
+ contentURL: "data:text/html," + encodeURI(html),
+ contentScript: "self.port.emit('color', " +
+ "window.getComputedStyle(document.body.firstChild, null). " +
+ " getPropertyValue('color'));"
+ });
+ panel.port.on("color", function (color) {
+ test.assertEqual(color, "rgb(255, 255, 0)",
+ "The panel text color style is preserved when a style exists.");
+ panel.destroy();
+ test.done();
+ });
+};
+
+function makeEventOrderTest(options) {
+ let expectedEvents = [];
+
+ return function(test) {
+ let panel = panels.Panel({ contentURL: "about:buildconfig" });
+
+ function expect(event, cb) {
+ expectedEvents.push(event);
+ panel.on(event, function() {
+ test.assertEqual(event, expectedEvents.shift());
+ if (cb)
+ require("timer").setTimeout(cb, 1);
+ });
+ return {then: expect};
+ }
+
+ test.waitUntilDone();
+ options.test(test, expect, panel);
+ }
+}
+
+tests.testAutomaticDestroy = function(test) {
+ let loader = Loader(module);
+ let panel = loader.require("panel").Panel({
+ contentURL: "about:buildconfig",
+ contentScript:
+ "self.port.on('event', function() self.port.emit('event-back'));"
+ });
+
+ loader.unload();
+
+ panel.port.on("event-back", function () {
+ test.fail("Panel should have been destroyed on module unload");
+ });
+ panel.port.emit("event");
+ test.pass("check automatic destroy");
+};
+
+tests.testWaitForInitThenShowThenDestroy = makeEventOrderTest({
+ test: function(test, expect, panel) {
+ expect('inited', function() { panel.show(); }).
+ then('show', function() { panel.destroy(); }).
+ then('hide', function() { test.done(); });
+ }
+});
+
+tests.testShowThenWaitForInitThenDestroy = makeEventOrderTest({
+ test: function(test, expect, panel) {
+ panel.show();
+ expect('inited').
+ then('show', function() { panel.destroy(); }).
+ then('hide', function() { test.done(); });
+ }
+});
+
+tests.testShowThenHideThenDestroy = makeEventOrderTest({
+ test: function(test, expect, panel) {
+ panel.show();
+ expect('show', function() { panel.hide(); }).
+ then('hide', function() { panel.destroy(); test.done(); });
+ }
+});
+
+tests.testContentURLOption = function(test) {
+ const URL_STRING = "about:buildconfig";
+ const HTML_CONTENT = "<html><title>Test</title><p>This is a test.</p></html>";
+
+ let (panel = Panel({ contentURL: URL_STRING })) {
+ test.pass("contentURL accepts a string URL.");
+ test.assertEqual(panel.contentURL, URL_STRING,
+ "contentURL is the string to which it was set.");
+ }
+
+ let dataURL = "data:text/html," + encodeURIComponent(HTML_CONTENT);
+ let (panel = Panel({ contentURL: dataURL })) {
+ test.pass("contentURL accepts a data: URL.");
+ }
+
+ let (panel = Panel({})) {
+ test.assert(panel.contentURL == null,
+ "contentURL is undefined.");
+ }
+
+ test.assertRaises(function () Panel({ contentURL: "foo" }),
+ "The `contentURL` option must be a valid URL.",
+ "Panel throws an exception if contentURL is not a URL.");
+};
+
+let panelSupported = true;
+
+try {
+ panels = require("panel");
+ Panel = panels.Panel;
+}
+catch(ex if ex.message == [
+ "The panel module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps ",
+ "for more information."
+ ].join("")) {
+ panelSupported = false;
+}
+
+if (panelSupported) {
+ for (let test in tests)
+ exports[test] = tests[test];
+}
+else {
+ exports.testPanelNotSupported = function(test) {
+ test.pass("The panel module is not supported on this app.");
+ }
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-passwords.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-passwords.js
new file mode 100644
index 0000000..e799b48
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-passwords.js
@@ -0,0 +1,277 @@
+"use strict";
+
+const { store, search, remove } = require("passwords");
+
+exports["test store requires `password` field"] = function(assert, done) {
+ store({
+ username: "foo",
+ realm: "bar",
+ onComplete: function onComplete() {
+ assert.fail("onComplete should not be called");
+ },
+ onError: function onError() {
+ assert.pass("'`password` is required");
+ done();
+ }
+ });
+};
+
+exports["test store requires `username` field"] = function(assert, done) {
+ store({
+ password: "foo",
+ realm: "bar",
+ onComplete: function onComplete() {
+ assert.fail("onComplete should not be called");
+ },
+ onError: function onError() {
+ assert.pass("'`username` is required");
+ done();
+ }
+ });
+};
+
+exports["test onComplete is optional"] = function(assert, done) {
+ store({
+ realm: "bla",
+ username: "bla",
+ password: "bla",
+ onError: function onError() {
+ assert.fail("onError was called");
+ }
+ });
+ assert.pass("exception is not thrown if `onComplete is missing")
+ done();
+};
+
+exports["test exceptions in onComplete are reported"] = function(assert, done) {
+ store({
+ realm: "throws",
+ username: "error",
+ password: "boom!",
+ onComplete: function onComplete(error) {
+ throw new Error("Boom!")
+ },
+ onError: function onError(error) {
+ assert.equal(error.message, "Boom!", "Error thrown is reported");
+ done();
+ }
+ });
+};
+
+exports["test store requires `realm` field"] = function(assert, done) {
+ store({
+ username: "foo",
+ password: "bar",
+ onComplete: function onComplete() {
+ assert.fail("onComplete should not be called");
+ },
+ onError: function onError() {
+ assert.pass("'`realm` is required");
+ done();
+ }
+ });
+};
+
+exports["test can't store same login twice"] = function(assert, done) {
+ store({
+ username: "user",
+ password: "pass",
+ realm: "realm",
+ onComplete: function onComplete() {
+ assert.pass("credential saved");
+
+ store({
+ username: "user",
+ password: "pass",
+ realm: "realm",
+ onComplete: function onComplete() {
+ assert.fail("onComplete should not be called");
+ },
+ onError: function onError() {
+ assert.pass("re-saving credential failed");
+
+ remove({
+ username: "user",
+ password: "pass",
+ realm: "realm",
+ onComplete: function onComplete() {
+ assert.pass("credential was removed");
+ done();
+ },
+ onError: function onError() {
+ assert.fail("remove should not fail");
+ }
+ });
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+};
+
+exports["test remove fails if no login found"] = function(assert, done) {
+ remove({
+ username: "foo",
+ password: "bar",
+ realm: "baz",
+ onComplete: function onComplete() {
+ assert.fail("should not be able to remove unstored credentials");
+ },
+ onError: function onError() {
+ assert.pass("can't remove unstored credentials");
+ done();
+ }
+ });
+};
+
+exports["test addon associated credentials"] = function(assert, done) {
+ store({
+ username: "foo",
+ password: "bar",
+ realm: "baz",
+ onComplete: function onComplete() {
+ search({
+ username: "foo",
+ password: "bar",
+ realm: "baz",
+ onComplete: function onComplete([credential]) {
+ assert.equal(credential.url.indexOf("addon:"), 0,
+ "`addon:` uri is used for add-on credentials");
+ assert.equal(credential.username, "foo",
+ "username matches");
+ assert.equal(credential.password, "bar",
+ "password matches");
+ assert.equal(credential.realm, "baz", "realm matches");
+ assert.equal(credential.formSubmitURL, null,
+ "`formSubmitURL` is `null` for add-on credentials");
+ assert.equal(credential.usernameField, "", "usernameField is empty");
+ assert.equal(credential.passwordField, "", "passwordField is empty");
+
+ remove({
+ username: credential.username,
+ password: credential.password,
+ realm: credential.realm,
+ onComplete: function onComplete() {
+ assert.pass("credential is removed");
+ done();
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+};
+
+exports["test web page associated credentials"] = function(assert, done) {
+ store({
+ url: "http://bar.foo.com/authentication/?login",
+ formSubmitURL: "http://login.foo.com/authenticate.cgi",
+ username: "user",
+ password: "pass",
+ usernameField: "user-f",
+ passwordField: "pass-f",
+ onComplete: function onComplete() {
+ search({
+ username: "user",
+ password: "pass",
+ url: "http://bar.foo.com",
+ formSubmitURL: "http://login.foo.com",
+ onComplete: function onComplete([credential]) {
+ assert.equal(credential.url, "http://bar.foo.com", "url matches");
+ assert.equal(credential.username, "user", "username matches");
+ assert.equal(credential.password, "pass", "password matches");
+ assert.equal(credential.realm, null, "realm is null");
+ assert.equal(credential.formSubmitURL, "http://login.foo.com",
+ "formSubmitURL matches");
+ assert.equal(credential.usernameField, "user-f",
+ "usernameField is matches");
+ assert.equal(credential.passwordField, "pass-f",
+ "passwordField matches");
+
+ remove({
+ url: credential.url,
+ formSubmitURL: credential.formSubmitURL,
+ username: credential.username,
+ password: credential.password,
+ usernameField: credential.usernameField,
+ passwordField: credential.passwordField,
+
+ onComplete: function onComplete() {
+ assert.pass("credential is removed");
+ done();
+ },
+ onError: function onError(e) {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+};
+
+exports["test site authentication credentials"] = function(assert, done) {
+ store({
+ url: "http://authentication.com",
+ username: "U",
+ password: "P",
+ realm: "R",
+ onComplete: function onComplete() {
+ search({
+ url: "http://authentication.com",
+ username: "U",
+ password: "P",
+ realm: "R",
+ onComplete: function onComplete([credential]) {
+ assert.equal(credential.url,"http://authentication.com",
+ "url matches");
+ assert.equal(credential.username, "U", "username matches");
+ assert.equal(credential.password, "P", "password matches");
+ assert.equal(credential.realm, "R", "realm matches");
+ assert.equal(credential.formSubmitURL, null, "formSubmitURL is null");
+ assert.equal(credential.usernameField, "", "usernameField is empty");
+ assert.equal(credential.passwordField, "", "passwordField is empty");
+
+ remove({
+ url: credential.url,
+ username: credential.username,
+ password: credential.password,
+ realm: credential.realm,
+ onComplete: function onComplete() {
+ assert.pass("credential is removed");
+ done();
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+ },
+ onError: function onError() {
+ assert.fail("onError should not be called");
+ }
+ });
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-private-browsing.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-private-browsing.js
new file mode 100644
index 0000000..a3c1519
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-private-browsing.js
@@ -0,0 +1,238 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Paul O’Shannessy <paul@oshannessy.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let pb = require("private-browsing");
+let {Cc,Ci} = require("chrome");
+const { Loader } = require('./helpers');
+
+let pbService;
+// Currently, only Firefox implements the private browsing service.
+if (require("xul-app").is("Firefox")) {
+ pbService = Cc["@mozilla.org/privatebrowsing;1"].
+ getService(Ci.nsIPrivateBrowsingService);
+}
+
+if (pbService) {
+
+ // tests that isActive has the same value as the private browsing service
+ // expects
+ exports.testGetIsActive = function (test) {
+ test.assertEqual(pb.isActive, false,
+ "private-browsing.isActive is correct without modifying PB service");
+
+ pbService.privateBrowsingEnabled = true;
+ test.assert(pb.isActive,
+ "private-browsing.isActive is correct after modifying PB service");
+
+ // Switch back to normal mode.
+ pbService.privateBrowsingEnabled = false;
+ };
+
+ // tests that activating does put the browser into private browsing mode
+ exports.testActivateDeactivate = function (test) {
+ test.waitUntilDone();
+ pb.once("start", function onStart() {
+ test.assertEqual(pbService.privateBrowsingEnabled, true,
+ "private browsing mode was activated");
+ pb.deactivate();
+ });
+ pb.once("stop", function onStop() {
+ test.assertEqual(pbService.privateBrowsingEnabled, false,
+ "private browsing mode was deactivate");
+ test.done();
+ });
+ pb.activate();
+ };
+
+ exports.testStart = function(test) {
+ test.waitUntilDone();
+ pb.on("start", function onStart() {
+ test.assertEqual(this, pb, "`this` should be private-browsing module");
+ test.assert(pbService.privateBrowsingEnabled,
+ 'private mode is active when "start" event is emitted');
+ test.assert(pb.isActive,
+ '`isActive` is `true` when "start" event is emitted');
+ pb.removeListener("start", onStart);
+ test.done();
+ });
+ pb.activate();
+ };
+
+ exports.testStop = function(test) {
+ test.waitUntilDone();
+ pb.on("stop", function onStop() {
+ test.assertEqual(this, pb, "`this` should be private-browsing module");
+ test.assertEqual(pbService.privateBrowsingEnabled, false,
+ "private mode is disabled when stop event is emitted");
+ test.assertEqual(pb.isActive, false,
+ "`isActive` is `false` when stop event is emitted");
+ pb.removeListener("stop", onStop);
+ test.done();
+ });
+ pb.activate();
+ pb.deactivate();
+ };
+
+ exports.testAutomaticUnload = function(test) {
+ test.waitUntilDone();
+ // Create another private browsing instance and unload it
+ let loader = Loader(module);
+ let pb2 = loader.require("private-browsing");
+ let called = false;
+ pb2.on("start", function onStart() {
+ called = true;
+ test.fail("should not be called:x");
+ });
+ loader.unload();
+
+ // Then switch to private mode in order to check that the previous instance
+ // is correctly destroyed
+ pb.activate();
+ pb.once("start", function onStart() {
+ require("timer").setTimeout(function () {
+ test.assert(!called,
+ "First private browsing instance is destroyed and inactive");
+
+ // Must reset to normal mode, so that next test starts with it.
+ pb.deactivate();
+ test.done();
+ }, 0);
+ });
+ };
+
+ exports.testBothListeners = function(test) {
+ test.waitUntilDone();
+ let stop = false;
+ let start = false;
+
+ function onStop() {
+ test.assertEqual(stop, false,
+ "stop callback must be called only once");
+ test.assertEqual(pbService.privateBrowsingEnabled, false,
+ "private mode is disabled when stop event is emitted");
+ test.assertEqual(pb.isActive, false,
+ "`isActive` is `false` when stop event is emitted");
+
+ pb.on("start", finish);
+ pb.removeListener("start", onStart);
+ pb.removeListener("start", onStart2);
+ pb.activate();
+ stop = true;
+ }
+
+ function onStart() {
+ test.assertEqual(false, start,
+ "stop callback must be called only once");
+ test.assert(pbService.privateBrowsingEnabled,
+ "private mode is active when start event is emitted");
+ test.assert(pb.isActive,
+ "`isActive` is `true` when start event is emitted");
+
+ pb.on("stop", onStop);
+ pb.deactivate();
+ start = true;
+ }
+
+ function onStart2() {
+ test.assert(start, "start listener must be called already");
+ test.assertEqual(false, stop, "stop callback must not be called yet");
+ }
+
+ function finish() {
+ test.assert(pbService.privateBrowsingEnabled, true,
+ "private mode is active when start event is emitted");
+ test.assert(pb.isActive,
+ "`isActive` is `true` when start event is emitted");
+
+ pb.removeListener("start", finish);
+ pb.removeListener("stop", onStop);
+
+ pb.deactivate();
+ pb.once("stop", function () {
+ test.assertEqual(pbService.privateBrowsingEnabled, false);
+ test.assertEqual(pb.isActive, false);
+
+ test.done();
+ });
+ }
+
+ pb.on("start", onStart);
+ pb.on("start", onStart2);
+ pbService.privateBrowsingEnabled = true;
+ };
+
+ exports["test activate private mode via handler"] = function(test) {
+ const tabs = require("tabs");
+
+ test.waitUntilDone();
+ function onReady(tab) {
+ if (tab.url == "about:robots")
+ tab.close(function() pb.activate());
+ }
+ function cleanup(tab) {
+ if (tab.url == "about:") {
+ tabs.removeListener("ready", cleanup);
+ tab.close(function onClose() {
+ test.done();
+ });
+ }
+ }
+
+ tabs.on("ready", onReady);
+ pb.once("start", function onStart() {
+ test.pass("private mode was activated");
+ pb.deactivate();
+ });
+ pb.once("stop", function onStop() {
+ test.pass("private mode was deactivated");
+ tabs.removeListener("ready", onReady);
+ tabs.on("ready", cleanup);
+ });
+ tabs.once("open", function onOpen() {
+ tabs.open("about:robots");
+ });
+ tabs.open("about:");
+ };
+}
+else {
+ // tests for the case where private browsing doesn't exist
+ exports.testNoImpl = function (test) {
+ test.assertEqual(pb.isActive, false,
+ "pb.isActive returns false when private browsing isn't supported");
+ };
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-request.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-request.js
new file mode 100644
index 0000000..8b8300e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-request.js
@@ -0,0 +1,358 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Paul O’Shannessy <paul@oshannessy.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Request = require("request").Request;
+
+var port = 8080;
+var data = require("self").data;
+var testFilePath = require("url").toFilename(data.url("test-request.txt"));
+var basePath = require("file").dirname(testFilePath);
+
+var {startServerAsync} = require("httpd");
+
+exports.testOptionsValidator = function(test) {
+ // First, a simple test to make sure we didn't break normal functionality.
+ test.assertRaises(function () {
+ Request({
+ url: null
+ });
+ }, 'The option "url" must be one of the following types: string');
+
+ // Next we'll have a Request that doesn't throw from c'tor, but from a setter.
+ let req = Request({
+ url: "http://playground.zpao.com/jetpack/request/text.php",
+ onComplete: function () {}
+ });
+ test.assertRaises(function () {
+ req.url = null;
+ }, 'The option "url" must be one of the following types: string');
+ // The url shouldn't have changed, so check that
+ test.assertEqual(req.url, "http://playground.zpao.com/jetpack/request/text.php");
+}
+
+exports.testContentValidator = function(test) {
+ test.waitUntilDone();
+ Request({
+ url: "data:text/html,response",
+ content: { 'key1' : null, 'key2' : 'some value' },
+ onComplete: function(response) {
+ test.assertEqual(response.text, "response?key1=null&key2=some+value");
+ test.done();
+ }
+ }).get();
+};
+
+// All tests below here require a network connection. They will be commented out
+// when checked in. If you'd like to run them, simply uncomment them.
+//
+// When we have the means, these tests will be converted so that they don't
+// require an external server nor a network connection.
+
+// This is a request to a file that exists.
+exports.testStatus200 = function (test) {
+ var srv = startServerAsync(port, basePath);
+
+ test.waitUntilDone();
+ var req = Request({
+ url: "http://localhost:" + port + "/test-request.txt",
+ onComplete: function (response) {
+ test.assertEqual(this, req, "`this` should be request");
+ test.assertEqual(response.status, 200);
+ test.assertEqual(response.statusText, "OK");
+ test.assertEqual(response.headers["Content-Type"], "text/plain");
+ test.assertEqual(response.text, "Look ma, no hands!\n");
+ srv.stop(function() test.done());
+ }
+ }).get();
+}
+
+// This tries to get a file that doesn't exist
+exports.testStatus404 = function (test) {
+ var srv = startServerAsync(port, basePath);
+
+ test.waitUntilDone();
+ Request({
+ // the following URL doesn't exist
+ url: "http://localhost:" + port + "/test-request-404.txt",
+ onComplete: function (response) {
+ test.assertEqual(response.status, 404);
+ test.assertEqual(response.statusText, "Not Found");
+ srv.stop(function() test.done());
+ }
+ }).get();
+}
+
+/*
+// a simple file with a known header
+exports.testKnownHeader = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/headers.php",
+ onComplete: function (response) {
+ test.assertEqual(response.headers["x-zpao-header"], "Jamba Juice");
+ test.done();
+ }
+ }).get();
+}
+
+// complex headers
+exports.testKnownHeader = function (test) {
+ let headers = {
+ "x-zpao-header": "Jamba Juice is: delicious",
+ "x-zpao-header-2": "foo, bar",
+ "Set-Cookie": "foo=bar\nbaz=foo"
+ }
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/complex_headers.php",
+ onComplete: function (response) {
+ for (k in headers) {
+ test.assertEqual(response.headers[k], headers[k]);
+ }
+ test.done();
+ }
+ }).get();
+}
+*/
+
+exports.testSimpleJSON = function (test) {
+ var srv = startServerAsync(port, basePath);
+
+ test.waitUntilDone();
+ Request({
+ url: "http://localhost:" + port + "/test-request.json",
+ onComplete: function (response) {
+ assertDeepEqual(test, response.json, { foo: "bar" });
+ srv.stop(function() test.done());
+ }
+ }).get();
+}
+
+exports.testInvalidJSON = function (test) {
+ var srv = startServerAsync(port, basePath);
+
+ test.waitUntilDone();
+ Request({
+ url: "http://localhost:" + port + "/test-request-invalid.json",
+ onComplete: function (response) {
+ test.assertEqual(response.json, null);
+ srv.stop(function() test.done());
+ }
+ }).get();
+}
+
+/*
+exports.testGetWithParamsNotContent = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php?foo=bar",
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithContent = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: { foo: "bar" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithParamsAndContent = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php?foo=bar",
+ content: { baz: "foo" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar", baz: "foo" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testSimplePost = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: { foo: "bar" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": { foo: "bar" },
+ "GET" : []
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).post();
+}
+
+exports.testEncodedContent = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: "foo=bar&baz=foo",
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar", baz: "foo" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testEncodedContentWithSpaces = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: "foo=bar+hop!&baz=foo",
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: "bar hop!", baz: "foo" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithArray = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: { foo: [1, 2], baz: "foo" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : { foo: [1, 2], baz: "foo" }
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithNestedArray = function (test) {
+ test.waitUntilDone();
+ Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: { foo: [1, 2, [3, 4]], bar: "baz" },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : this.content
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+
+exports.testGetWithNestedArray = function (test) {
+ test.waitUntilDone();
+ let request = Request({
+ url: "http://playground.zpao.com/jetpack/request/getpost.php",
+ content: {
+ foo: [1, 2, {
+ omg: "bbq",
+ "all your base!": "are belong to us"
+ }],
+ bar: "baz"
+ },
+ onComplete: function (response) {
+ let expected = {
+ "POST": [],
+ "GET" : request.content
+ };
+ assertDeepEqual(test, response.json, expected);
+ test.done();
+ }
+ }).get();
+}
+*/
+
+// This is not a proper testing for deep equal, but it's good enough for my uses
+// here. It will do type coercion to check equality, but that's good here. Data
+// coming from the server will be stringified and so "0" should be equal to 0.
+function assertDeepEqual(test, obj1, obj2, msg) {
+ function equal(o1, o2) {
+ // cover our non-object cases well enough
+ if (o1 == o2)
+ return true;
+ if (typeof(o1) != typeof(o2))
+ return false;
+ if (typeof(o1) != "object")
+ return o1 == o2;
+
+ let e = true;
+ for (let [key, val] in Iterator(o1)) {
+ e = e && key in o2 && equal(o2[key], val);
+ if (!e)
+ break;
+ }
+ for (let [key, val] in Iterator(o2)) {
+ e = e && key in o1 && equal(o1[key], val);
+ if (!e)
+ break;
+ }
+ return e;
+ }
+ msg = msg || "objects not equal - " + JSON.stringify(obj1) + " != " +
+ JSON.stringify(obj2);
+ test.assert(equal(obj1, obj2), msg);
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-selection.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-selection.js
new file mode 100644
index 0000000..cd96072
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-selection.js
@@ -0,0 +1,490 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Eric H. Jung <eric.jung@yahoo.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let timer = require("timer");
+let {Cc,Ci} = require("chrome");
+
+// Arbitrary delay needed to avoid weird behavior.
+// TODO: We need to find all uses of this and replace them
+// with more deterministic solutions.
+const ARB_DELAY = 100;
+
+// Select all divs elements in an HTML document
+function selectAllDivs(window) {
+ let divs = window.document.getElementsByTagName("div");
+ let s = window.getSelection();
+ if (s.rangeCount > 0)
+ s.removeAllRanges();
+ for (let i = 0; i < divs.length; i++) {
+ let range = window.document.createRange();
+ range.selectNode(divs[i]);
+ s.addRange(range);
+ }
+}
+
+function selectTextarea(window, from, to) {
+ let textarea = window.document.getElementsByTagName("textarea")[0];
+
+ from = from || 0;
+ to = to || textarea.value.length;
+
+ textarea.setSelectionRange(from, to);
+ textarea.focus();
+}
+
+function primeTestCase(html, test, callback) {
+ let tabBrowser = require("tab-browser");
+ let dataURL = "data:text/html," + encodeURI(html);
+ let tracker = tabBrowser.whenContentLoaded(
+ function(window) {
+ if (window.document.location.href != dataURL)
+ return;
+ callback(window, test);
+ timer.setTimeout(function() {
+ tracker.unload();
+ test.done();
+ window.close();
+ },
+ ARB_DELAY);
+ }
+ );
+ tabBrowser.addTab(dataURL);
+}
+
+const DIV1 = '<div id="foo">bar</div>';
+const DIV2 = '<div>noodles</div>';
+const HTML_MULTIPLE = '<html><body>' + DIV1 + DIV2 + '</body></html>';
+const HTML_SINGLE = '<html><body>' + DIV1 + '</body></html>';
+
+// Tests of contiguous
+
+exports.testContiguousMultiple = function testContiguousMultiple(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.isContiguous, false,
+ "selection.isContiguous multiple works.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testContiguousSingle = function testContiguousSingle(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.isContiguous, true,
+ "selection.isContiguous single works.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testContiguousWithoutSelection =
+ function testContiguousWithoutSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ test.assertEqual(selection.isContiguous, false,
+ "selection.isContiguous without selection works.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+/**
+ * Test that setting the contiguous property has no effect.
+ */
+/*exports.testSetContiguous = function testSetContiguous(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ try {
+ selection.isContiguous = true;
+ test.assertEqual(selection.isContiguous, false,
+ "setting selection.isContiguous doesn't work (as expected).");
+ }
+ catch (e) {
+ test.pass("setting selection.isContiguous doesn't work (as expected).");
+ }
+ });
+
+ test.waitUntilDone(5000);
+};*/
+
+
+// HTML tests
+
+exports.testGetHTMLSingleSelection = function testGetHTMLSingleSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.html, DIV1, "get html selection works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+/* Myk's comments: This is fine. However, it reminds me to figure out and
+ specify whether iteration is ordered. If so, we'll want to change this
+ test in the future to test that the discontiguous selections are returned in
+ the appropriate order. In the meantime, add a comment to that effect here */
+exports.testGetHTMLMultipleSelection =
+ function testGetHTMLMultipleSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ let assertions = false;
+ for each (let i in selection) {
+ test.assertEqual(true, [DIV1, DIV2].some(function(t) t == i.html),
+ "get multiple selection html works");
+ assertions = true;
+ }
+ // Ensure we ran at least one assertEqual()
+ test.assert(assertions, "No assertions were called");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLNull = function testGetHTMLNull(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ test.assertEqual(selection.html, null, "get html null works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLWeird = function testGetHTMLWeird(test) {
+ let selection = require("selection");
+ // If the getter is used when there are contiguous selections, the first
+ // selection should be returned
+ primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.html, DIV1, "get html weird works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLNullInTextareaSelection =
+ function testGetHTMLNullInTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ test.assertEqual(selection.html, null, "get html null in textarea works")
+ });
+
+ test.waitUntilDone(5000);
+};
+
+const REPLACEMENT_HTML = "<b>Lorem ipsum dolor sit amet</b>";
+
+exports.testSetHTMLSelection = function testSetHTMLSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ selection.html = REPLACEMENT_HTML;
+ test.assertEqual(selection.html, "<span>" + REPLACEMENT_HTML +
+ "</span>", "selection html works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testSetHTMLException = function testSetHTMLException(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ try {
+ selection.html = REPLACEMENT_HTML;
+ test.fail("set HTML throws when there's no selection.");
+ }
+ catch (e) {
+ test.pass("set HTML throws when there's no selection.");
+ }
+ });
+
+ test.waitUntilDone(5000);
+};
+
+const TEXT1 = "foo";
+const TEXT2 = "noodles";
+const TEXT_MULTIPLE = "<html><body><div>" + TEXT1 + "</div><div>" + TEXT2 +
+ "</div></body></html>";
+const TEXT_SINGLE = "<html><body><div>" + TEXT1 + "</div></body></html>";
+const TEXT_FIELD = "<html><body><textarea>" + TEXT1 + "</textarea></body></html>";
+
+// Text tests
+
+exports.testSetHTMLinTextareaSelection =
+ function testSetHTMLinTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ // HTML string is set as plain text in textareas, that's because
+ // `selection.html` and `selection.text` are basically aliases when a
+ // value is set. See bug 677269
+ selection.html = REPLACEMENT_HTML;
+
+ test.assertEqual(selection.text, REPLACEMENT_HTML,
+ "set selection html in textarea works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextSingleSelection =
+ function testGetTextSingleSelection(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.text, TEXT1, "get text selection works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextMultipleSelection =
+ function testGetTextMultipleSelection(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ let assertions = false;
+ for each (let i in selection) {
+ test.assertEqual(true, [TEXT1, TEXT2].some(function(t) t == i.text),
+ "get multiple selection text works");
+ assertions = true;
+ }
+ // Ensure we ran at least one assertEqual()
+ test.assert(assertions, "No assertions were called");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextNull = function testGetTextNull(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ test.assertEqual(selection.text, null, "get text null works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextWeird = function testGetTextWeird(test) {
+ let selection = require("selection");
+ // If the getter is used when there are contiguous selections, the first
+ // selection should be returned
+ primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ test.assertEqual(selection.text, TEXT1, "get text weird works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextNullInTextareaSelection =
+ function testGetTextInTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ test.assertEqual(selection.text, null, "get text null in textarea works")
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testGetTextInTextareaSelection =
+ function testGetTextInTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ test.assertEqual(selection.text, TEXT1, "get text null in textarea works")
+ });
+
+ test.waitUntilDone(5000);
+};
+
+const REPLACEMENT_TEXT = "Lorem ipsum dolor sit amet";
+
+exports.testSetTextSelection = function testSetTextSelection(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ selectAllDivs(window);
+ selection.text = REPLACEMENT_TEXT;
+ test.assertEqual(selection.text, REPLACEMENT_TEXT, "selection text works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testSetHTMLException = function testSetHTMLException(test) {
+ let selection = require("selection");
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ try {
+ selection.text = REPLACEMENT_TEXT;
+ test.fail("set HTML throws when there's no selection.");
+ }
+ catch (e) {
+ test.pass("set HTML throws when there's no selection.");
+ }
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testSetTextInTextareaSelection =
+ function testSetTextInTextareaSelection(test) {
+ let selection = require("selection");
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ selection.text = REPLACEMENT_TEXT;
+
+ test.assertEqual(selection.text, REPLACEMENT_TEXT,
+ "set selection text in textarea works");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+// Iterator tests
+
+exports.testIterator = function testIterator(test) {
+ let selection = require("selection");
+ let selectionCount = 0;
+ primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+ selectAllDivs(window);
+ for each (let i in selection)
+ selectionCount++;
+ test.assertEqual(2, selectionCount, "iterator works.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+exports.testIteratorWithTextareaSelection =
+ function testIteratorWithTextareaSelection(test) {
+ let selection = require("selection");
+ let selectionCount = 0;
+
+ primeTestCase(TEXT_FIELD, test, function(window, test) {
+ selectTextarea(window);
+
+ for each (let i in selection)
+ selectionCount++;
+
+ test.assertEqual(1, selectionCount, "iterator works in textarea.");
+ });
+
+ test.waitUntilDone(5000);
+};
+
+/* onSelect tests */
+
+/*
+function sendSelectionSetEvent(window) {
+ const Ci = Components.interfaces;
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+ if (!utils.sendSelectionSetEvent(0, 1, false))
+ dump("**** sendSelectionSetEvent did not select anything\n");
+ else
+ dump("**** sendSelectionSetEvent succeeded\n");
+}
+
+// testOnSelect() requires nsIDOMWindowUtils, which is only available in
+// Firefox 3.7+.
+exports.testOnSelect = function testOnSelect(test) {
+ let selection = require("selection");
+ let callbackCount = 0;
+ primeTestCase(TEXT_SINGLE, test, function(window, test) {
+ selection.onSelect = function() {callbackCount++};
+ // Now simulate the user selecting stuff
+ sendSelectionSetEvent(window);
+ selection.text = REPLACEMENT_TEXT;
+ test.assertEqual(1, callbackCount, "onSelect text listener works.");
+ //test.pass();
+ //test.done();
+ });
+
+ test.waitUntilDone(5000);
+};
+
+// testOnSelectExceptionNoBubble() requires nsIDOMWindowUtils, which is only
+// available in Firefox 3.7+.
+exports.testOnSelectExceptionNoBubble =
+ function testOnSelectTextSelection(test) {
+ let selection = require("selection");
+ primeTestCase(HTML_SINGLE, test, function(window, test) {
+ selection.onSelect = function() {
+ throw new Error("Exception thrown in testOnSelectExceptionNoBubble");
+ };
+ // Now simulate the user selecting stuff
+ sendSelectionSetEvent(window);
+ test.pass("onSelect catches exceptions.");
+ });
+
+ test.waitUntilDone(5000);
+};
+*/
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ require("selection");
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("The selection module does not support this application.");
+ };
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-simple-prefs.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-simple-prefs.js
new file mode 100644
index 0000000..581eca1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-simple-prefs.js
@@ -0,0 +1,180 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Erik Vold <erikvvold@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+const { Loader } = require("./helpers");
+const setTimeout = require("timers").setTimeout;
+const notify = require("observer-service").notify;
+const { jetpackID } = require("@packaging");
+
+exports.testSetGetBool = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ test.assertEqual(sp.test, undefined, "Value should not exist");
+ sp.test = true;
+ test.assert(sp.test, "Value read should be the value previously set");
+
+ loader.unload();
+ test.done();
+};
+
+exports.testSetGetInt = function(test) {
+ test.waitUntilDone();
+
+ // Load the module once, set a value.
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ test.assertEqual(sp["test-int"], undefined, "Value should not exist");
+ sp["test-int"] = 1;
+ test.assertEqual(sp["test-int"], 1, "Value read should be the value previously set");
+
+ loader.unload();
+ test.done();
+};
+
+exports.testSetComplex = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ try {
+ sp["test-complex"] = {test: true};
+ test.fail("Complex values are not allowed");
+ }
+ catch (e) {
+ test.pass("Complex values are not allowed");
+ }
+
+ loader.unload();
+ test.done();
+};
+
+exports.testSetGetString = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ test.assertEqual(sp["test-string"], undefined, "Value should not exist");
+ sp["test-string"] = "test";
+ test.assertEqual(sp["test-string"], "test", "Value read should be the value previously set");
+
+ loader.unload();
+ test.done();
+};
+
+exports.testHasAndRemove = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs").prefs;
+
+ sp.test = true;
+ test.assert(("test" in sp), "Value exists");
+ delete sp.test;
+ test.assertEqual(sp.test, undefined, "Value should be undefined");
+
+ loader.unload();
+ test.done();
+
+};
+
+exports.testPrefListener = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs");
+
+ let listener = function(prefName) {
+ test.assertEqual(prefName, "test-listen", "The prefs listener heard the right event");
+ test.done();
+ };
+
+ sp.on("test-listen", listener);
+
+ sp.prefs["test-listen"] = true;
+ loader.unload();
+};
+
+exports.testBtnListener = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs");
+
+ sp.on("test-btn-listen", function() {
+ test.pass("Button press event was heard");
+ test.done();
+ });
+ notify((jetpackID + "-cmdPressed"), "", "test-btn-listen");
+
+ loader.unload();
+};
+
+exports.testPrefRemoveListener = function(test) {
+ test.waitUntilDone();
+
+ let loader = Loader(module);
+ let sp = loader.require("simple-prefs");
+ let counter = 0;
+
+ let listener = function() {
+ test.pass("The prefs listener was not removed yet");
+
+ if (++counter > 1)
+ test.fail("The prefs listener was not removed");
+
+ sp.removeListener("test-listen2", listener);
+
+ sp.prefs["test-listen2"] = false;
+
+ setTimeout(function() {
+ test.pass("The prefs listener was removed");
+ loader.unload();
+ test.done();
+ }, 250);
+ };
+
+ sp.on("test-listen2", listener);
+
+ sp.prefs["test-listen2"] = true;
+};
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-simple-storage.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-simple-storage.js
new file mode 100644
index 0000000..95e4689
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-simple-storage.js
@@ -0,0 +1,346 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const file = require("file");
+const prefs = require("preferences-service");
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+
+let {Cc,Ci} = require("chrome");
+
+const { Loader } = require("./helpers");
+const options = require("@packaging");
+
+let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+storeFile.append("jetpack");
+storeFile.append(options.jetpackID);
+storeFile.append("simple-storage");
+storeFile.append("store.json");
+let storeFilename = storeFile.path;
+
+exports.testSetGet = function (test) {
+ test.waitUntilDone();
+
+ // Load the module once, set a value.
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ // Load the module again and make sure the value stuck.
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ file.remove(storeFilename);
+ test.done();
+ };
+ test.assertEqual(ss.storage.foo, val, "Value should persist");
+ loader.unload();
+ };
+ let val = "foo";
+ ss.storage.foo = val;
+ test.assertEqual(ss.storage.foo, val, "Value read should be value set");
+ loader.unload();
+};
+
+exports.testSetGetRootArray = function (test) {
+ setGetRoot(test, [1, 2, 3], function (arr1, arr2) {
+ if (arr1.length !== arr2.length)
+ return false;
+ for (let i = 0; i < arr1.length; i++) {
+ if (arr1[i] !== arr2[i])
+ return false;
+ }
+ return true;
+ });
+};
+
+exports.testSetGetRootBool = function (test) {
+ setGetRoot(test, true);
+};
+
+exports.testSetGetRootFunction = function (test) {
+ setGetRootError(test, function () {},
+ "Setting storage to a function should fail");
+};
+
+exports.testSetGetRootNull = function (test) {
+ setGetRoot(test, null);
+};
+
+exports.testSetGetRootNumber = function (test) {
+ setGetRoot(test, 3.14);
+};
+
+exports.testSetGetRootObject = function (test) {
+ setGetRoot(test, { foo: 1, bar: 2 }, function (obj1, obj2) {
+ for (let [prop, val] in Iterator(obj1)) {
+ if (!(prop in obj2) || obj2[prop] !== val)
+ return false;
+ }
+ for (let [prop, val] in Iterator(obj2)) {
+ if (!(prop in obj1) || obj1[prop] !== val)
+ return false;
+ }
+ return true;
+ });
+};
+
+exports.testSetGetRootString = function (test) {
+ setGetRoot(test, "sho' 'nuff");
+};
+
+exports.testSetGetRootUndefined = function (test) {
+ setGetRootError(test, undefined, "Setting storage to undefined should fail");
+};
+
+exports.testEmpty = function (test) {
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ loader.unload();
+ test.assert(!file.exists(storeFilename), "Store file should not exist");
+};
+
+exports.testMalformed = function (test) {
+ let stream = file.open(storeFilename, "w");
+ stream.write("i'm not json");
+ stream.close();
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ let empty = true;
+ for (let key in ss.storage) {
+ empty = false;
+ break;
+ }
+ test.assert(empty, "Malformed storage should cause root to be empty");
+ loader.unload();
+};
+
+// Go over quota and handle it by listener.
+exports.testQuotaExceededHandle = function (test) {
+ test.waitUntilDone();
+ prefs.set(QUOTA_PREF, 18);
+
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ ss.on("OverQuota", function () {
+ test.pass("OverQuota was emitted as expected");
+ test.assertEqual(this, ss, "`this` should be simple storage");
+ ss.storage = { x: 4, y: 5 };
+
+ manager(loader).jsonStore.onWrite = function () {
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ let numProps = 0;
+ for (let prop in ss.storage)
+ numProps++;
+ test.assert(numProps, 2,
+ "Store should contain 2 values: " + ss.storage.toSource());
+ test.assertEqual(ss.storage.x, 4, "x value should be correct");
+ test.assertEqual(ss.storage.y, 5, "y value should be correct");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ prefs.reset(QUOTA_PREF);
+ test.done();
+ };
+ loader.unload();
+ };
+ loader.unload();
+ });
+ // This will be JSON.stringify()ed to: {"a":1,"b":2,"c":3} (19 bytes)
+ ss.storage = { a: 1, b: 2, c: 3 };
+ manager(loader).jsonStore.write();
+};
+
+// Go over quota but don't handle it. The last good state should still persist.
+exports.testQuotaExceededNoHandle = function (test) {
+ test.waitUntilDone();
+ prefs.set(QUOTA_PREF, 5);
+
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+
+ manager(loader).jsonStore.onWrite = function (storage) {
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ test.assertEqual(ss.storage, val,
+ "Value should have persisted: " + ss.storage);
+ ss.storage = "some very long string that is very long";
+ ss.on("OverQuota", function () {
+ test.pass("OverQuota emitted as expected");
+ manager(loader).jsonStore.onWrite = function () {
+ test.fail("Over-quota value should not have been written");
+ };
+ loader.unload();
+
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ test.assertEqual(ss.storage, val,
+ "Over-quota value should not have been written, " +
+ "old value should have persisted: " + ss.storage);
+ loader.unload();
+ prefs.reset(QUOTA_PREF);
+ test.done();
+ });
+ manager(loader).jsonStore.write();
+ };
+
+ let val = "foo";
+ ss.storage = val;
+ loader.unload();
+};
+
+exports.testQuotaUsage = function (test) {
+ test.waitUntilDone();
+
+ let quota = 21;
+ prefs.set(QUOTA_PREF, quota);
+
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+
+ // {"a":1} (7 bytes)
+ ss.storage = { a: 1 };
+ test.assertEqual(ss.quotaUsage, 7 / quota, "quotaUsage should be correct");
+
+ // {"a":1,"bb":2} (14 bytes)
+ ss.storage = { a: 1, bb: 2 };
+ test.assertEqual(ss.quotaUsage, 14 / quota, "quotaUsage should be correct");
+
+ // {"a":1,"bb":2,"cc":3} (21 bytes)
+ ss.storage = { a: 1, bb: 2, cc: 3 };
+ test.assertEqual(ss.quotaUsage, 21 / quota, "quotaUsage should be correct");
+
+ manager(loader).jsonStore.onWrite = function () {
+ prefs.reset(QUOTA_PREF);
+ test.done();
+ };
+ loader.unload();
+};
+
+exports.testUninstall = function (test) {
+ test.waitUntilDone();
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function () {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ loader.unload("uninstall");
+ test.assert(!file.exists(storeFilename), "Store file should be removed");
+ test.done();
+ };
+ ss.storage.foo = "foo";
+ loader.unload();
+};
+
+exports.testSetNoSetRead = function (test) {
+ test.waitUntilDone();
+
+ // Load the module, set a value.
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ // Load the module again but don't access ss.storage.
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ test.fail("Nothing should be written since `storage` was not accessed.");
+ };
+ loader.unload();
+
+ // Load the module a third time and make sure the value stuck.
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function (storage) {
+ file.remove(storeFilename);
+ test.done();
+ };
+ test.assertEqual(ss.storage.foo, val, "Value should persist");
+ loader.unload();
+ };
+ let val = "foo";
+ ss.storage.foo = val;
+ test.assertEqual(ss.storage.foo, val, "Value read should be value set");
+ loader.unload();
+};
+
+function manager(loader) loader.sandbox("simple-storage").manager;
+
+function newLoader() Loader(module);
+
+function setGetRoot(test, val, compare) {
+ test.waitUntilDone();
+
+ compare = compare || function (a, b) a === b;
+
+ // Load the module once, set a value.
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function () {
+ test.assert(file.exists(storeFilename), "Store file should exist");
+
+ // Load the module again and make sure the value stuck.
+ loader = newLoader(test);
+ ss = loader.require("simple-storage");
+ manager(loader).jsonStore.onWrite = function () {
+ file.remove(storeFilename);
+ test.done();
+ };
+ test.assert(compare(ss.storage, val), "Value should persist");
+ loader.unload();
+ };
+ ss.storage = val;
+ test.assert(compare(ss.storage, val), "Value read should be value set");
+ loader.unload();
+}
+
+function setGetRootError(test, val, msg) {
+ let pred = "storage must be one of the following types: " +
+ "array, boolean, null, number, object, string";
+ let loader = newLoader(test);
+ let ss = loader.require("simple-storage");
+ test.assertRaises(function () ss.storage = val, pred, msg);
+ loader.unload();
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-tabs.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-tabs.js
new file mode 100644
index 0000000..ae98312
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-tabs.js
@@ -0,0 +1,905 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Dietrich Ayala <dietrich@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+var {Cc,Ci} = require("chrome");
+const { Loader } = require("./helpers");
+
+// test tab.activeTab getter
+exports.testActiveTab_getter = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+
+ let url = "data:text/html,<html><head><title>foo</title></head></html>";
+ require("tab-browser").addTab(
+ url,
+ {
+ onLoad: function(e) {
+ test.assert(tabs.activeTab);
+ test.assertEqual(tabs.activeTab.url, url);
+ test.assertEqual(tabs.activeTab.title, "foo");
+ closeBrowserWindow(window, function() test.done());
+ }
+ }
+ );
+ });
+};
+
+// test 'BrowserWindow' instance creation on tab 'activate' event
+// See bug 648244: there was a infinite loop.
+exports.testBrowserWindowCreationOnActivate = function(test) {
+ test.waitUntilDone();
+
+ let windows = require("windows").browserWindows;
+ let tabs = require("tabs");
+
+ let gotActivate = false;
+
+ tabs.once('activate', function onActivate(eventTab) {
+ test.assert(windows.activeWindow, "Is able to fetch activeWindow");
+ gotActivate = true;
+ });
+
+ openBrowserWindow(function(window, browser) {
+ test.assert(gotActivate, "Received activate event before openBrowserWindow's callback is called");
+ closeBrowserWindow(window, function () test.done());
+ });
+}
+
+// test tab.activeTab setter
+exports.testActiveTab_setter = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,<html><head><title>foo</title></head></html>";
+
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ test.assertEqual(tabs.activeTab.url, "about:blank", "activeTab url has not changed");
+ test.assertEqual(tab.url, url, "url of new background tab matches");
+ tabs.on('activate', function onActivate(eventTab) {
+ tabs.removeListener('activate', onActivate);
+ test.assertEqual(tabs.activeTab.url, url, "url after activeTab setter matches");
+ test.assertEqual(eventTab, tab, "event argument is the activated tab");
+ test.assertEqual(eventTab, tabs.activeTab, "the tab is the active one");
+ closeBrowserWindow(window, function() test.done());
+ });
+ tab.activate();
+ })
+
+ tabs.open({
+ url: url,
+ inBackground: true
+ });
+ });
+};
+
+exports.testAutomaticDestroy = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+
+ // Create a second tab instance that we will destroy
+ let called = false;
+
+ let loader = Loader(module);
+ let tabs2 = loader.require("tabs");
+ tabs2.on('open', function onOpen(tab) {
+ called = true;
+ });
+
+ loader.unload();
+
+ // Fire a tab event an ensure that this destroyed tab is inactive
+ tabs.once('open', function () {
+ require("timer").setTimeout(function () {
+ test.assert(!called, "Unloaded tab module is destroyed and inactive");
+ closeBrowserWindow(window, function() test.done());
+ }, 0);
+ });
+
+ tabs.open("data:text/html,foo");
+
+ });
+};
+
+// test tab properties
+exports.testTabProperties = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs= require("tabs");
+ let url = "data:text/html,<html><head><title>foo</title></head><body>foo</body></html>";
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ test.assertEqual(tab.title, "foo", "title of the new tab matches");
+ test.assertEqual(tab.url, url, "URL of the new tab matches");
+ test.assert(tab.favicon, "favicon of the new tab is not empty");
+ test.assertEqual(tab.style, null, "style of the new tab matches");
+ test.assertEqual(tab.index, 1, "index of the new tab matches");
+ test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// test tabs iterator and length property
+exports.testTabsIteratorAndLength = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let startCount = 0;
+ for each (let t in tabs) startCount++;
+ test.assertEqual(startCount, tabs.length, "length property is correct");
+ let url = "data:text/html,default";
+ tabs.open(url);
+ tabs.open(url);
+ tabs.open({
+ url: url,
+ onOpen: function(tab) {
+ let count = 0;
+ for each (let t in tabs) count++;
+ test.assertEqual(count, startCount + 3, "iterated tab count matches");
+ test.assertEqual(startCount + 3, tabs.length, "iterated tab count matches length property");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// test tab.url setter
+exports.testTabLocation = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url1 = "data:text/html,foo";
+ let url2 = "data:text/html,bar";
+
+ tabs.on('ready', function onReady(tab) {
+ if (tab.url != url2)
+ return;
+ tabs.removeListener('ready', onReady);
+ test.pass("tab.load() loaded the correct url");
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.open({
+ url: url1,
+ onOpen: function(tab) {
+ tab.url = url2
+ }
+ });
+ });
+};
+
+// test tab.close()
+exports.testTabClose = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,foo";
+
+ test.assertNotEqual(tabs.activeTab.url, url, "tab is now the active tab");
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab");
+ tab.close(function() {
+ closeBrowserWindow(window, function() test.done());
+ });
+ test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
+ });
+
+ tabs.open(url);
+ });
+};
+
+// test tab.reload()
+exports.testTabReload = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,<!doctype%20html><title></title>";
+
+ tabs.open({ url: url, onReady: function onReady(tab) {
+ tab.removeListener("ready", onReady);
+
+ browser.addEventListener(
+ "load",
+ function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+
+ browser.addEventListener(
+ "load",
+ function onReload() {
+ browser.removeEventListener("load", onReload, true);
+ test.pass("the tab was loaded again");
+ test.assertEqual(tab.url, url, "the tab has the same URL");
+ closeBrowserWindow(window, function() test.done());
+ },
+ true
+ );
+ tab.reload();
+ },
+ true
+ );
+ }});
+ });
+};
+
+// test tab.move()
+exports.testTabMove = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,foo";
+
+ tabs.open({
+ url: url,
+ onOpen: function(tab) {
+ test.assertEqual(tab.index, 1, "tab index before move matches");
+ tab.index = 0;
+ test.assertEqual(tab.index, 0, "tab index after move matches");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// open tab with default options
+exports.testOpen = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,default";
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ test.assertEqual(tab.url, url, "URL of the new tab matches");
+ test.assertEqual(window.content.location, url, "URL of active tab in the current window matches");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// open pinned tab
+exports.testOpenPinned = function(test) {
+ const xulApp = require("xul-app");
+ if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
+ // test tab pinning
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,default";
+ tabs.open({
+ url: url,
+ isPinned: true,
+ onOpen: function(tab) {
+ test.assertEqual(tab.isPinned, true, "The new tab is pinned");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+ }
+ else {
+ test.pass("Pinned tabs are not supported in this application.");
+ }
+};
+
+// pin/unpin opened tab
+exports.testPinUnpin = function(test) {
+ const xulApp = require("xul-app");
+ if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let url = "data:text/html,default";
+ tabs.open({
+ url: url,
+ onOpen: function(tab) {
+ tab.pin();
+ test.assertEqual(tab.isPinned, true, "The tab was pinned correctly");
+ tab.unpin();
+ test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+ }
+ else {
+ test.pass("Pinned tabs are not supported in this application.");
+ }
+};
+
+// open tab in background
+exports.testInBackground = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let activeUrl = tabs.activeTab.url;
+ let url = "data:text/html,background";
+ test.assertEqual(activeWindow, window, "activeWindow matches this window");
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
+ test.assertEqual(tab.url, url, "URL of the new background tab matches");
+ test.assertEqual(activeWindow, window, "a new window was not opened");
+ test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
+ closeBrowserWindow(window, function() test.done());
+ });
+ tabs.open({
+ url: url,
+ inBackground: true
+ });
+ });
+};
+
+// open tab in new window
+exports.testOpenInNewWindow = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+
+ let cache = [];
+ let windowUtils = require("window-utils");
+ let wt = new windowUtils.WindowTracker({
+ onTrack: function(win) {
+ cache.push(win);
+ },
+ onUntrack: function(win) {
+ cache.splice(cache.indexOf(win), 1)
+ }
+ });
+ let startWindowCount = cache.length;
+
+ let url = "data:text/html,newwindow";
+ tabs.open({
+ url: url,
+ inNewWindow: true,
+ onReady: function(tab) {
+ let newWindow = cache[cache.length - 1];
+ test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
+ test.assertEqual(activeWindow, newWindow, "new window is active");
+ test.assertEqual(tab.url, url, "URL of the new tab matches");
+ test.assertEqual(newWindow.content.location, url, "URL of new tab in new window matches");
+ test.assertEqual(tabs.activeTab.url, url, "URL of activeTab matches");
+ for (var i in cache) cache[i] = null;
+ wt.unload();
+ closeBrowserWindow(newWindow, function() {
+ closeBrowserWindow(window, function() test.done());
+ });
+ }
+ });
+ });
+};
+
+// onOpen event handler
+exports.testTabsEvent_onOpen = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,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) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('open', listener1);
+ tabs.removeListener('open', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.open(url);
+ });
+};
+
+// onClose event handler
+exports.testTabsEvent_onClose = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,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) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('close', listener1);
+ tabs.removeListener('close', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.on('ready', function onReady(tab) {
+ tabs.removeListener('ready', onReady);
+ tab.close();
+ });
+
+ tabs.open(url);
+ });
+};
+
+// onClose event handler when a window is closed
+exports.testTabsEvent_onCloseWindow = function(test) {
+ test.waitUntilDone();
+
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+
+ let closeCount = 0, individualCloseCount = 0;
+ function listener() {
+ closeCount++;
+ }
+ tabs.on('close', listener);
+
+ // One tab is already open with the window
+ let openTabs = 1;
+ function testCasePossiblyLoaded() {
+ if (++openTabs == 4) {
+ beginCloseWindow();
+ }
+ }
+
+ tabs.open({
+ url: "data:text/html,tab2",
+ onOpen: function() testCasePossiblyLoaded(),
+ onClose: function() individualCloseCount++
+ });
+
+ tabs.open({
+ url: "data:text/html,tab3",
+ onOpen: function() testCasePossiblyLoaded(),
+ onClose: function() individualCloseCount++
+ });
+
+ tabs.open({
+ url: "data:text/html,tab4",
+ onOpen: function() testCasePossiblyLoaded(),
+ onClose: function() individualCloseCount++
+ });
+
+ function beginCloseWindow() {
+ closeBrowserWindow(window, function testFinished() {
+ tabs.removeListener("close", listener);
+
+ test.assertEqual(closeCount, 4, "Correct number of close events received");
+ test.assertEqual(individualCloseCount, 3,
+ "Each tab with an attached onClose listener received a close " +
+ "event when the window was closed");
+
+ test.done();
+ });
+ }
+
+ });
+}
+
+// onReady event handler
+exports.testTabsEvent_onReady = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,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) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('ready', listener1);
+ tabs.removeListener('ready', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.open(url);
+ });
+};
+
+// onActivate event handler
+exports.testTabsEvent_onActivate = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,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) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('activate', listener1);
+ tabs.removeListener('activate', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.open(url);
+ });
+};
+
+// onDeactivate event handler
+exports.testTabsEvent_onDeactivate = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let url = "data:text/html,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) {
+ test.assertEqual(++eventCount, 2, "both listeners notified");
+ tabs.removeListener('deactivate', listener1);
+ tabs.removeListener('deactivate', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+
+ tabs.on('open', function onOpen(tab) {
+ tabs.removeListener('open', onOpen);
+ tabs.open("data:text/html,foo");
+ });
+
+ tabs.open(url);
+ });
+};
+
+// per-tab event handlers
+exports.testPerTabEvents = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ var tabs = require("tabs");
+ let eventCount = 0;
+
+ tabs.open({
+ url: "data:text/html,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() {
+ test.assertEqual(eventCount, 1, "both listeners notified");
+ tab.removeListener('ready', listener1);
+ tab.removeListener('ready', listener2);
+ closeBrowserWindow(window, function() test.done());
+ });
+ }
+ });
+ });
+};
+
+exports.testAttachOnOpen = function (test) {
+ // Take care that attach has to be called on tab ready and not on tab open.
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+
+ tabs.open({
+ url: "data:text/html,foobar",
+ onOpen: function (tab) {
+ let worker = tab.attach({
+ contentScript: 'self.postMessage(document.location.href); ',
+ onMessage: function (msg) {
+ test.assertEqual(msg, "about:blank",
+ "Worker document url is about:blank on open");
+ worker.destroy();
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ }
+ });
+
+ });
+}
+
+exports.testAttachOnMultipleDocuments = function (test) {
+ // Example of attach that process multiple tab documents
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let firstLocation = "data:text/html,foobar";
+ let secondLocation = "data:text/html,bar";
+ let thirdLocation = "data:text/html,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 () self.postMessage(document.location.href)' +
+ ');',
+ onMessage: function (msg) {
+ test.assertEqual(msg, firstLocation,
+ "Worker url is equal to the 1st document");
+ tab.url = secondLocation;
+ },
+ onDetach: function () {
+ detachEventCount++;
+ test.pass("Got worker1 detach event");
+ test.assertRaises(function () {
+ worker1.postMessage("ex-1");
+ },
+ /The page has been destroyed/,
+ "postMessage throw because worker1 is destroyed");
+ checkEnd();
+ }
+ });
+ worker1.postMessage("new-doc-1");
+ }
+ else if (onReadyCount == 2) {
+
+ worker2 = tab.attach({
+ contentScript: 'self.on("message", ' +
+ ' function () self.postMessage(document.location.href)' +
+ ');',
+ onMessage: function (msg) {
+ test.assertEqual(msg, secondLocation,
+ "Worker url is equal to the 2nd document");
+ tab.url = thirdLocation;
+ },
+ onDetach: function () {
+ detachEventCount++;
+ test.pass("Got worker2 detach event");
+ test.assertRaises(function () {
+ worker2.postMessage("ex-2");
+ },
+ /The page has been destroyed/,
+ "postMessage throw because worker2 is destroyed");
+ checkEnd();
+ }
+ });
+ worker2.postMessage("new-doc-2");
+ }
+ else if (onReadyCount == 3) {
+
+ tab.close();
+
+ }
+
+ }
+ });
+
+ function checkEnd() {
+ if (detachEventCount != 2)
+ return;
+
+ test.pass("Got all detach events");
+
+ closeBrowserWindow(window, function() test.done());
+ }
+
+ });
+}
+
+
+exports.testAttachWrappers = function (test) {
+ // Check that content script has access to wrapped values by default
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let document = "data:text/html,<script>var globalJSVar = true; " +
+ " document.getElementById = 3;</script>";
+ 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) {
+ test.assertEqual(msg, true, "Worker has wrapped objects ("+count+")");
+ if (count++ == 1)
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ }
+ });
+
+ });
+}
+
+/*
+// We do not offer unwrapped access to DOM since bug 601295 landed
+// See 660780 to track progress of unwrap feature
+exports.testAttachUnwrapped = function (test) {
+ // Check that content script has access to unwrapped values through unsafeWindow
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ let tabs = require("tabs");
+ let document = "data:text/html,<script>var globalJSVar=true;</script>";
+ 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) {
+ test.assertEqual(msg, true, "Worker has access to javascript content globals ("+count+")");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ }
+ });
+
+ });
+}
+*/
+
+exports['test window focus changes active tab'] = function(test) {
+ test.waitUntilDone();
+ let win1 = openBrowserWindow(function() {
+ let win2 = openBrowserWindow(function() {
+ let tabs = require("tabs");
+ tabs.on("activate", function onActivate() {
+ tabs.removeListener("activate", onActivate);
+ test.pass("activate was called on windows focus change.");
+ closeBrowserWindow(win1, function() {
+ closeBrowserWindow(win2, function() { test.done(); });
+ });
+ });
+ win1.focus();
+ }, "data:text/html,test window focus changes active tab</br><h1>Window #2");
+ }, "data:text/html,test window focus changes active tab</br><h1>Window #1");
+};
+
+exports['test ready event on new window tab'] = function(test) {
+ test.waitUntilDone();
+ let uri = encodeURI("data:text/html,Waiting for ready event!");
+
+ require("tabs").on("ready", function onReady(tab) {
+ if (tab.url === uri) {
+ require("tabs").removeListener("ready", onReady);
+ test.pass("ready event was emitted");
+ closeBrowserWindow(window, function() {
+ test.done();
+ });
+ }
+ });
+
+ let window = openBrowserWindow(function(){}, uri);
+};
+/******************* helpers *********************/
+
+// Helper for getting the active window
+this.__defineGetter__("activeWindow", function activeWindow() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+});
+
+// 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 {
+ require("timer").setTimeout(function () {
+ callback(window, browsers[0]);
+ }, 10);
+ } catch (e) { console.exception(e); }
+ }
+ }, true);
+ }
+
+ return window;
+}
+
+// Helper for calling code at window close
+function closeBrowserWindow(window, callback) {
+ window.addEventListener("unload", function unload() {
+ window.removeEventListener("unload", unload, false);
+ callback();
+ }, false);
+ window.close();
+}
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ require("tabs");
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("the tabs module does not support this application.");
+ };
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-timers.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-timers.js
new file mode 100644
index 0000000..98ea613
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-timers.js
@@ -0,0 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Shane Tomlinson <stomlinson@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+const timers = require("timers");
+
+exports.testTimeout = function (test) {
+ test.waitUntilDone();
+ timers.setTimeout(function () {
+ test.pass("timers.setTimeout works");
+ test.done();
+ }, 0);
+}
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-widget.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-widget.js
new file mode 100644
index 0000000..a5ab83c
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-widget.js
@@ -0,0 +1,950 @@
+const {Cc,Ci} = require("chrome");
+const { Loader } = require('./helpers');
+
+exports.testConstructor = function(test) {
+
+ const tabBrowser = require("tab-browser");
+
+ test.waitUntilDone(30000);
+
+ const widgets = require("widget");
+ const url = require("url");
+ const windowUtils = require("window-utils");
+
+ let browserWindow = windowUtils.activeBrowserWindow;
+ let doc = browserWindow.document;
+ let AddonsMgrListener = browserWindow.AddonsMgrListener;
+
+ function container() doc.getElementById("addon-bar");
+ function widgetCount() container() ? container().getElementsByTagName("toolbaritem").length : 0;
+ let widgetStartCount = widgetCount();
+ function widgetNode(index) container() ? container().getElementsByTagName("toolbaritem")[index] : null;
+
+ // Test basic construct/destroy
+ AddonsMgrListener.onInstalling();
+ let w = widgets.Widget({ id: "fooID", label: "foo", content: "bar" });
+ AddonsMgrListener.onInstalled();
+ test.assertEqual(widgetCount(), widgetStartCount + 1, "panel has correct number of child elements after widget construction");
+
+ // test widget height
+ test.assertEqual(widgetNode(0).firstChild.boxObject.height, 16, "widget has correct default height");
+
+ AddonsMgrListener.onUninstalling();
+ w.destroy();
+ AddonsMgrListener.onUninstalled();
+ w.destroy();
+ test.pass("Multiple destroys do not cause an error");
+ test.assertEqual(widgetCount(), widgetStartCount, "panel has correct number of child elements after destroy");
+
+ // Test automatic widget destroy on unload
+ let loader = Loader(module);
+ let widgetsFromLoader = loader.require("widget");
+ let widgetStartCount = widgetCount();
+ let w = widgetsFromLoader.Widget({ id: "fooID", label: "foo", content: "bar" });
+ test.assertEqual(widgetCount(), widgetStartCount + 1, "widget has been correctly added");
+ loader.unload();
+ test.assertEqual(widgetCount(), widgetStartCount, "widget has been destroyed on module unload");
+
+ // Test nothing
+ test.assertRaises(
+ function() widgets.Widget({}),
+ "The widget must have a non-empty label property.",
+ "throws on no properties");
+
+ // Test no label
+ test.assertRaises(
+ function() widgets.Widget({content: "foo"}),
+ "The widget must have a non-empty label property.",
+ "throws on no label");
+
+ // Test empty label
+ test.assertRaises(
+ function() widgets.Widget({label: "", content: "foo"}),
+ "The widget must have a non-empty label property.",
+ "throws on empty label");
+
+ // Test no content or image
+ test.assertRaises(
+ function() widgets.Widget({id: "fooID", label: "foo"}),
+ "No content or contentURL property found. Widgets must have one or the other.",
+ "throws on no content");
+
+ // Test empty content, no image
+ test.assertRaises(
+ function() widgets.Widget({id:"fooID", label: "foo", content: ""}),
+ "No content or contentURL property found. Widgets must have one or the other.",
+ "throws on empty content");
+
+ // Test empty image, no content
+ test.assertRaises(
+ function() widgets.Widget({id:"fooID", label: "foo", image: ""}),
+ "No content or contentURL property found. Widgets must have one or the other.",
+ "throws on empty content");
+
+ // Test empty content, empty image
+ test.assertRaises(
+ function() widgets.Widget({id:"fooID", label: "foo", content: "", image: ""}),
+ "No content or contentURL property found. Widgets must have one or the other.",
+ "throws on empty content");
+
+ // Test duplicated ID
+ let duplicateID = widgets.Widget({id: "foo", label: "foo", content: "bar"});
+ test.assertRaises(
+ function() widgets.Widget({id: "foo", label: "bar", content: "bar"}),
+ /This widget ID is already used:/,
+ "throws on duplicated id");
+ duplicateID.destroy();
+
+ // Test duplicate label, different ID
+ let w1 = widgets.Widget({id: "id1", label: "foo", content: "bar"});
+ let w2 = widgets.Widget({id: "id2", label: "foo", content: "bar"});
+ w1.destroy();
+ w2.destroy();
+
+ // Test position restore on create/destroy/create
+ // Create 3 ordered widgets
+ let w1 = widgets.Widget({id: "first", label:"first", content: "bar"});
+ let w2 = widgets.Widget({id: "second", label:"second", content: "bar"});
+ let w3 = widgets.Widget({id: "third", label:"third", content: "bar"});
+ // Remove the middle widget
+ test.assertEqual(widgetNode(1).getAttribute("label"), "second", "second widget is the second widget inserted");
+ w2.destroy();
+ test.assertEqual(widgetNode(1).getAttribute("label"), "third", "second widget is removed, so second widget is now the third one");
+ w2 = widgets.Widget({id: "second", label:"second", content: "bar"});
+ test.assertEqual(widgetNode(1).getAttribute("label"), "second", "second widget is created again, at the same location");
+ // Cleanup this testcase
+ AddonsMgrListener.onUninstalling();
+ w1.destroy();
+ w2.destroy();
+ w3.destroy();
+ AddonsMgrListener.onUninstalled();
+
+ // Test concurrent widget module instances on addon-bar hiding
+ let loader = Loader(module);
+ let anotherWidgetsInstance = loader.require("widget");
+ test.assert(container().collapsed, "UI is hidden when no widgets");
+ AddonsMgrListener.onInstalling();
+ let w1 = widgets.Widget({id: "foo", label: "foo", content: "bar"});
+ // Ideally we would let AddonsMgrListener display the addon bar
+ // But, for now, addon bar is immediatly displayed by sdk code
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=627484
+ test.assert(!container().collapsed, "UI is already visible when we just added the widget");
+ AddonsMgrListener.onInstalled();
+ test.assert(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation");
+ let w2 = anotherWidgetsInstance.Widget({id: "bar", label: "bar", content: "foo"});
+ test.assert(!container().collapsed, "UI still visible when we add a second widget");
+ AddonsMgrListener.onUninstalling();
+ w1.destroy();
+ AddonsMgrListener.onUninstalled();
+ test.assert(!container().collapsed, "UI still visible when we remove one of two widgets");
+ AddonsMgrListener.onUninstalling();
+ w2.destroy();
+ test.assert(!container().collapsed, "UI is still visible when we have removed all widget but still not called onUninstalled");
+ AddonsMgrListener.onUninstalled();
+ test.assert(container().collapsed, "UI is hidden when we have removed all widget and called onUninstalled");
+
+ // Helper for testing a single widget.
+ // Confirms proper addition and content setup.
+ function testSingleWidget(widgetOptions) {
+ // We have to display which test is being run, because here we do not
+ // use the regular test framework but rather a custom one that iterates
+ // the `tests` array.
+ console.info("executing: " + widgetOptions.id);
+
+ let startCount = widgetCount();
+ let widget = widgets.Widget(widgetOptions);
+ let node = widgetNode(startCount);
+ test.assert(node, "widget node at index");
+ test.assertEqual(node.tagName, "toolbaritem", "widget element is correct");
+ test.assertEqual(widget.width + "px", node.style.minWidth, "widget width is correct");
+ test.assertEqual(widgetCount(), startCount + 1, "container has correct number of child elements");
+ let content = node.firstElementChild;
+ test.assert(content, "found content");
+ test.assertMatches(content.tagName, /iframe|image/, "content is iframe or image");
+ return widget;
+ }
+
+ // Array of widgets to test
+ // and a function to test them.
+ let tests = [];
+ function nextTest() {
+ test.assertEqual(widgetCount(), 0, "widget in last test property cleaned itself up");
+ if (!tests.length)
+ test.done();
+ else
+ require("timer").setTimeout(tests.shift(), 0);
+ }
+ function doneTest() nextTest();
+
+ // text widget
+ tests.push(function testTextWidget() testSingleWidget({
+ id: "text",
+ label: "text widget",
+ content: "oh yeah",
+ contentScript: "self.postMessage(document.body.innerHTML);",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(this.content, message, "content matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // html widget
+ tests.push(function testHTMLWidget() testSingleWidget({
+ id: "html",
+ label: "html widget",
+ content: "<div>oh yeah</div>",
+ contentScript: "self.postMessage(document.body.innerHTML);",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(this.content, message, "content matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // image url widget
+ tests.push(function testImageURLWidget() testSingleWidget({
+ id: "image",
+ label: "image url widget",
+ contentURL: require("self").data.url("test.html"),
+ contentScript: "self.postMessage({title: document.title, " +
+ "tag: document.body.firstElementChild.tagName, " +
+ "content: document.body.firstElementChild.innerHTML});",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(message.title, "foo", "title matches");
+ test.assertEqual(message.tag, "P", "element matches");
+ test.assertEqual(message.content, "bar", "element content matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // web uri widget
+ tests.push(function testWebURIWidget() testSingleWidget({
+ id: "web",
+ label: "web uri widget",
+ contentURL: require("self").data.url("test.html"),
+ contentScript: "self.postMessage({title: document.title, " +
+ "tag: document.body.firstElementChild.tagName, " +
+ "content: document.body.firstElementChild.innerHTML});",
+ contentScriptWhen: "end",
+ onMessage: function (message) {
+ test.assertEqual(message.title, "foo", "title matches");
+ test.assertEqual(message.tag, "P", "element matches");
+ test.assertEqual(message.content, "bar", "element content matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onclick + content
+ tests.push(function testOnclickEventContent() testSingleWidget({
+ id: "click",
+ label: "click test widget - content",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onClick: function() {
+ test.pass("onClick called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onmouseover + content
+ tests.push(function testOnmouseoverEventContent() testSingleWidget({
+ id: "mouseover",
+ label: "mouseover test widget - content",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseover', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onMouseover: function() {
+ test.pass("onMouseover called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onmouseout + content
+ tests.push(function testOnmouseoutEventContent() testSingleWidget({
+ id: "mouseout",
+ label: "mouseout test widget - content",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseout', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onMouseout: function() {
+ test.pass("onMouseout called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onclick + image
+ tests.push(function testOnclickEventImage() testSingleWidget({
+ id: "click",
+ label: "click test widget - image",
+ contentURL: require("self").data.url("moz_favicon.ico"),
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.body.firstElementChild.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onClick: function() {
+ test.pass("onClick called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onmouseover + image
+ tests.push(function testOnmouseoverEventImage() testSingleWidget({
+ id: "mouseover",
+ label: "mouseover test widget - image",
+ contentURL: require("self").data.url("moz_favicon.ico"),
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseover', true, true ); " +
+ "document.body.firstElementChild.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onMouseover: function() {
+ test.pass("onMouseover called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // event: onmouseout + image
+ tests.push(function testOnmouseoutEventImage() testSingleWidget({
+ id: "mouseout",
+ label: "mouseout test widget - image",
+ contentURL: require("self").data.url("moz_favicon.ico"),
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseout', true, true ); " +
+ "document.body.firstElementChild.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onMouseout: function() {
+ test.pass("onMouseout called");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test multiple widgets
+ tests.push(function testMultipleWidgets() {
+ let w1 = widgets.Widget({id: "first", label: "first widget", content: "first content"});
+ let w2 = widgets.Widget({id: "second", label: "second widget", content: "second content"});
+
+ w1.destroy();
+ w2.destroy();
+
+ doneTest();
+ });
+
+ // test updating widget content
+ let loads = 0;
+ tests.push(function testUpdatingWidgetContent() testSingleWidget({
+ id: "content",
+ label: "content update test widget",
+ content: "<div id='me'>foo</div>",
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ if (!this.flag) {
+ this.content = "<div id='me'>bar</div>";
+ this.flag = 1;
+ }
+ else {
+ test.assertEqual(this.content, "<div id='me'>bar</div>");
+ this.destroy();
+ doneTest();
+ }
+ }
+ }));
+
+ // test updating widget contentURL
+ let url1 = "data:text/html,<body>foodle</body>";
+ let url2 = "data:text/html,<body>nistel</body>";
+
+ tests.push(function testUpdatingContentURL() testSingleWidget({
+ id: "content",
+ label: "content update test widget",
+ contentURL: url1,
+ contentScript: "self.postMessage(document.location.href);",
+ contentScriptWhen: "end",
+ onMessage: function(message) {
+ if (!this.flag) {
+ test.assertEqual(this.contentURL.toString(), url1);
+ test.assertEqual(message, url1);
+ this.contentURL = url2;
+ this.flag = 1;
+ }
+ else {
+ test.assertEqual(this.contentURL.toString(), url2);
+ test.assertEqual(message, url2);
+ this.destroy();
+ doneTest();
+ }
+ }
+ }));
+
+ // test tooltip
+ tests.push(function testTooltip() testSingleWidget({
+ id: "text",
+ label: "text widget",
+ content: "oh yeah",
+ tooltip: "foo",
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ test.assertEqual(this.tooltip, "foo", "tooltip matches");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test tooltip fallback to label
+ tests.push(function testTooltipFallback() testSingleWidget({
+ id: "fallback",
+ label: "fallback",
+ content: "oh yeah",
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ test.assertEqual(this.tooltip, this.label, "tooltip fallbacks to label");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test updating widget tooltip
+ let updated = false;
+ tests.push(function testUpdatingTooltip() testSingleWidget({
+ id: "tooltip",
+ label: "tooltip update test widget",
+ tooltip: "foo",
+ content: "<div id='me'>foo</div>",
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ this.tooltip = "bar";
+ test.assertEqual(this.tooltip, "bar", "tooltip gets updated");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test allow attribute
+ tests.push(function testDefaultAllow() testSingleWidget({
+ id: "allow",
+ label: "allow.script attribute",
+ content: "<script>document.title = 'ok';</script>",
+ contentScript: "self.postMessage(document.title)",
+ onMessage: function(message) {
+ test.assertEqual(message, "ok", "scripts are evaluated by default");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ tests.push(function testExplicitAllow() testSingleWidget({
+ id: "allow",
+ label: "allow.script attribute",
+ allow: {script: true},
+ content: "<script>document.title = 'ok';</script>",
+ contentScript: "self.postMessage(document.title)",
+ onMessage: function(message) {
+ test.assertEqual(message, "ok", "scripts are evaluated when we want to");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ tests.push(function testExplicitDisallow() testSingleWidget({
+ id: "allow",
+ label: "allow.script attribute",
+ content: "<script>document.title = 'ok';</script>",
+ allow: {script: false},
+ contentScript: "self.postMessage(document.title)",
+ onMessage: function(message) {
+ test.assertNotEqual(message, "ok", "scripts aren't evaluated when " +
+ "explicitly blocked it");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test multiple windows
+ tests.push(function testMultipleWindows() {
+ tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
+ let browserWindow = e.target.defaultView;
+ let doc = browserWindow.document;
+ function container() doc.getElementById("addon-bar");
+ function widgetCount2() container() ? container().childNodes.length : 0;
+ let widgetStartCount2 = widgetCount2();
+
+ let w1Opts = {id:"first", label: "first widget", content: "first content"};
+ let w1 = testSingleWidget(w1Opts);
+ test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first widget");
+
+ let w2Opts = {id:"second", label: "second widget", content: "second content"};
+ let w2 = testSingleWidget(w2Opts);
+ test.assertEqual(widgetCount2(), widgetStartCount2 + 2, "2nd window has correct number of child elements after second widget");
+
+ w1.destroy();
+ test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first destroy");
+ w2.destroy();
+ test.assertEqual(widgetCount2(), widgetStartCount2, "2nd window has correct number of child elements after second destroy");
+
+ closeBrowserWindow(browserWindow, function() {
+ doneTest();
+ });
+ }});
+ });
+
+ // test window closing
+ tests.push(function testWindowClosing() {
+ // 1/ Create a new widget
+ let w1Opts = {
+ id:"first",
+ label: "first widget",
+ content: "first content",
+ contentScript: "self.port.on('event', function () self.port.emit('event'))"
+ };
+ let widget = testSingleWidget(w1Opts);
+ let windows = require("windows").browserWindows;
+
+ // 2/ Retrieve a WidgetView for the initial browser window
+ let acceptDetach = false;
+ let mainView = widget.getView(windows.activeWindow);
+ test.assert(mainView, "Got first widget view");
+ mainView.on("detach", function () {
+ // 8/ End of our test. Accept detach event only when it occurs after
+ // widget.destroy()
+ if (acceptDetach)
+ doneTest();
+ else
+ test.fail("View on initial window should not be destroyed");
+ });
+ mainView.port.on("event", function () {
+ // 7/ Receive event sent during 6/ and cleanup our test
+ acceptDetach = true;
+ widget.destroy();
+ });
+
+ // 3/ First: open a new browser window
+ windows.open({
+ url: "about:blank",
+ onOpen: function(window) {
+ // 4/ Retrieve a WidgetView for this new window
+ let view = widget.getView(window);
+ test.assert(view, "Got second widget view");
+ view.port.on("event", function () {
+ test.fail("We should not receive event on the detach view");
+ });
+ view.on("detach", function () {
+ // The related view is destroyed
+ // 6/ Send a custom event
+ test.assertRaises(function () {
+ view.port.emit("event");
+ },
+ /The widget has been destroyed and can no longer be used./,
+ "emit on a destroyed view should throw");
+ widget.port.emit("event");
+ });
+
+ // 5/ Destroy this window
+ window.close();
+ }
+ });
+ });
+
+ tests.push(function testAddonBarHide() {
+ // Hide the addon-bar
+ browserWindow.setToolbarVisibility(container(), false);
+
+ // Then open a browser window and verify that the addon-bar remains hidden
+ tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
+ let browserWindow = e.target.defaultView;
+ let doc = browserWindow.document;
+ function container2() doc.getElementById("addon-bar");
+ function widgetCount2() container2() ? container2().childNodes.length : 0;
+ let widgetStartCount2 = widgetCount2();
+
+ let w1Opts = {id:"first", label: "first widget", content: "first content"};
+ let w1 = testSingleWidget(w1Opts);
+ test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after widget creation");
+
+ w1.destroy();
+ test.assertEqual(widgetCount2(), widgetStartCount2, "2nd window has correct number of child elements after widget destroy");
+
+ test.assert(container().collapsed, "1st window has an hidden addon-bar");
+ test.assert(container2().collapsed, "2nd window has an hidden addon-bar");
+
+ browserWindow.setToolbarVisibility(container(), true);
+
+ closeBrowserWindow(browserWindow, function() {
+ doneTest();
+ });
+ }});
+ });
+
+ // test widget.width
+ tests.push(function testWidgetWidth() testSingleWidget({
+ id: "text",
+ label: "test widget.width",
+ content: "test width",
+ width: 200,
+ contentScript: "self.postMessage(1)",
+ contentScriptWhen: "ready",
+ onMessage: function(message) {
+ test.assertEqual(this.width, 200);
+
+ let node = widgetNode(0);
+ test.assertEqual(this.width, node.style.minWidth.replace("px", ""));
+ test.assertEqual(this.width, node.firstElementChild.style.width.replace("px", ""));
+ this.width = 300;
+ test.assertEqual(this.width, node.style.minWidth.replace("px", ""));
+ test.assertEqual(this.width, node.firstElementChild.style.width.replace("px", ""));
+
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // test click handler not respond to right-click
+ let clickCount = 0;
+ tests.push(function testNoRightClick() testSingleWidget({
+ id: "click-content",
+ label: "click test widget - content",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('MouseEvents'); " +
+ "evt.initMouseEvent('click', true, true, window, " +
+ " 0, 0, 0, 0, 0, false, false, false, false, 2, null); " +
+ "document.getElementById('me').dispatchEvent(evt); " +
+ "evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt); " +
+ "evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('mouseover', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onClick: function() clickCount++,
+ onMouseover: function() {
+ test.assertEqual(clickCount, 1, "right click wasn't sent to click handler");
+ this.destroy();
+ doneTest();
+ }
+ }));
+
+ // kick off test execution
+ doneTest();
+};
+
+exports.testPanelWidget1 = function testPanelWidget1(test) {
+ const widgets = require("widget");
+
+ let widget1 = widgets.Widget({
+ id: "panel1",
+ label: "panel widget 1",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.body.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ panel: require("panel").Panel({
+ contentURL: "data:text/html,<body>Look ma, a panel!</body>",
+ onShow: function() {
+ widget1.destroy();
+ test.pass("panel displayed on click");
+ test.done();
+ }
+ })
+ });
+ test.waitUntilDone();
+};
+
+exports.testPanelWidget2 = function testPanelWidget2(test) {
+ const widgets = require("widget");
+ test.assertRaises(
+ function() {
+ widgets.Widget({
+ id: "panel2",
+ label: "panel widget 2",
+ panel: {}
+ });
+ },
+ "The option \"panel\" must be one of the following types: null, undefined, object",
+ "widget.panel must be a Panel object"
+ );
+};
+
+exports.testPanelWidget3 = function testPanelWidget3(test) {
+ const widgets = require("widget");
+ let onClickCalled = false;
+ let widget3 = widgets.Widget({
+ id: "panel3",
+ label: "panel widget 3",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.body.firstElementChild.dispatchEvent(evt);",
+ contentScriptWhen: "end",
+ onClick: function() {
+ onClickCalled = true;
+ this.panel.show();
+ },
+ panel: require("panel").Panel({
+ contentURL: "data:text/html,<body>Look ma, a panel!</body>",
+ onShow: function() {
+ test.assert(
+ onClickCalled,
+ "onClick called on click for widget with both panel and onClick"
+ );
+ widget3.destroy();
+ test.done();
+ }
+ })
+ });
+ test.waitUntilDone();
+};
+
+exports.testWidgetMessaging = function testWidgetMessaging(test) {
+ test.waitUntilDone();
+ let origMessage = "foo";
+ const widgets = require("widget");
+ let widget = widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "<bar>baz</bar>",
+ contentScriptWhen: "end",
+ contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');",
+ onMessage: function(message) {
+ if (message == "ready")
+ widget.postMessage(origMessage);
+ else {
+ test.assertEqual(origMessage, message);
+ widget.destroy();
+ test.done();
+ }
+ }
+ });
+};
+
+exports.testWidgetViews = function testWidgetViews(test) {
+ test.waitUntilDone();
+ const widgets = require("widget");
+ let widget = widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "<bar>baz</bar>",
+ contentScriptWhen: "ready",
+ contentScript: "self.on('message', function(data) self.postMessage(data)); self.postMessage('ready')",
+ onAttach: function(view) {
+ test.pass("WidgetView created");
+ view.on("message", function () {
+ test.pass("Got message in WidgetView");
+ widget.destroy();
+ });
+ view.on("detach", function () {
+ test.pass("WidgetView destroyed");
+ test.done();
+ });
+ }
+ });
+
+};
+
+exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(test) {
+ test.waitUntilDone();
+ const widgets = require("widget");
+ let view = null;
+ let widget = widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "<div id='me'>foo</div>",
+ contentScript: "var evt = document.createEvent('HTMLEvents'); " +
+ "evt.initEvent('click', true, true ); " +
+ "document.getElementById('me').dispatchEvent(evt);",
+ contentScriptWhen: "ready",
+ onAttach: function(attachView) {
+ view = attachView;
+ test.pass("Got attach event");
+ },
+ onClick: function (eventView) {
+ test.assertEqual(view, eventView,
+ "event first argument is equal to the WidgetView");
+ let view2 = widget.getView(require("windows").browserWindows.activeWindow);
+ test.assertEqual(view, view2,
+ "widget.getView return the same WidgetView");
+ widget.destroy();
+ test.done();
+ }
+ });
+};
+
+exports.testWidgetViewsCustomEvents = function testWidgetViewsCustomEvents(test) {
+ test.waitUntilDone();
+ const widgets = require("widget");
+ let widget = widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "<div id='me'>foo</div>",
+ contentScript: "self.port.emit('event', 'ok');",
+ contentScriptWhen: "ready",
+ onAttach: function(view) {
+ view.port.on("event", function (data) {
+ test.assertEqual(data, "ok",
+ "event argument is valid on WidgetView");
+ });
+ },
+ });
+ widget.port.on("event", function (data) {
+ test.assertEqual(data, "ok",
+ "event argument is valid on Widget");
+ widget.destroy();
+ test.done();
+ });
+};
+
+exports.testWidgetViewsTooltip = function testWidgetViewsTooltip(test) {
+ test.waitUntilDone();
+ const widgets = require("widget");
+
+ let widget = new widgets.Widget({
+ id: "foo",
+ label: "foo",
+ content: "foo"
+ });
+ let view = widget.getView(require("windows").browserWindows.activeWindow);
+ widget.tooltip = null;
+ test.assertEqual(view.tooltip, "foo",
+ "view tooltip defaults to base widget label");
+ test.assertEqual(widget.tooltip, "foo",
+ "tooltip defaults to base widget label");
+ widget.destroy();
+ test.done();
+};
+
+exports.testWidgetMove = function testWidgetMove(test) {
+ test.waitUntilDone();
+
+ let windowUtils = require("window-utils");
+ let widgets = require("widget");
+
+ let browserWindow = windowUtils.activeBrowserWindow;
+ let doc = browserWindow.document;
+
+ let label = "unique-widget-label";
+ let origMessage = "message after node move";
+ let gotFirstReady = false;
+
+ let widget = widgets.Widget({
+ id: "foo",
+ label: label,
+ content: "<bar>baz</bar>",
+ contentScriptWhen: "ready",
+ contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');",
+ onMessage: function(message) {
+ if (message == "ready") {
+ if (!gotFirstReady) {
+ test.pass("Got first ready event");
+ let widgetNode = doc.querySelector('toolbaritem[label="' + label + '"]');
+ let parent = widgetNode.parentNode;
+ parent.insertBefore(widgetNode, parent.firstChild);
+ gotFirstReady = true;
+ } else {
+ test.pass("Got second ready event");
+ widget.postMessage(origMessage);
+ }
+ }
+ else {
+ test.assertEqual(origMessage, message, "Got message after node move");
+ widget.destroy();
+ test.done();
+ }
+ }
+ });
+};
+
+/*
+The bug is exhibited when a widget with HTML content has it's content
+changed to new HTML content with a pound in it. Because the src of HTML
+content is converted to a data URI, the underlying iframe doesn't
+consider the content change a navigation change, so doesn't load
+the new content.
+*/
+exports.testWidgetWithPound = function testWidgetWithPound(test) {
+ test.waitUntilDone();
+
+ function getWidgetContent(widget) {
+ let windowUtils = require("window-utils");
+ let browserWindow = windowUtils.activeBrowserWindow;
+ let doc = browserWindow.document;
+ let widgetNode = doc.querySelector('toolbaritem[label="' + widget.label + '"]');
+ test.assert(widgetNode, 'found widget node in the front-end');
+ return widgetNode.firstChild.contentDocument.body.innerHTML;
+ }
+
+ let widgets = require("widget");
+ let count = 0;
+ let widget = widgets.Widget({
+ id: "1",
+ label: "foo",
+ content: "foo",
+ contentScript: "window.addEventListener('load', self.postMessage, false);",
+ onMessage: function() {
+ count++;
+ if (count == 1) {
+ widget.content = "foo#";
+ }
+ else {
+ test.assertEqual(getWidgetContent(widget), "foo#", "content updated to pound?");
+ widget.destroy();
+ test.done();
+ }
+ }
+ });
+};
+
+/******************* helpers *********************/
+
+// Helper for calling code at window close
+function closeBrowserWindow(window, callback) {
+ require("timer").setTimeout(function() {
+ window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload, false);
+ callback();
+ }, false);
+ window.close();
+ }, 0);
+}
+
+// ADD NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ require("widget");
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("context-menu does not support this application.");
+ };
+}
+
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-windows.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-windows.js
new file mode 100644
index 0000000..e2824f8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-windows.js
@@ -0,0 +1,342 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Felipe Gomes <felipc@gmail.com> (Original author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {Cc, Ci} = require("chrome");
+const { setTimeout } = require("timer");
+const { Loader } = require('./helpers');
+const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+let browserWindows;
+
+function getTestRunnerWindow() wm.getMostRecentWindow("test:runner")
+
+exports.testOpenAndCloseWindow = function(test) {
+ test.waitUntilDone();
+
+ test.assertEqual(browserWindows.length, 1, "Only one window open");
+
+ browserWindows.open({
+ url: "data:text/html,<title>windows API test</title>",
+ onOpen: function(window) {
+ test.assertEqual(this, browserWindows,
+ "The 'this' object is the windows object.");
+ test.assertEqual(window.tabs.length, 1, "Only one tab open");
+ test.assertEqual(browserWindows.length, 2, "Two windows open");
+ window.tabs.activeTab.on('ready', function onReady(tab) {
+ tab.removeListener('ready', onReady);
+ test.assert(window.title.indexOf("windows API test") != -1,
+ "URL correctly loaded");
+ window.close();
+ });
+ },
+ onClose: function(window) {
+ test.assertEqual(window.tabs.length, 0, "Tabs were cleared");
+ test.assertEqual(browserWindows.length, 1, "Only one window open");
+ test.done();
+ }
+ });
+};
+
+exports.testAutomaticDestroy = function(test) {
+
+ test.waitUntilDone();
+ let windows = browserWindows;
+
+ // Create a second windows instance that we will unload
+ let called = false;
+ let loader = Loader(module);
+ let windows2 = loader.require("windows").browserWindows;
+ windows2.on("open", function() {
+ called = true;
+ });
+
+ loader.unload();
+
+ // Fire a windows event and check that this unloaded instance is inactive
+ windows.open({
+ url: "data:text/html,foo",
+ onOpen: function(window) {
+ setTimeout(function () {
+ test.assert(!called,
+ "Unloaded windows instance is destroyed and inactive");
+ window.close(function () {
+ test.done();
+ });
+ });
+ }
+ });
+
+};
+
+exports.testOnOpenOnCloseListeners = function(test) {
+ test.waitUntilDone();
+ let windows = browserWindows;
+
+ test.assertEqual(browserWindows.length, 1, "Only one window open");
+
+ let received = {
+ listener1: false,
+ listener2: false,
+ listener3: false,
+ listener4: false
+ }
+
+ function listener1() {
+ test.assertEqual(this, windows, "The 'this' object is the windows object.");
+ if (received.listener1)
+ test.fail("Event received twice");
+ received.listener1 = true;
+ }
+
+ function listener2() {
+ if (received.listener2)
+ test.fail("Event received twice");
+ received.listener2 = true;
+ }
+
+ function listener3() {
+ test.assertEqual(this, windows, "The 'this' object is the windows object.");
+ if (received.listener3)
+ test.fail("Event received twice");
+ received.listener3 = true;
+ }
+
+ function listener4() {
+ if (received.listener4)
+ test.fail("Event received twice");
+ received.listener4 = true;
+ }
+
+ windows.on('open', listener1);
+ windows.on('open', listener2);
+ windows.on('close', listener3);
+ windows.on('close', listener4);
+
+ function verify() {
+ test.assert(received.listener1, "onOpen handler called");
+ test.assert(received.listener2, "onOpen handler called");
+ test.assert(received.listener3, "onClose handler called");
+ test.assert(received.listener4, "onClose handler called");
+
+ windows.removeListener('open', listener1);
+ windows.removeListener('open', listener2);
+ windows.removeListener('close', listener3);
+ windows.removeListener('close', listener4);
+ test.done();
+ }
+
+
+ windows.open({
+ url: "data:text/html,foo",
+ onOpen: function(window) {
+ window.close(verify);
+ }
+ });
+};
+
+exports.testWindowTabsObject = function(test) {
+ test.waitUntilDone();
+
+ browserWindows.open({
+ url: "data:text/html,<title>tab 1</title>",
+ onOpen: function onOpen(window) {
+ test.assertEqual(window.tabs.length, 1, "Only 1 tab open");
+
+ window.tabs.open({
+ url: "data:text/html,<title>tab 2</title>",
+ inBackground: true,
+ onReady: function onReady(newTab) {
+ test.assertEqual(window.tabs.length, 2, "New tab open");
+ test.assertEqual(newTab.title, "tab 2", "Correct new tab title");
+ test.assertEqual(window.tabs.activeTab.title, "tab 1", "Correct active tab");
+
+ let i = 1;
+ for each (let tab in window.tabs)
+ test.assertEqual(tab.title, "tab " + i++, "Correct title");
+
+ window.close();
+ }
+ });
+ },
+ onClose: function onClose(window) {
+ test.assertEqual(window.tabs.length, 0, "No more tabs on closed window");
+ test.done();
+ }
+ });
+};
+
+exports.testActiveWindow = function(test) {
+ const xulApp = require("xul-app");
+ if (xulApp.versionInRange(xulApp.platformVersion, "1.9.2", "1.9.2.*")) {
+ test.pass("This test is disabled on 3.6. For more information, see bug 598525");
+ return;
+ }
+
+ let windows = browserWindows;
+
+ // API window objects
+ let window2, window3;
+
+ // Raw window objects
+ let nonBrowserWindow = getTestRunnerWindow(), rawWindow2, rawWindow3;
+
+ test.waitUntilDone();
+
+ let testSteps = [
+ function() {
+ test.assertEqual(windows.length, 3, "Correct number of browser windows");
+ let count = 0;
+ for (let window in windows)
+ count++;
+ test.assertEqual(count, 3, "Correct number of windows returned by iterator");
+
+ rawWindow2.focus();
+ continueAfterFocus(rawWindow2);
+ },
+ function() {
+ nonBrowserWindow.focus();
+ continueAfterFocus(nonBrowserWindow);
+ },
+ function() {
+ /**
+ * Bug 614079: This test fails intermittently on some specific linux
+ * environnements, without being able to reproduce it in same
+ * distribution with same window manager.
+ * Disable it until being able to reproduce it easily.
+
+ // On linux, focus is not consistent, so we can't be sure
+ // what window will be on top.
+ // Here when we focus "non-browser" window,
+ // Any Browser window may be selected as "active".
+ test.assert(windows.activeWindow == window2 || windows.activeWindow == window3,
+ "Non-browser windows aren't handled by this module");
+ */
+ window2.activate();
+ continueAfterFocus(rawWindow2);
+ },
+ function() {
+ test.assertEqual(windows.activeWindow.title, window2.title, "Correct active window - 2");
+ window3.activate();
+ continueAfterFocus(rawWindow3);
+ },
+ function() {
+ test.assertEqual(windows.activeWindow.title, window3.title, "Correct active window - 3");
+ nonBrowserWindow.focus();
+ finishTest();
+ }
+ ];
+
+ windows.open({
+ url: "data:text/html,<title>window 2</title>",
+ onOpen: function(window) {
+ window2 = window;
+ rawWindow2 = wm.getMostRecentWindow("navigator:browser");
+
+ windows.open({
+ url: "data:text/html,<title>window 3</title>",
+ onOpen: function(window) {
+ window.tabs.activeTab.on('ready', function onReady() {
+ window3 = window;
+ rawWindow3 = wm.getMostRecentWindow("navigator:browser");
+ nextStep()
+ });
+ }
+ });
+ }
+ });
+
+ function nextStep() {
+ if (testSteps.length > 0)
+ testSteps.shift()();
+ }
+
+ function continueAfterFocus(targetWindow) {
+
+ // Based on SimpleTest.waitForFocus
+ var fm = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+
+ var childTargetWindow = {};
+ fm.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
+ childTargetWindow = childTargetWindow.value;
+
+ var focusedChildWindow = {};
+ if (fm.activeWindow) {
+ fm.getFocusedElementForWindow(fm.activeWindow, true, focusedChildWindow);
+ focusedChildWindow = focusedChildWindow.value;
+ }
+
+ var focused = (focusedChildWindow == childTargetWindow);
+ if (focused) {
+ nextStep();
+ } else {
+ childTargetWindow.addEventListener("focus", function focusListener() {
+ childTargetWindow.removeEventListener("focus", focusListener, true);
+ nextStep();
+ }, true);
+ }
+
+ }
+
+ function finishTest() {
+ window3.close(function() {
+ window2.close(function() {
+ test.done();
+ });
+ });
+ }
+};
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ browserWindows = require("windows").browserWindows;
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=571449";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("the windows module does not support this application.");
+ };
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/README.md b/tools/addon-sdk-1.4/packages/api-utils/README.md
new file mode 100644
index 0000000..e973e4c
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/README.md
@@ -0,0 +1,31 @@
+API Utils provides a basic CommonJS infrastructure for
+developing traditional XULRunner add-ons and applications. It is
+the basis for the Add-on SDK.
+
+To address issues present in traditional add-on development,
+API Utils provides mechanisms for:
+
+* writing and executing test cases, inspired by Python's [nose][]
+ package,
+* tracking JS objects of interest to aid in memory profiling and leak
+ detection,
+* registering callbacks that perform cleanup tasks when modules are
+ unloaded,
+* easily reporting errors with full stack tracebacks.
+
+API Utils also has the following characteristics:
+
+* Beautiful, concise documentation.
+* A rigorous test suite ensuring that the library doesn't break as the
+ Mozilla platform evolves.
+* Solid developer ergonomics ensuring that developers can easily find
+ out why something they're doing isn't working.
+
+API Utils is intended to be very small and only contain the bare
+minimum of functionality that all add-ons need.
+
+Note that the API Utils package has not fully stabilized yet, meaning that
+we do still expect to make incompatible changes to its APIs in future releases
+of the SDK.
+
+ [nose]: http://code.google.com/p/python-nose/
diff --git a/tools/addon-sdk-1.4/packages/api-utils/data/content-proxy.js b/tools/addon-sdk-1.4/packages/api-utils/data/content-proxy.js
new file mode 100644
index 0000000..6eadce9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/data/content-proxy.js
@@ -0,0 +1,834 @@
+"use strict";
+
+let Ci = Components.interfaces;
+
+/**
+ * Access key that allows privileged code to unwrap proxy wrappers through
+ * valueOf:
+ * let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY);
+ * This key should only be used by proxy unit test.
+ */
+ const UNWRAP_ACCESS_KEY = {};
+
+
+ /**
+ * Returns a closure that wraps arguments before calling the given function,
+ * which can be given to native functions that accept a function, such that when
+ * the closure is called, the given function is called with wrapped arguments.
+ *
+ * @param fun {Function}
+ * the function for which to create a closure wrapping its arguments
+ * @param obj {Object}
+ * target object from which `fun` comes from
+ * (optional, for debugging purpose)
+ * @param name {String}
+ * name of the attribute from which `fun` is binded on `obj`
+ * (optional, for debugging purpose)
+ *
+ * Example:
+ * function contentScriptListener(event) {}
+ * let wrapper = ContentScriptFunctionWrapper(contentScriptListener);
+ * xray.addEventListener("...", wrapper, false);
+ * -> Allow to `event` to be wrapped
+ */
+function ContentScriptFunctionWrapper(fun, obj, name) {
+ if ("___proxy" in fun && typeof fun.___proxy == "function")
+ return fun.___proxy;
+
+ let wrappedFun = function () {
+ let args = [];
+ for (let i = 0, l = arguments.length; i < l; i++)
+ args.push(wrap(arguments[i]));
+
+ //console.log("Called from native :"+obj+"."+name);
+ //console.log(">args "+arguments.length);
+ //console.log(fun);
+
+ // Native code can execute this callback with `this` being the wrapped
+ // function. For example, window.mozRequestAnimationFrame.
+ if (this == wrappedFun)
+ return fun.apply(fun, args);
+
+ return fun.apply(wrap(this), args);
+ };
+
+ Object.defineProperty(fun, "___proxy", {value : wrappedFun,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return wrappedFun;
+}
+
+/**
+ * Returns a closure that unwraps arguments before calling the `fun` function,
+ * which can be used to build a wrapper for a native function that accepts
+ * wrapped arguments, since native function only accept unwrapped arguments.
+ *
+ * @param fun {Function}
+ * the function to wrap
+ * @param originalObject {Object}
+ * target object from which `fun` comes from
+ * (optional, for debugging purpose)
+ * @param name {String}
+ * name of the attribute from which `fun` is binded on `originalObject`
+ * (optional, for debugging purpose)
+ *
+ * Example:
+ * wrapper.appendChild = NativeFunctionWrapper(xray.appendChild, xray);
+ * wrapper.appendChild(anotherWrapper);
+ * -> Allow to call xray.appendChild with unwrapped version of anotherWrapper
+ */
+function NativeFunctionWrapper(fun, originalObject, name) {
+ return function () {
+ let args = [];
+ let obj = this && typeof this.valueOf == "function" ?
+ this.valueOf(UNWRAP_ACCESS_KEY) : this;
+
+ for (let i = 0, l = arguments.length; i < l; i++)
+ args.push( unwrap(arguments[i], obj, name) );
+
+ //if (name != "toString")
+ //console.log(">>calling native ["+(name?name:'#closure#')+"]: \n"+fun.apply+"\n"+obj+"\n("+args.join(', ')+")\nthis :"+obj+"from:"+originalObject+"\n");
+
+ // Need to use Function.prototype.apply.apply because XMLHttpRequest
+ // is a function (typeof return 'function') and fun.apply is null :/
+ let unwrapResult = Function.prototype.apply.apply(fun, [obj, args]);
+ let result = wrap(unwrapResult, obj, name);
+
+ //console.log("<< "+rr+" -> "+r);
+
+ return result;
+ };
+}
+
+/*
+ * Unwrap a JS value that comes from the content script.
+ * Mainly converts proxy wrapper to XPCNativeWrapper.
+ */
+function unwrap(value, obj, name) {
+ //console.log("unwrap : "+value+" ("+name+")");
+ if (!value)
+ return value;
+ let type = typeof value;
+
+ // In case of proxy, unwrap them recursively
+ // (it should not be recursive, just in case of)
+ if (["object", "function"].indexOf(type) !== -1 &&
+ "__isWrappedProxy" in value) {
+ while("__isWrappedProxy" in value)
+ value = value.valueOf(UNWRAP_ACCESS_KEY);
+ return value;
+ }
+
+ // In case of functions we need to return a wrapper that converts native
+ // arguments applied to this function into proxies.
+ if (type == "function")
+ return ContentScriptFunctionWrapper(value, obj, name);
+
+ // We must wrap objects coming from content script too, as they may have
+ // a function that will be called by a native method.
+ // For example:
+ // addEventListener(..., { handleEvent: function(event) {} }, ...);
+ if (type == "object")
+ return ContentScriptObjectWrapper(value);
+
+ if (["string", "number", "boolean"].indexOf(type) !== -1)
+ return value;
+ //console.log("return non-wrapped to native : "+typeof value+" -- "+value);
+ return value;
+}
+
+/**
+ * Returns an XrayWrapper proxy object that allow to wrap any of its function
+ * though `ContentScriptFunctionWrapper`. These proxies are given to
+ * XrayWrappers in order to automatically wrap values when they call a method
+ * of these proxies. So that they are only used internaly and content script,
+ * nor web page have ever access to them. As a conclusion, we can consider
+ * this code as being safe regarding web pages overload.
+ *
+ *
+ * @param obj {Object}
+ * object coming from content script context to wrap
+ *
+ * Example:
+ * let myListener = { handleEvent: function (event) {} };
+ * node.addEventListener("click", myListener, false);
+ * `event` has to be wrapped, so handleEvent has to be wrapped using
+ * `ContentScriptFunctionWrapper` function.
+ * In order to do so, we build this new kind of proxies.
+ */
+function ContentScriptObjectWrapper(obj) {
+ if ("___proxy" in obj && typeof obj.___proxy == "object")
+ return obj.___proxy;
+
+ function valueOf(key) {
+ if (key === UNWRAP_ACCESS_KEY)
+ return obj;
+ return this;
+ }
+
+ let proxy = Proxy.create({
+ // Fundamental traps
+ getPropertyDescriptor: function(name) {
+ return Object.getOwnPropertyDescriptor(obj, name);
+ },
+ defineProperty: function(name, desc) {
+ return Object.defineProperty(obj, name, desc);
+ },
+ getOwnPropertyNames: function () {
+ return Object.getOwnPropertyNames(obj);
+ },
+ delete: function(name) {
+ return delete obj[name];
+ },
+ // derived traps
+ has: function(name) {
+ return name === "__isXrayWrapperProxy" ||
+ name in obj;
+ },
+ hasOwn: function(name) {
+ return Object.prototype.hasOwnProperty.call(obj, name);
+ },
+ get: function(receiver, name) {
+ if (name == "valueOf")
+ return valueOf;
+ let value = obj[name];
+ if (!value)
+ return value;
+
+ return unwrap(value);
+ },
+ set: function(receiver, name, val) {
+ obj[name] = val;
+ return true;
+ },
+ enumerate: function() {
+ var result = [];
+ for each (let name in obj) {
+ result.push(name);
+ };
+ return result;
+ },
+ keys: function() {
+ return Object.keys(obj);
+ }
+ });
+
+ Object.defineProperty(obj, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return proxy;
+}
+
+// List of all existing typed arrays.
+// Can be found here:
+// http://mxr.mozilla.org/mozilla-central/source/js/src/jsapi.cpp#1790
+const typedArraysCtor = [
+ ArrayBuffer,
+ Int8Array,
+ Uint8Array,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array,
+ Uint8ClampedArray
+];
+
+/*
+ * Wrap a JS value coming from the document by building a proxy wrapper.
+ */
+function wrap(value, obj, name, debug) {
+ if (!value)
+ return value;
+ let type = typeof value;
+ if (type == "object") {
+ // Bug 671016: Typed arrays don't need to be proxified.
+ // We avoid checking the whole constructor list on all objects
+ // by doing this check only on non-extensible objects:
+ if (!Object.isExtensible(value) &&
+ typedArraysCtor.indexOf(value.constructor) !== -1)
+ return value;
+
+ // We may have a XrayWrapper proxy.
+ // For example:
+ // let myListener = { handleEvent: function () {} };
+ // node.addEventListener("click", myListener, false);
+ // When native code want to call handleEvent,
+ // we go though ContentScriptFunctionWrapper that calls `wrap(this)`
+ // `this` is the XrayWrapper proxy of myListener.
+ // We return this object without building a CS proxy as it is already
+ // a value coming from the CS.
+ if ("__isXrayWrapperProxy" in value)
+ return value.valueOf(UNWRAP_ACCESS_KEY);
+
+ // Unwrap object before wrapping it.
+ // It should not happen with CS proxy objects.
+ while("__isWrappedProxy" in value) {
+ value = value.valueOf(UNWRAP_ACCESS_KEY);
+ }
+
+ if (XPCNativeWrapper.unwrap(value) !== value)
+ return getProxyForObject(value);
+ // In case of Event, HTMLCollection or NodeList or ???
+ // XPCNativeWrapper.unwrap(value) === value
+ // but it's still a XrayWrapper so let's build a proxy
+ return getProxyForObject(value);
+ }
+ if (type == "function") {
+ if (XPCNativeWrapper.unwrap(value) !== value
+ || (typeof value.toString === "function" &&
+ value.toString().match(/\[native code\]/))) {
+ return getProxyForFunction(value, NativeFunctionWrapper(value, obj, name));
+ }
+ return value;
+ }
+ if (type == "string")
+ return value;
+ if (type == "number")
+ return value;
+ if (type == "boolean")
+ return value;
+ //console.log("return non-wrapped to wrapped : "+value);
+ return value;
+}
+
+/*
+ * Wrap an object from the document to a proxy wrapper
+ */
+function getProxyForObject(obj) {
+ if (typeof obj != "object") {
+ let msg = "tried to proxify something other than an object: " + typeof obj;
+ console.warn(msg);
+ throw msg;
+ }
+ if ("__isWrappedProxy" in obj) {
+ return obj;
+ }
+ // Check if there is a proxy cached on this wrapper,
+ // but take care of prototype ___proxy attribute inheritance!
+ if (obj && obj.___proxy && obj.___proxy.valueOf(UNWRAP_ACCESS_KEY) === obj) {
+ return obj.___proxy;
+ }
+
+ let proxy = Proxy.create(handlerMaker(obj));
+
+ Object.defineProperty(obj, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+ return proxy;
+}
+
+/*
+ * Wrap a function from the document to a proxy wrapper
+ */
+function getProxyForFunction(fun, callTrap) {
+ if (typeof fun != "function") {
+ let msg = "tried to proxify something other than a function: " + typeof fun;
+ console.warn(msg);
+ throw msg;
+ }
+ if ("__isWrappedProxy" in fun)
+ return obj;
+ if ("___proxy" in fun)
+ return fun.___proxy;
+
+ let proxy = Proxy.createFunction(handlerMaker(fun), callTrap);
+
+ Object.defineProperty(fun, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return proxy;
+}
+
+/*
+ * Check if a DOM attribute name is an event name.
+ */
+function isEventName(id) {
+ if (id.indexOf("on") != 0 || id.length == 2)
+ return false;
+ // Taken from:
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#7616
+ switch (id[2]) {
+ case 'a' :
+ return (id == "onabort" ||
+ id == "onafterscriptexecute" ||
+ id == "onafterprint");
+ case 'b' :
+ return (id == "onbeforeunload" ||
+ id == "onbeforescriptexecute" ||
+ id == "onblur" ||
+ id == "onbeforeprint");
+ case 'c' :
+ return (id == "onchange" ||
+ id == "onclick" ||
+ id == "oncontextmenu" ||
+ id == "oncopy" ||
+ id == "oncut" ||
+ id == "oncanplay" ||
+ id == "oncanplaythrough");
+ case 'd' :
+ return (id == "ondblclick" ||
+ id == "ondrag" ||
+ id == "ondragend" ||
+ id == "ondragenter" ||
+ id == "ondragleave" ||
+ id == "ondragover" ||
+ id == "ondragstart" ||
+ id == "ondrop" ||
+ id == "ondurationchange");
+ case 'e' :
+ return (id == "onerror" ||
+ id == "onemptied" ||
+ id == "onended");
+ case 'f' :
+ return id == "onfocus";
+ case 'h' :
+ return id == "onhashchange";
+ case 'i' :
+ return (id == "oninput" ||
+ id == "oninvalid");
+ case 'k' :
+ return (id == "onkeydown" ||
+ id == "onkeypress" ||
+ id == "onkeyup");
+ case 'l' :
+ return (id == "onload" ||
+ id == "onloadeddata" ||
+ id == "onloadedmetadata" ||
+ id == "onloadstart");
+ case 'm' :
+ return (id == "onmousemove" ||
+ id == "onmouseout" ||
+ id == "onmouseover" ||
+ id == "onmouseup" ||
+ id == "onmousedown" ||
+ id == "onmessage");
+ case 'p' :
+ return (id == "onpaint" ||
+ id == "onpageshow" ||
+ id == "onpagehide" ||
+ id == "onpaste" ||
+ id == "onpopstate" ||
+ id == "onpause" ||
+ id == "onplay" ||
+ id == "onplaying" ||
+ id == "onprogress");
+ case 'r' :
+ return (id == "onreadystatechange" ||
+ id == "onreset" ||
+ id == "onresize" ||
+ id == "onratechange");
+ case 's' :
+ return (id == "onscroll" ||
+ id == "onselect" ||
+ id == "onsubmit" ||
+ id == "onseeked" ||
+ id == "onseeking" ||
+ id == "onstalled" ||
+ id == "onsuspend");
+ case 't':
+ return id == "ontimeupdate"
+ /*
+ // TODO: Make it work for mobile version
+ ||
+ (nsDOMTouchEvent::PrefEnabled() &&
+ (id == "ontouchstart" ||
+ id == "ontouchend" ||
+ id == "ontouchmove" ||
+ id == "ontouchenter" ||
+ id == "ontouchleave" ||
+ id == "ontouchcancel"))*/;
+
+ case 'u' :
+ return id == "onunload";
+ case 'v':
+ return id == "onvolumechange";
+ case 'w':
+ return id == "onwaiting";
+ }
+
+ return false;
+}
+
+// XrayWrappers miss some attributes.
+// Here is a list of functions that return a value when it detects a miss:
+const NODES_INDEXED_BY_NAME = ["IMG", "FORM", "APPLET", "EMBED", "OBJECT"];
+const xRayWrappersMissFixes = [
+
+ // Fix bug with XPCNativeWrapper on HTMLCollection
+ // We can only access array item once, then it's undefined :o
+ function (obj, name) {
+ let i = parseInt(name);
+ if (obj.toString().match(/HTMLCollection|NodeList/) &&
+ i >= 0 && i < obj.length) {
+ return wrap(XPCNativeWrapper(obj.wrappedJSObject[name]), obj, name);
+ }
+ return null;
+ },
+
+ // Trap access to document["form name"]
+ // that may refer to an existing form node
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9285
+ function (obj, name) {
+ if ("nodeType" in obj && obj.nodeType == 9) {
+ let node = obj.wrappedJSObject[name];
+ // List of supported tag:
+ // http://mxr.mozilla.org/mozilla-central/source/content/html/content/src/nsGenericHTMLElement.cpp#1267
+ if (node && NODES_INDEXED_BY_NAME.indexOf(node.tagName) != -1)
+ return wrap(XPCNativeWrapper(node));
+ }
+ return null;
+ },
+
+ // Trap access to window["frame name"] and window.frames[i]
+ // that refer to an (i)frame internal window object
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#6824
+ function (obj, name) {
+ if (typeof obj == "object" && "document" in obj) {
+ // Ensure that we are on a window object
+ try {
+ obj.QueryInterface(Ci.nsIDOMWindow);
+ }
+ catch(e) {
+ return null;
+ }
+
+ // Integer case:
+ let i = parseInt(name);
+ if (i >= 0 && i in obj) {
+ return wrap(XPCNativeWrapper(obj[i]));
+ }
+
+ // String name case:
+ if (name in obj.wrappedJSObject) {
+ let win = obj.wrappedJSObject[name];
+ let nodes = obj.document.getElementsByName(name);
+ for (let i = 0, l = nodes.length; i < l; i++) {
+ let node = nodes[i];
+ if ("contentWindow" in node && node.contentWindow == win)
+ return wrap(node.contentWindow);
+ }
+ }
+ }
+ return null;
+ },
+
+ // Trap access to form["node name"]
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9477
+ function (obj, name) {
+ if (typeof obj == "object" && obj.tagName == "FORM") {
+ let match = obj.wrappedJSObject[name];
+ let nodes = obj.ownerDocument.getElementsByName(name);
+ for (let i = 0, l = nodes.length; i < l; i++) {
+ let node = nodes[i];
+ if (node == match)
+ return wrap(node);
+ }
+ }
+ return null;
+ },
+
+ // Fix XPathResult's constants being undefined on XrayWrappers
+ // these constants are defined here:
+ // http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/xpath/nsIDOMXPathResult.idl
+ // and are only numbers.
+ // The platform bug 665279 was fixed in Gecko 10.0a1.
+ // FIXME: remove this workaround once the SDK no longer supports Firefox 9.
+ function (obj, name) {
+ if (typeof obj == "object" && name in Ci.nsIDOMXPathResult) {
+ let value = Ci.nsIDOMXPathResult[name];
+ if (typeof value == "number" && value === obj.wrappedJSObject[name])
+ return value;
+ }
+ return null;
+ }
+
+];
+
+// XrayWrappers have some buggy methods.
+// Here is the list of them with functions returning some replacement
+// for a given object `obj`:
+const xRayWrappersMethodsFixes = {
+ // postMessage method is checking the Javascript global
+ // and it expects it to be a window object.
+ // But in our case, the global object is our sandbox global object.
+ // See nsGlobalWindow::CallerInnerWindow():
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsGlobalWindow.cpp#5893
+ // nsCOMPtr<nsPIDOMWindow> win = do_QueryWrappedNative(wrapper);
+ // win is null
+ postMessage: function (obj) {
+ // Ensure that we are on a window object
+ try {
+ obj.QueryInterface(Ci.nsIDOMWindow);
+ }
+ catch(e) {
+ return null;
+ }
+ // Create a wrapper that is going to call `postMessage` through `eval`
+ let f = function postMessage(message, targetOrigin) {
+ message = message.toString().replace(/['\\]/g,"\\$&");
+ targetOrigin = targetOrigin.toString().replace(/['\\]/g,"\\$&");
+
+ let jscode = "this.postMessage('" + message + "', '" +
+ targetOrigin + "')";
+ return this.wrappedJSObject.eval(jscode);
+ };
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+
+ // Fix mozMatchesSelector uses that is broken on XrayWrappers
+ // when we use document.documentElement.mozMatchesSelector.call(node, expr)
+ // It's only working if we call mozMatchesSelector on the node itself.
+ // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
+ mozMatchesSelector: function (obj) {
+ // Ensure that we are on an object to expose this buggy method
+ try {
+ // Bug 707576 removed nsIDOMNSElement.
+ // Can be simplified as soon as Firefox 11 become the minversion
+ obj.QueryInterface("nsIDOMElement" in Ci ? Ci.nsIDOMElement :
+ Ci.nsIDOMNSElement);
+ }
+ catch(e) {
+ return null;
+ }
+ // We can't use `wrap` function as `f` is not a native function,
+ // so wrap it manually:
+ let f = function mozMatchesSelector(selectors) {
+ return this.mozMatchesSelector(selectors);
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+
+ // Bug 679054: History API doesn't work with Proxy objects. We have to pass
+ // regular JS objects on `pushState` and `replaceState` methods.
+ // In addition, the first argument has to come from the same compartment.
+ pushState: function (obj) {
+ // Ensure that we are on an object that expose History API
+ try {
+ obj.QueryInterface(Ci.nsIDOMHistory);
+ }
+ catch(e) {
+ return null;
+ }
+ let f = function fix() {
+ // Call native method with JSON objects
+ // (need to convert `arguments` to an array via `slice`)
+ return this.pushState.apply(this, JSON.parse(JSON.stringify(Array.slice(arguments))));
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+ replaceState: function (obj) {
+ // Ensure that we are on an object that expose History API
+ try {
+ obj.QueryInterface(Ci.nsIDOMHistory);
+ }
+ catch(e) {
+ return null;
+ }
+ let f = function fix() {
+ // Call native method with JSON objects
+ // (need to convert `arguments` to an array via `slice`)
+ return this.replaceState.apply(this, JSON.parse(JSON.stringify(Array.slice(arguments))));
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ }
+};
+
+/*
+ * Generate handler for proxy wrapper
+ */
+function handlerMaker(obj) {
+ // Overloaded attributes dictionary
+ let overload = {};
+ // Expando attributes dictionary (i.e. onclick, onfocus, on* ...)
+ let expando = {};
+ // Cache of methods overloaded to fix XrayWrapper bug
+ let methodFixes = {};
+ return {
+ // Fundamental traps
+ getPropertyDescriptor: function(name) {
+ return Object.getOwnPropertyDescriptor(obj, name);
+ },
+ defineProperty: function(name, desc) {
+ return Object.defineProperty(obj, name, desc);
+ },
+ getOwnPropertyNames: function () {
+ return Object.getOwnPropertyNames(obj);
+ },
+ delete: function(name) {
+ delete expando[name];
+ delete overload[name];
+ return delete obj[name];
+ },
+
+ // derived traps
+ has: function(name) {
+ if (name == "___proxy") return false;
+ if (isEventName(name)) {
+ // XrayWrappers throw exception when we try to access expando attributes
+ // even on "name in wrapper". So avoid doing it!
+ return name in expando;
+ }
+ return name in obj || name in overload || name == "__isWrappedProxy" ||
+ undefined !== this.get(null, name);
+ },
+ hasOwn: function(name) {
+ return Object.prototype.hasOwnProperty.call(obj, name);
+ },
+ get: function(receiver, name) {
+ if (name == "___proxy")
+ return undefined;
+
+ // Overload toString in order to avoid returning "[XrayWrapper [object HTMLElement]]"
+ // or "[object Function]" for function's Proxy
+ if (name == "toString") {
+ if ("wrappedJSObject" in obj) {
+ // Bug 714778: we should not pass obj.wrappedJSObject.toString
+ // in order to avoid sharing its proxy over contents scripts:
+ return wrap(function () {
+ return obj.wrappedJSObject.toString.call(
+ this.valueOf(UNWRAP_ACCESS_KEY), arguments);
+ }, obj, name);
+ }
+ else {
+ return wrap(obj.toString, obj, name);
+ }
+ }
+
+ // Offer a way to retrieve XrayWrapper from a proxified node through `valueOf`
+ if (name == "valueOf")
+ return function (key) {
+ if (key === UNWRAP_ACCESS_KEY)
+ return obj;
+ return this;
+ };
+
+ // Return overloaded value if there is one.
+ // It allows to overload native methods like addEventListener that
+ // are not saved, even on the wrapper itself.
+ // (And avoid some methods like toSource from being returned here! [__proto__ test])
+ if (name in overload &&
+ overload[name] != Object.getPrototypeOf(overload)[name] &&
+ name != "__proto__") {
+ return overload[name];
+ }
+
+ // Catch exceptions thrown by XrayWrappers when we try to access on*
+ // attributes like onclick, onfocus, ...
+ if (isEventName(name)) {
+ //console.log("expando:"+obj+" - "+obj.nodeType);
+ return name in expando ? expando[name].original : undefined;
+ }
+
+ // Overload some XrayWrappers method in order to fix its bugs
+ if (name in methodFixes &&
+ methodFixes[name] != Object.getPrototypeOf(methodFixes)[name] &&
+ name != "__proto__")
+ return methodFixes[name];
+ if (Object.keys(xRayWrappersMethodsFixes).indexOf(name) !== -1) {
+ let fix = xRayWrappersMethodsFixes[name](obj);
+ if (fix)
+ return methodFixes[name] = fix;
+ }
+
+ let o = obj[name];
+
+ // XrayWrapper miss some attributes, try to catch these and return a value
+ if (!o) {
+ for each(let atttributeFixer in xRayWrappersMissFixes) {
+ let fix = atttributeFixer(obj, name);
+ if (fix)
+ return fix;
+ }
+ }
+
+ // Generic case
+ return wrap(o, obj, name);
+
+ },
+
+ set: function(receiver, name, val) {
+
+ if (isEventName(name)) {
+ //console.log("SET on* attribute : " + name + " / " + val + "/" + obj);
+ let shortName = name.replace(/^on/,"");
+
+ // Unregister previously set listener
+ if (expando[name]) {
+ obj.removeEventListener(shortName, expando[name], true);
+ delete expando[name];
+ }
+
+ // Only accept functions
+ if (typeof val != "function")
+ return false;
+
+ // Register a new listener
+ let original = val;
+ val = ContentScriptFunctionWrapper(val);
+ expando[name] = val;
+ val.original = original;
+ obj.addEventListener(name.replace(/^on/, ""), val, true);
+ return true;
+ }
+
+ obj[name] = val;
+
+ // Handle native method not overloaded on XrayWrappers:
+ // obj.addEventListener = val; -> obj.addEventlistener = native method
+ // And, XPCNativeWrapper bug where nested values appear to be wrapped:
+ // obj.customNestedAttribute = val -> obj.customNestedAttribute !== val
+ // obj.customNestedAttribute = "waive wrapper something"
+ // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
+ // TODO: check that DOM can't be updated by the document itself and so overloaded value becomes wrong
+ // but I think such behavior is limited to primitive type
+ if ((typeof val == "function" || typeof val == "object") && name) {
+ overload[name] = val;
+ }
+
+ return true;
+ },
+
+ enumerate: function() {
+ var result = [];
+ for each (let name in Object.keys(obj)) {
+ result.push(name);
+ };
+ return result;
+ },
+
+ keys: function() {
+ return Object.keys(obj);
+ }
+ };
+};
+
+
+/*
+ * Wrap an object from the document to a proxy wrapper.
+ */
+function create(object) {
+ if ("wrappedJSObject" in object)
+ object = object.wrappedJSObject;
+ let xpcWrapper = XPCNativeWrapper(object);
+ // If we can't build an XPCNativeWrapper, it doesn't make sense to build
+ // a proxy. All proxy code is based on having such wrapper that store
+ // different JS attributes set.
+ // (we can't build XPCNativeWrapper when object is from the same
+ // principal/domain)
+ if (object === xpcWrapper) {
+ return object;
+ }
+ return getProxyForObject(xpcWrapper);
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/data/test-content-symbiont.js b/tools/addon-sdk-1.4/packages/api-utils/data/test-content-symbiont.js
new file mode 100644
index 0000000..808af37
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/data/test-content-symbiont.js
@@ -0,0 +1 @@
+// test-content-symbiont
diff --git a/tools/addon-sdk-1.4/packages/api-utils/data/test-httpd.txt b/tools/addon-sdk-1.4/packages/api-utils/data/test-httpd.txt
new file mode 100644
index 0000000..7956d3a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/data/test-httpd.txt
@@ -0,0 +1 @@
+This is the HTTPD test file.
diff --git a/tools/addon-sdk-1.4/packages/api-utils/data/test-trusted-document.html b/tools/addon-sdk-1.4/packages/api-utils/data/test-trusted-document.html
new file mode 100644
index 0000000..166009d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/data/test-trusted-document.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+ <title>Worker test</title>
+</head>
+<body>
+ <p id="paragraph">Lorem ipsum dolor sit amet.</p>
+ <script>
+ addon.port.on('addon-to-document', function (arg) {
+ addon.port.emit('document-to-addon', arg);
+ });
+ </script>
+</body>
+</html>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/api-utils.md b/tools/addon-sdk-1.4/packages/api-utils/docs/api-utils.md
new file mode 100644
index 0000000..d87acdb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/api-utils.md
@@ -0,0 +1,153 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `api-utils` module provides some helpers useful to the SDK's high-level API
+implementations.
+
+Introduction
+------------
+
+The SDK high-level API design guidelines make a number of recommendations.
+This module implements some of those patterns so that your own implementations
+don't need to reinvent them.
+
+For example, public constructors should be callable both with and without the
+`new` keyword. Your module can implement this recommendation using the
+`publicConstructor` function.
+
+Options objects or "dictionaries" are also common throughout the high-level
+APIs. The guidelines recommend that public constructors should generally define
+a single `options` parameter rather than defining many parameters. Since one of
+the SDK's principles is to be friendly to developers, ideally all properties on
+options dictionaries should be checked for correct type, and informative error
+messages should be generated when clients make mistakes. With the
+`validateOptions` function, your module can easily do so.
+
+And objects are sometimes iterable over a custom set of key/value pairs.
+Such objects should have custom iterators that let consumers iterate keys,
+values, or [key, value] pairs. The `addIterator` function makes it easy to do
+so in a way that is consistent with the behavior of default iterators during
+`for...in`, `for each...in`, and `for...in Iterator()` loops.
+
+<api name="publicConstructor">
+@function
+Returns a function *C* that creates an instance of `privateConstructor`. *C*
+may be called with or without the `new` keyword.
+
+The prototype of each instance returned from *C* is *C*.`prototype`, and
+*C*.`prototype` is an object whose prototype is
+`privateConstructor.prototype`. Instances returned from *C* are therefore
+instances of both *C* and `privateConstructor`.
+
+Additionally, the constructor of each instance returned from *C* is *C*.
+
+Instances returned from *C* are automatically memory tracked using
+`memory.track` under the bin name `privateConstructor.name`.
+
+**Example**
+
+ function MyObject() {}
+ exports.MyObject = apiUtils.publicConstructor(MyObject);
+
+@returns {function}
+A function that makes new instances of `privateConstructor`.
+
+@param privateConstructor {constructor}
+</api>
+
+<api name="validateOptions">
+@function
+A function to validate an options dictionary according to the specified
+constraints.
+
+`map`, `is`, and `ok` are used in that order.
+
+The return value is 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.
+
+**Examples**
+
+A typical use:
+
+ var opts = { foo: 1337 };
+ var requirements = {
+ foo: {
+ map: function (val) val.toString(),
+ is: ["string"],
+ ok: function (val) val.length > 0,
+ msg: "foo must be a non-empty string."
+ }
+ };
+ var validatedOpts = apiUtils.validateOptions(opts, requirements);
+ // validatedOpts == { foo: "1337" }
+
+If the key `foo` is optional and doesn't need to be mapped:
+
+ var opts = { foo: 1337 };
+ var validatedOpts = apiUtils.validateOptions(opts, { foo: {} });
+ // validatedOpts == { foo: 1337 }
+
+ opts = {};
+ validatedOpts = apiUtils.validateOptions(opts, { foo: {} });
+ // validatedOpts == {}
+
+@returns {object}
+A validated options dictionary given some requirements. If any of the
+requirements are not met, an exception is thrown.
+
+@param options {object}
+The options dictionary to validate. It's not modified. If it's null or
+otherwise falsey, an empty object is assumed.
+
+@param requirements {object}
+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. The keys of that object are the following, and each is optional:
+
+@prop [map] {function}
+A function that's passed the value of the key in the `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 is caught and discarded,
+and the key's value is its value in `options`.
+
+@prop [is] {array}
+An array containing the number of `typeof` type names. If the key's value is
+none of these types it fails validation. Arrays and nulls are identified by
+the special type names "array" and "null"; "object" will not match either.
+No type coercion is done.
+
+@prop [ok] {function}
+A function that is passed the key's value. If it returns false, the value
+fails validation.
+
+@prop [msg] {string}
+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.
+</api>
+
+<api name="addIterator">
+@function
+Adds an iterator to the specified object that iterates keys, values,
+or [key, value] pairs depending on how it is invoked, i.e.:
+
+ for (var key in obj) { ... } // iterate keys
+ for each (var val in obj) { ... } // iterate values
+ for (var [key, val] in Iterator(obj)) { ... } // iterate pairs
+
+If your object only iterates either keys or values, you don't need this
+function. Simply assign a generator function that iterates the keys/values
+to your object's `__iterator__` property instead, f.e.:
+
+ obj.__iterator__ = function () { for each (var i in items) yield i; }
+
+@param obj {object}
+the object to which to add the iterator
+
+@param keysValsGen {function}
+a generator function that yields [key, value] pairs
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/app-strings.md b/tools/addon-sdk-1.4/packages/api-utils/docs/app-strings.md
new file mode 100644
index 0000000..3e4d358
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/app-strings.md
@@ -0,0 +1,61 @@
+The `app-strings` module gives you access to the host application's localized
+string bundles (`.properties` files).
+
+The module exports the `StringBundle` constructor function. To access a string
+bundle, construct an instance of `StringBundle`, passing it the bundle's URL:
+
+ var StringBundle = require("app-strings").StringBundle;
+ var bundle = StringBundle("chrome://browser/locale/browser.properties");
+
+To get the value of a string, call the object's `get` method, passing it
+the name of the string:
+
+ var accessKey = bundle.get("contextMenuSearchText.accesskey");
+ // "S" in the en-US locale
+
+To get the formatted value of a string that accepts arguments, call the object's
+`get` method, passing it the name of the string and an array of arguments
+with which to format the string:
+
+ var searchText = bundle.get("contextMenuSearchText",
+ ["universe", "signs of intelligent life"]);
+ // 'Search universe for "signs of intelligent life"' in the en-US locale
+
+To get all strings in the bundle, iterate the object, which returns arrays
+of the form [name, value]:
+
+ for (var [name, value] in Iterator(bundle))
+ console.log(name + " = " + value);
+
+Iteration
+---------
+
+<code>for (var name in bundle) { ... }</code>
+
+Iterate the names of strings in the bundle.
+
+<code>for each (var val in bundle) { ... }</code>
+
+Iterate the values of strings in the bundle.
+
+<code>for (var [name, value] in Iterator(bundle)) { ... }</code>
+
+Iterate the names and values of strings in the bundle.
+
+
+<api name="StringBundle">
+@class
+The `StringBundle` object represents a string bundle.
+<api name="StringBundle">
+@constructor
+Creates a StringBundle object that gives you access to a string bundle.
+@param url {string} the URL of the string bundle
+@returns {StringBundle} the string bundle
+</api>
+<api name="get">
+@method Get the value of the string with the given name.
+@param [name] {string} the name of the string to get
+@param [args] {array} (optional) strings that replace placeholders in the string
+@returns {string} the value of the string
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/byte-streams.md b/tools/addon-sdk-1.4/packages/api-utils/docs/byte-streams.md
new file mode 100644
index 0000000..f7cffe8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/byte-streams.md
@@ -0,0 +1,64 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `byte-streams` module provides streams for reading and writing bytes.
+
+<api name="ByteReader">
+@class
+<api name="ByteReader">
+@constructor
+ Creates a binary input stream that reads bytes from a backing stream.
+@param inputStream {stream}
+ The backing stream, an <a href="http://mxr.mozilla.org/mozilla-central/
+source/xpcom/io/nsIInputStream.idl"><code>nsIInputStream</code></a>.
+</api>
+<api name="closed">
+@property {boolean}
+ True if the stream is closed.
+</api>
+
+<api name="close">
+@method
+ Closes both the stream and its backing stream. If the stream is already
+ closed, an exception is thrown.
+</api>
+
+<api name="read">
+@method
+ Reads a string from the stream. If the stream is closed, an exception is
+ thrown.
+@param [numBytes] {number}
+ The number of bytes to read. If not given, the remainder of the entire stream
+ is read.
+@returns {string}
+ A string containing the bytes read. If the stream is at the end, returns the
+ empty string.
+</api>
+</api>
+
+<api name="ByteWriter">
+@class
+<api name="ByteWriter">
+@constructor
+ Creates a binary output stream that writes bytes to a backing stream.
+@param outputStream {stream}
+ The backing stream, an <a href="http://mxr.mozilla.org/mozilla-central/
+source/xpcom/io/nsIOutputStream.idl"><code>nsIOutputStream</code></a>.
+</api>
+<api name="closed">
+@property {boolean}
+ True if the stream is closed.
+</api>
+<api name="close">
+@method
+ Closes both the stream and its backing stream. If the stream is already
+ closed, an exception is thrown.
+</api>
+<api name="write">
+@method
+ Writes a string to the stream. If the stream is closed, an exception is
+ thrown.
+@param str {string}
+ The string to write.
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/collection.md b/tools/addon-sdk-1.4/packages/api-utils/docs/collection.md
new file mode 100644
index 0000000..796289a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/collection.md
@@ -0,0 +1,73 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `collection` module provides a simple list-like class and utilities for
+using it. A collection is ordered, like an array, but its items are unique,
+like a set.
+
+<api name="Collection">
+@class
+A collection object provides for...in-loop iteration. Items are yielded in the
+order they were added. For example, the following code...
+
+ var collection = require("collection");
+ var c = new collection.Collection();
+ c.add(1);
+ c.add(2);
+ c.add(3);
+ for (item in c)
+ console.log(item);
+
+... would print this to the console:
+
+<pre>
+ 1
+ 2
+ 3
+</pre>
+
+Iteration proceeds over a copy of the collection made before iteration begins,
+so it is safe to mutate the collection during iteration; doing so does not
+affect the results of the iteration.
+
+<api name="Collection">
+@constructor
+Creates a new collection. The collection is backed by an array.
+@param [array] {array}
+If *array* is given, it will be used as the backing array. This way the caller
+can fully control the collection. Otherwise a new empty array will be used, and
+no one but the collection will have access to it.
+</api>
+<api name="length">
+@property {number}
+The number of items in the collection array.
+</api>
+<api name="add">
+@method
+Adds a single item or an array of items to the collection. Any items already
+contained in the collection are ignored.
+@param itemOrItems {object} An item or array of items.
+@returns {Collection} The Collection.
+</api>
+<api name="remove">
+@method
+Removes a single item or an array of items from the collection. Any items not
+contained in the collection are ignored.
+@param itemOrItems {object} An item or array of items.
+@returns {Collection} The Collection.
+</api>
+</api>
+
+<api name="addCollectionProperty">
+@function
+Adds a collection property to the given object. Setting the property to a
+scalar value empties the collection and adds the value. Setting it to an array
+empties the collection and adds all the items in the array.
+@param object {object}
+The property will be defined on this object.
+@param propName {string}
+The name of the property.
+@param [backingArray] {array}
+If given, this will be used as the collection's backing array.
+</api>
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/content.md b/tools/addon-sdk-1.4/packages/api-utils/docs/content.md
new file mode 100644
index 0000000..b258d02
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/content.md
@@ -0,0 +1,11 @@
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `content` module exports three different traits [Loader][], [Worker][] and
+[Symbiont][]. None of this traits is intended to be used directly by programs.
+Rather, they are intended to be used by other modules that provide high
+level APIs to programs or libraries.
+
+[Loader]:packages/api-utils/docs/content/loader.html
+[Worker]:packages/api-utils/docs/content/worker.html
+[Symbiont]:packages/api-utils/docs/content/symbiont.html
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/content/loader.md b/tools/addon-sdk-1.4/packages/api-utils/docs/content/loader.md
new file mode 100644
index 0000000..83b6276
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/content/loader.md
@@ -0,0 +1,88 @@
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+Loader is base trait and it provides set of core properties and associated
+validations. Trait is useful for all the compositions providing high level
+APIs for creating JavaScript contexts that can access web content.
+
+Loader is composed from the
+[EventEmitter](packages/api-utils/docs/events.html) trait, therefore
+instances of Loader and their descendants expose all the public properties
+exposed by EventEmitter along with additional public properties:
+
+Value changes on all of the above mentioned properties emit `propertyChange`
+events on an instances.
+
+**Example:**
+
+The following code creates a wrapper on hidden frame that reloads a web page
+in frame every time `contentURL` property is changed:
+
+ var hiddenFrames = require("hidden-frame");
+ var { Loader } = require("content");
+ var PageLoader = Loader.compose({
+ constructor: function PageLoader(options) {
+ options = options || {};
+ if (options.contentURL)
+ this.contentURL = options.contentURL;
+ this.on('propertyChange', this._onChange = this._onChange.bind(this));
+ let self = this;
+ hiddenFrames.add(hiddenFrames.HiddenFrame({
+ onReady: function onReady() {
+ let frame = self._frame = this.element;
+ self._emit('propertyChange', { contentURL: self.contentURL });
+ }
+ }));
+ },
+ _onChange: function _onChange(e) {
+ if ('contentURL' in e)
+ this._frame.setAttribute('src', this._contentURL);
+ }
+ });
+
+<api name="Loader">
+@class
+<api name="contentScriptFile">
+@property {array}
+The local file URLs of content scripts to load. Content scripts specified by
+this property are loaded *before* those specified by the `contentScript`
+property.
+</api>
+
+<api name="contentScript">
+@property {array}
+The texts of content scripts to load. Content scripts specified by this
+property are loaded *after* those specified by the `contentScriptFile` property.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+When to load the content scripts. This may take one of the following
+values:
+
+* "start": load content scripts immediately after the document
+element for the page is inserted into the DOM, but before the DOM content
+itself has been loaded
+* "ready": load content scripts once DOM content has been loaded,
+corresponding to the
+[DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+event
+* "end": load content scripts once all the content (DOM, JS, CSS,
+images) for the page has been loaded, at the time the
+[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+fires
+
+</api>
+
+<api name="contentURL">
+@property {string}
+The URL of the content loaded.
+</api>
+
+<api name="allow">
+@property {object}
+Permissions for the content, with the following keys:
+@prop script {boolean}
+ Whether or not to execute script in the content. Defaults to true.
+</api>
+</api>
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/content/proxy.md b/tools/addon-sdk-1.4/packages/api-utils/docs/content/proxy.md
new file mode 100644
index 0000000..012fbc7
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/content/proxy.md
@@ -0,0 +1,237 @@
+<!-- contributed by Alexandre Poirot [apoirot@mozilla.com] -->
+
+Content scripts need access to the DOM of the pages they are attached to.
+However, those pages should be considered to be hostile environments: we
+have no control over any other scripts loaded by the web page that may be
+executing in the same context. If the content scripts and scripts loaded
+by the web page were to access the same DOM objects, there are two possible
+security problems:
+
+First, a malicious page might redefine functions and properties of DOM
+objects so they don't do what the add-on expects. For example, if a
+content script calls `document.getElementById()` to retrieve a DOM
+element, then a malicious page could redefine its behavior to return
+something unexpected:
+
+<pre><code>
+// If the web document contains the following script:
+document.getElementById = function (str) {
+ // Overload indexOf method of all string instances
+ str.__proto__.indexOf = function () {return -1;};
+ // Overload toString method of all object instances
+ str.__proto__.__proto__.toString = function () {return "evil";};
+};
+// After the following line, the content script will be compromised:
+var node = document.getElementById("element");
+// Then your content script is totally out of control.
+</code></pre>
+
+Second, changes the content script made to the DOM objects would be visible
+to the page, leaking information to it.
+
+The general approach to fixing these problems is to wrap DOM objects in
+[`XrayWrappers`](https://developer.mozilla.org/en/XPCNativeWrapper)
+(also know as `XPCNativeWrapper`). This guarantees that:
+
+* when the content script accesses DOM properties and functions it gets the
+original native version of them, ignoring any modifications made by the web
+page
+* changes to the DOM made by the content script are not visible to scripts
+running in the page.
+
+However, `XrayWrapper` has some limitations and bugs, which break many
+popular web frameworks. In particular, you can't:
+
+* define attributes like `onclick`: you have to use `addEventListener` syntax
+* overload native methods on DOM objects, like this:
+<pre><code>
+proxy.addEventListener = function () {};
+</code></pre>
+* access named elements using properties like `window[framename]` or
+`document[formname]`
+* use some other features that have bugs in the `XrayWrapper`
+implementation, like `mozMatchesSelector`
+
+The `proxy` module uses `XrayWrapper` in combination with the
+experimental
+[Proxy API](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Proxy)
+to address both the security vulnerabilities of content scripts and the
+limitations of `XrayWrapper`.
+
+<pre>
+ /--------------------\ /------------------------\
+ | Web document | | Content script sandbox |
+ | http://mozilla.org | | data/worker.js |
+ | | require('content-proxy'). | |
+ | window >-----------|- create(window) -|-> window |
+ \--------------------/ \------------------------/
+</pre>
+
+
+## The Big Picture ##
+
+The implementation defines two different kinds of proxy:
+
+ 1. Content script proxies that wrap DOM objects that are exposed to
+ content scripts as described above.
+ 2. XrayWrapper proxies that wrap objects from content scripts before handing
+ them over to XrayWrapper functions. These proxies are internal
+ and are not exposed to content scripts or document content.
+
+<pre>
+ /--------------------\ /------------------------\
+ | Web document | | Content script sandbox |
+ | http://mozilla.org | | data/worker.js |
+ | | /-------|-> myObject = {} |
+ | | /----------------v--\ | |
+ | | | XrayWrapper Proxy | | - document |
+ | | \---------v---------/ \----^-------------------/
+ | | v |
+ | | /-------------\ /----------\ |
+ | - document >-------|->| XrayWrapper |<-| CS proxy |-/
+ \--------------------/ \-------------/ \----------/
+</pre>
+
+Everything begins with a single call to the `create` function exported by the
+content-proxy module:
+
+ // Retrieve the unwrapped reference to the current web page window object
+ var win = gBrowser.contentDocument.defaultView.wrappedJSObject;
+ // Or in addon sdk style
+ var win = require("tab-browser").activeTab.linkedBrowser.contentWindow.wrappedJSObject;
+ // Now create a content script proxy for the window object
+ var windowProxy = require("api-utils/content/content-proxy").create(win);
+
+ // We finally use this window object as sandbox prototype,
+ // so that all web page globals are accessible in CS too:
+ var contentScriptSandbox = new Cu.Sandbox(win, {
+ sandboxPrototype: windowProxy
+ });
+
+Then all other proxies are created from this one. Attempts to access DOM
+attributes of this proxy are trapped, and the proxy constructs and returns
+content script proxies for those attributes:
+
+ // For example, if you simply do this:
+ var document = window.document;
+ // accessing the `document` attribute will be trapped by the `window` content script
+ // proxy, and that proxy will that create another content script proxy for `document`
+
+So the main responsibility of the content script proxy implementation is to
+ensure that we always return content script proxies to the content script.
+
+## Internal Implementation ##
+
+Each content script proxy keeps a reference to the `XrayWrapper` that enables
+it to be sure of calling native DOM methods.
+
+There are two internal functions to convert between content script proxy
+values and `XrayWrapper` values.
+
+1. __`wrap`__ takes an XrayWrapper value and wraps it in a content script
+proxy if needed.
+ This method is called when:
+ * a content script accesses an attribute of a content script proxy.
+ * XrayWrapper code calls a callback function defined in the content
+script, so that arguments passed into the function by the XrayWrapper are
+converted into content script proxies. For example, if a content script
+calls `addEventListener`, then the listener function will expect any arguments
+to be content script proxies.
+<br/><br/>
+2. __`unwrap`__ takes an object coming from the content script context and:
+ * if the object is a content script proxy, unwraps it back to an
+XrayWrapper reference
+ * if the object is not a content script proxy, wraps it in an XrayWrapper
+proxy.
+<br/><br/>
+This means we can call a XrayWrapper method either with:
+
+ * a raw XrayWrapper object.
+
+ // The following line doesn't work if child is a content script proxy,
+ // it has to be a raw XrayWrapper reference
+ xrayWrapper.appendChild(child)
+
+ * an XrayWrapper proxy when you pass a custom object from the content
+script context.
+
+ var myListener = {
+ handleEvent: function(event) {
+ // `event` should be a content script proxy
+ }
+ };
+ // `myListener` has to be another kind of Proxy: XrayWrapper proxy,
+ // that aims to catch the call to `handleEvent` in order to wrap its
+ // arguments in a content script proxy.
+ xrayWrapper.addEventListener("click", myListener, false);
+
+
+## Stack Traces ##
+
+The following code:
+
+ function listener(event) {
+
+ }
+ csProxy.addEventListener("message", listener, false);
+
+generates the following internal calls:
+
+ -> CS Proxy:: get("addEventListener")
+ -> wrap(xrayWrapper.addEventListener)
+ -> NativeFunctionWrapper(xrayWrapper.addEventListener)
+ // NativeFunctionWrapper generates:
+ function ("message", listener, false) {
+ return xraywrapper.addEventListener("message", unwrap(listener), false);
+ }
+ -> unwrap(listener)
+ -> ContentScriptFunctionWrapper(listener)
+ // ContentScriptFunctionWrapper generates:
+ function (event) {
+ return listener(wrap(event));
+ }
+
+<br>
+
+ // First, create an object from content script context
+ var myListener = {
+ handleEvent: function (event) {
+
+ }
+ };
+ // Then, pass this object as an argument to a CS proxy method
+ window.addEventListener("message", myListener, false);
+
+ // Generates the following internal calls:
+ -> CS Proxy:: get("addEventListener")
+ -> wrap(xrayWrapper.addEventListener)
+ -> NativeFunctionWrapper(xrayWrapper.addEventListener)
+ // Generate the following function:
+ function ("message", myListener, false) {
+ return xraywrapper.addEventListener("message", unwrap(myListener), false);
+ }
+ -> unwrap(myListener)
+ -> ContentScriptObjectWrapper(myListener)
+ // Generate an XrayWrapper proxy and give it to xrayWrapper method.
+ // Then when native code fires an event, the proxy will catch it:
+ -> XrayWrapper Proxy:: get("handleEvent")
+ -> unwrap(myListener.handleEvent)
+ -> ContentScriptFunctionWrapper(myListener.handleEvent)
+ // Generate following function:
+ function (event) {
+ return myListener.handleEvent(wrap(event));
+ }
+
+
+<api name="create">
+@function
+ Create a content script proxy. <br/>
+ Doesn't create a proxy if we are not able to create a XrayWrapper for
+ this object: for example, if the object comes from system principal.
+
+@param object {Object}
+ The object to proxify.
+
+@returns {Object}
+ A content script proxy that wraps `object`.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/content/symbiont.md b/tools/addon-sdk-1.4/packages/api-utils/docs/content/symbiont.md
new file mode 100644
index 0000000..7519e52
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/content/symbiont.md
@@ -0,0 +1,136 @@
+<!-- contributed by Myk Melez [myk@mozilla.org] -->
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+
+This module is not intended to be used directly by programs. Rather, it is
+intended to be used by other modules that provide APIs to programs.
+
+
+This module exports `Symbiont` trait that can be used for creating JavaScript
+contexts that can access web content in host application frames (i.e. XUL
+`<iframe>` and `<browser>` elements) and communicate with programs via
+asynchronous JSON pipes. It is useful in the construction of APIs that
+are compatible with the execution model codenamed "electrolysis" in which
+programs run in separate processes from web content.
+
+Introduction
+------------
+
+`Symbiont` constructs a content symbiont for a given frame, it loads the
+specified contentURL and scripts into it, and plumbs an asynchronous
+JSON pipe between the content symbiont object and the content symbiont
+context. If frame is not provided hidden frame will be created.
+
+Examples
+--------
+
+ var { Symbiont } = require('content');
+ var Thing = Symbiont.resolve({ constructor: '_init' }).compose({
+ constructor: function Thing(options) {
+ // `getMyFrame` returns the host application frame in which
+ // the page is loaded.
+ this._frame = getMyFrame();
+ this._init(options)
+ }
+ });
+
+See the [panel][] module for a real-world example of usage of this module.
+
+[panel]:packages/addon-kit/docs/panel.html
+
+Reference
+---------
+
+<api name="Symbiont">
+@class
+Symbiont is composed from the [Worker][] trait, therefore instances
+of Symbiont and their descendants expose all the public properties
+exposed by [Worker][] along with additional public properties that
+are listed below:
+
+[Worker]:packages/api-utils/docs/content/worker.html
+
+<api name="Symbiont">
+@constructor
+Creates a content symbiont.
+@param options {object}
+ Options for the constructor. Includes all the keys that
+the [Worker](packages/api-utils/docs/content/worker.html)
+constructor accepts and a few more:
+
+ @prop [frame] {object}
+ The host application frame in which the page is loaded.
+ If frame is not provided hidden one will be created.
+ @prop [contentScriptWhen="end"] {string}
+ When to load the content scripts. This may take one of the following
+ values:
+
+ * "start": load content scripts immediately after the document
+ element for the page is inserted into the DOM, but before the DOM content
+ itself has been loaded
+ * "ready": load content scripts once DOM content has been loaded,
+ corresponding to the
+ [DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+ event
+ * "end": load content scripts once all the content (DOM, JS, CSS,
+ images) for the page has been loaded, at the time the
+ [window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+ fires
+
+ This property is optional and defaults to "end".
+
+ @prop [allow] {object}
+ Permissions for the content, with the following keys:
+ @prop [script] {boolean}
+ Whether or not to execute script in the content. Defaults to true.
+ Optional.
+ Optional.
+</api>
+
+<api name="contentScriptFile">
+@property {array}
+The local file URLs of content scripts to load. Content scripts specified by
+this property are loaded *before* those specified by the `contentScript`
+property.
+</api>
+
+<api name="contentScript">
+@property {array}
+The texts of content scripts to load. Content scripts specified by this
+property are loaded *after* those specified by the `contentScriptFile` property.
+</api>
+
+<api name="contentScriptWhen">
+@property {string}
+When to load the content scripts. This may have one of the following
+values:
+
+* "start": load content scripts immediately after the document
+element for the page is inserted into the DOM, but before the DOM content
+itself has been loaded
+* "ready": load content scripts once DOM content has been loaded,
+corresponding to the
+[DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
+event
+* "end": load content scripts once all the content (DOM, JS, CSS,
+images) for the page has been loaded, at the time the
+[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
+fires
+
+</api>
+
+<api name="contentURL">
+@property {string}
+The URL of the content loaded.
+</api>
+
+<api name="allow">
+@property {object}
+Permissions for the content, with a single boolean key called `script` which
+defaults to true and indicates whether or not to execute scripts in the
+content.
+</api>
+
+</api>
+
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/content/worker.md b/tools/addon-sdk-1.4/packages/api-utils/docs/content/worker.md
new file mode 100644
index 0000000..ec9bb3a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/content/worker.md
@@ -0,0 +1,126 @@
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+This module exports the `Worker` trait, which may be used to construct objects
+implementing the [Worker][] interface defined by the W3C, with minor
+differences.
+
+Content workers are message-passing facilities for communication between
+[content scripts](dev-guide/addon-development/web-content.html) and the main
+add-on code.
+
+It is important to note that unlike "web workers," these workers run in the
+same process as web content and browser chrome, so code within workers can
+block the UI.
+
+[Worker]:http://www.w3.org/TR/workers/#worker
+
+<api name="Worker">
+@class
+Worker is composed from the [EventEmitter][] trait, therefore instances
+of Worker and their descendants expose all the public properties
+exposed by [EventEmitter][] along with additional public properties that
+are listed below.
+
+**Example**
+
+ var workers = require("content/worker");
+ let worker = workers.Worker({
+ window: require("window-utils").activeWindow,
+ contentScript:
+ "self.port.on('hello', function(name) { " +
+ " self.port.emit('response', window.location); " +
+ "});"
+ });
+ worker.port.emit("hello", { name: "worker"});
+ worker.port.on("response", function (location) {
+ console.log(location);
+ });
+
+[EventEmitter]:packages/api-utils/docs/events.html
+
+<api name="Worker">
+@constructor
+Creates a content worker.
+@param options {object}
+Options for the constructor, with the following keys:
+ @prop window {object}
+ The content window to create JavaScript sandbox for communication with.
+ @prop [contentScriptFile] {string,array}
+ The local file URLs of content scripts to load. Content scripts specified
+ by this option are loaded *before* those specified by the `contentScript`
+ option. Optional.
+ @prop [contentScript] {string,array}
+ The texts of content scripts to load. Content scripts specified by this
+ option are loaded *after* those specified by the `contentScriptFile` option.
+ Optional.
+ @prop [onMessage] {function}
+ Functions that will registered as a listener to a 'message' events.
+ @prop [onError] {function}
+ Functions that will registered as a listener to an 'error' events.
+</api>
+
+<api name="port">
+@property {EventEmitter}
+[EventEmitter](packages/api-utils/docs/events.html) object that allows you to:
+
+* send customized messages to the worker using the `port.emit` function
+* receive events from the worker using the `port.on` function
+
+</api>
+
+<api name="postMessage">
+@method
+Asynchronously emits `"message"` events in the enclosed worker, where content
+script was loaded.
+@param data {number,string,JSON}
+The data to send. Must be stringifiable to JSON.
+</api>
+
+<api name="destroy">
+@method
+Destroy the worker by removing the content script from the page and removing
+all registered listeners. A `detach` event is fired just before removal.
+</api>
+
+<api name="url">
+@property {string}
+The URL of the content.
+</api>
+
+<api name="tab">
+@property {object}
+If this worker is attached to a content document, returns the related
+[tab](packages/addon-kit/docs/tabs.html).
+</api>
+
+<api name="message">
+@event
+This event allows the content worker to receive messages from its associated
+content scripts. Calling the `self.postMessage()` function from a content
+script will asynchronously emit the `message` event on the corresponding
+worker.
+
+@argument {value}
+The event listener is passed the message, which must be a
+<a href = "dev-guide/addon-development/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
+</api>
+
+<api name="error">
+@event
+This event allows the content worker to react to an uncaught runtime script
+error that occurs in one of the content scripts.
+
+@argument {Error}
+The event listener is passed a single argument which is an
+[Error](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error)
+object.
+</api>
+
+<api name="detach">
+@event
+This event is emitted when the document associated with this worker is unloaded
+or the worker's `destroy()` method is called.
+</api>
+
+</api>
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/cortex.md b/tools/addon-sdk-1.4/packages/api-utils/docs/cortex.md
new file mode 100644
index 0000000..87a8eda
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/cortex.md
@@ -0,0 +1,156 @@
+
+## Property Encapsulation ##
+
+In JavaScript it is not possible to create properties that have limited or
+controlled accessibility. It is possible to create non-enumerable and
+non-writable properties, but still they can be discovered and accessed.
+Usually so called "closure capturing" is used to encapsulate such properties
+in lexical scope:
+
+ function Foo() {
+ var _secret = 'secret';
+ this.hello = function hello() {
+ return 'Hello ' + _secret;
+ }
+ }
+
+This provides desired result, but has side effect of degrading code readability,
+especially with object-oriented programs. Another disadvantage with this pattern
+is that there is no immediate solution for inheriting access to the privates
+(illustrated by the following example):
+
+ function Derived() {
+ this.hello = function hello() {
+ return _secret;
+ }
+ this.bye = function bye() {
+ return _secret;
+ }
+ }
+ Derived.prototype = Object.create(Foo.prototype);
+
+## Facade Objects ##
+
+Alternatively constructor can returned facade objects - proxies to the
+instance's public properties:
+
+ function Foo() {
+ var foo = Object.create(Foo.prototype);
+ return {
+ bar: foo.hello.bind(foo);
+ }
+ }
+ Foo.prototype._secret = 'secret';
+ Foo.prototype.hello = function hello() {
+ return 'Hello ' + this._secret;
+ }
+
+ function Derived() {
+ var derived = Object.create(Derived.prototype);
+ return {
+ bar: derived.hello.bind(derived);
+ bye: derived.bye.bind(derived);
+ }
+ }
+ Derived.prototype = Object.create(Foo.prototype);
+ Derived.prototype.bye = function bye() {
+ return 'Bye ' + this._secret;
+ };
+
+While this solution solves given issue and provides proper encapsulation for
+both own and inherited private properties, it does not addresses following:
+
+ - Privates defined on the `prototype` can be compromised, since they are
+ accessible through the constructor (`Foo.prototype._secret`).
+ - Behavior of `instanceof` is broken, since `new Derived() instanceof Derived`
+ is going to evaluate to `false`.
+
+## Tamper Proofing with Property Descriptor Maps ##
+
+In ES5 new property descriptor maps were introduced, which can be used as a
+building blocks for defining reusable peace of functionality. To some degree
+they are similar to a `prototype` objects, and can be used so to define pieces
+of functionality that is considered to be private (In contrast to `prototype`
+they are not exposed by default).
+
+ function Foo() {
+ var foo = Object.create(Foo.prototype, FooDescriptor);
+ var facade = Object.create(Foo.prototype);
+ facade.hello = foo.hello.bind(foo);
+ return facade;
+ }
+ Foo.prototype.hello = function hello() {
+ return 'Hello ' + this._secret;
+ }
+ var FooDescriptor = {
+ _secret: { value: 'secret' };
+ }
+
+ function Derived() {
+ var derived = Object.create(Derived.prototype, DerivedDescriptor);
+ var facade = Object.create(Derived.prototype);
+ facade.hello = derived.hello.bind(derived);
+ facade.bye = derived.bye.bind(derived);
+ return facade;
+ }
+ Derived.prototype = Object.create(Foo.prototype);
+ Derived.prototype.bye = function bye() {
+ return 'Bye ' + this._secret;
+ };
+ DerivedDescriptor = {};
+
+ Object.keys(FooDescriptor).forEach(function(key) {
+ DerivedDescriptor[key] = FooDescriptor[key];
+ });
+
+## Cortex Objects ##
+
+Last approach solves all of the concerns, but adds complexity, verbosity
+and decreases code readability. Combination of `Cortex`'s and `Trait`'s
+will gracefully solve all these issues and keep code clean:
+
+ var Cortex = require('cortex').Cortex;
+ var Trait = require('light-traits').Trait;
+
+ var FooTrait = Trait({
+ _secret: 'secret',
+ hello: function hello() {
+ return 'Hello ' + this._secret;
+ }
+ });
+ function Foo() {
+ return Cortex(FooTrait.create(Foo.prototype));
+ }
+
+ var DerivedTrait = Trait.compose(FooTrait, Trait({
+ bye: function bye() {
+ return 'Bye ' + this._secret;
+ }
+ }));
+ function Derived() {
+ var derived = DerivedTrait.create(Derived.prototype);
+ return Cortex(derived);
+ }
+
+Function `Cortex` takes any object and returns a proxy for its public
+properties. By default properties are considered to be public if they don't
+start with `"_"`, but default behavior can be overridden if needed, by passing
+array of public property names as a second argument.
+
+## Gotchas ##
+
+`Cortex` is just a utility function to create a proxy object, and it does not
+solve the `prototype`-related issues highlighted earlier, but since traits make
+use of property descriptor maps instead of `prototype`s, there aren't any
+issues with using `Cortex` to wrap objects created from traits.
+
+If you want to use `Cortex` with an object that uses a `prototype` chain,
+however, you should either make sure you don't have any private properties
+in the prototype chain or pass the optional third `prototype` argument.
+
+In the latter case, the returned proxy will inherit from the given prototype,
+and the `prototype` chain of the wrapped object will be inaccessible.
+However, note that the behavior of the `instanceof` operator will vary,
+as `proxy instanceof Constructor` will return false even if the Constructor
+function's prototype is in the wrapped object's prototype chain.
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/cuddlefish.md b/tools/addon-sdk-1.4/packages/api-utils/docs/cuddlefish.md
new file mode 100644
index 0000000..6177c53
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/cuddlefish.md
@@ -0,0 +1,5 @@
+`cuddlefish` is the name of the SDK's module loader. It builds on
+`securable-module` to provide many SDK-specific globals such as `console`
+and `memory`.
+
+This module still needs to be documented.
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/environment.md b/tools/addon-sdk-1.4/packages/api-utils/docs/environment.md
new file mode 100644
index 0000000..5697a4f
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/environment.md
@@ -0,0 +1,39 @@
+Module provides API to access, set and unset environment variables via exported
+`env` object.
+
+ var { env } = require('api-utils/environment');
+
+You can get the value of an environment variable, by accessing property that
+has name of desired variable:
+
+ var PATH = env.PATH;
+
+You can check existence of an environment variable by checking if property with
+such variable name exists:
+
+ console.log('PATH' in env); // true
+ console.log('FOO' in env); // false
+
+You can set value of an environment variable by setting a property:
+
+ env.FOO = 'foo';
+ env.PATH += ':/my/path/'
+
+You can unset environment variable by deleting a property:
+
+ delete env.FOO;
+
+## Limitations ##
+
+There is no way to enumerate existing environment variables, also `env`
+won't have any enumerable properties:
+
+ console.log(Object.keys(env)); // []
+
+Environment variable will be unset, show up as non-existing if it's set
+to `null`, `undefined` or `''`.
+
+ env.FOO = null;
+ console.log('FOO' in env); // false
+ env.BAR = '';
+ console.log(env.BAR); // undefined
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/errors.md b/tools/addon-sdk-1.4/packages/api-utils/docs/errors.md
new file mode 100644
index 0000000..3ce5b19
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/errors.md
@@ -0,0 +1,38 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+
+The `errors` module provides helpers for safely invoking user callbacks.
+
+<api name="catchAndLog">
+@function
+ Wraps a callback in a function that when invoked will catch and log any
+ exception thrown by the callback.
+@param callback {function}
+ The callback to wrap.
+@param [defaultResponse] {value}
+ This value will be returned by the wrapper if `callback` throws an exception.
+ If not given, `undefined` is used.
+@param [logException] {function}
+ When `callback` throws an exception, it will be passed to this function. If
+ not given, the exception is logged using `console.exception()`.
+@returns {function}
+ A function that will invoke `callback` when called. The return value of this
+ function is the return value of `callback` unless `callback` throws an
+ exception. In that case, `defaultResponse` is returned or `undefined` if
+ `defaultResponse` is not given.
+</api>
+
+<api name="catchAndLogProps">
+@function
+ Replaces methods of an object with wrapped versions of those methods returned
+ by `catchAndLog()`.
+@param object {object}
+ The object whose methods to replace.
+@param props {string,array}
+ The names of the methods of `object` to replace, either a string for a single
+ method or an array of strings for multiple methods.
+@param [defaultResponse] {value}
+ This value will be returned by any wrapper whose wrapped method throws an
+ exception. If not given, `undefined` is used.
+@param [logException] {function}
+ See `catchAndLog()`.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/es5.md b/tools/addon-sdk-1.4/packages/api-utils/docs/es5.md
new file mode 100644
index 0000000..0906b0d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/es5.md
@@ -0,0 +1,52 @@
+The `es5` module provides shim layer to a versions of Firefox that do not yet
+implement certain EcmaScript 5 features.
+
+For more information on EcmaScript 5:
+
+* The new APIs are described in the official [ES5 specification][].
+* A good [introduction][] to the new APIs by John Resig.
+* A Google tech talk on [changes to JavaScript][].
+
+**There is no need to `require` this module** since it gets preloaded into
+all sandboxes automatically.
+
+Usage of new ES5 API's is encouraged, but since not everything can be
+provided to all the versions of firefox, there are few things to be aware of:
+
+`Object.freeze`, `Object.seal`, `Object.preventExtensions` does not really
+prevents any mutations. One thing it guarantees though, `Object.isFrozen`,
+`Object.isSealed`, `Object.isExtensible` checks will behave as defined in
+specification.
+
+`Object.defineProperty` is only partially compliant with the specification:
+
+- Non configurable properties will be created as configurable ones.
+
+- Instead of non-writable properties getters and setters will be defined,
+but `Object.getOwnPropertyDescriptor` will still behave as expected
+(will return property descriptor for non-writable property not a getter)
+
+- Defining properties using ES5 functions will break your
+ [custom iterators][] if you have any. Think twice before employing
+ custom iterators, because in most cases you can just make properties
+ non enumerable. If you really need to have a custom iterator, add it
+ after running ES5 functions and don't ignore previous iterators.
+ For example:
+
+ let object = Object.create({}, {
+ myField: { value: 6 }
+ });
+ object.__iterator__ = (function(original) {
+ return function myIterator() {
+ this.__iterator__ = original;
+ for (let key in this) {
+ // your logic here
+ }
+ this.__iterator__ = myIterator;
+ }
+ })(object.__iterator__);
+
+[custom iterators]:https://developer.mozilla.org/en/New_in_JavaScript_1.7#Iterators
+[ES5 specification]:http://www.ecmascript.org/docs/tc39-2009-043.pdf
+[introduction]:http://ejohn.org/blog/ecmascript-5-objects-and-properties/
+[changes to JavaScript]:http://www.youtube.com/watch?v=Kq4FpMe6cRs
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/events.md b/tools/addon-sdk-1.4/packages/api-utils/docs/events.md
new file mode 100644
index 0000000..cb74c77
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/events.md
@@ -0,0 +1,74 @@
+The `events` module provides base API for emitting events.
+
+This module is not intended to be used directly by programs. Rather, it is
+intended to be used by other modules that provide APIs to programs.
+
+<api name="EventEmitter">
+@class
+The EventEmitter is the base building block for all compositions that
+would need to broadcast data to multiple consumers.
+
+Please note that `EventEmitter` does not expose either a method for emitting
+events or a list of available event listeners as its public API. Obviously
+both are accessible but from the instance itself through the private API.
+<api name="EventEmitter">
+@constructor
+Creates an EventEmitter object.
+</api>
+
+<api name="on">
+@method
+Registers an event `listener` that will be called when events of
+specified `type` are emitted.
+
+If the `listener` is already registered for this `type`, a call to this
+method has no effect.
+
+If the event listener is being registered while an event is being processed,
+the event listener is not called during the current emit.
+
+**Example:**
+
+ // worker is instance of EventEmitter
+ worker.on('message', function (data) {
+ console.log('data received: ' + data)
+ });
+
+@param type {String}
+ The type of the event.
+@param listener {Function}
+ The listener function that processes the event.
+</api>
+
+<api name="once">
+@method
+Registers an event `listener` that will only be called once, the next time
+an event of the specified `type` is emitted.
+
+If the event listener is registered while an event of the specified `type`
+is being emitted, the event listener will not be called during the current
+emit.
+
+@param type {String}
+ The type of the event.
+@param listener {Function}
+ The listener function that processes the event.
+</api>
+
+<api name="removeListener">
+@method
+Unregisters an event `listener` for the specified event `type`.
+
+If the `listener` is not registered for this `type`, a call to this
+method has no effect.
+
+If an event listener is removed while an event is being processed, it is
+still triggered by the current emit. After it is removed, the event listener
+is never invoked again (unless registered again for future processing).
+
+@param type {String}
+ The type of the event.
+@param listener {Function}
+ The listener function that processes the event.
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/file.md b/tools/addon-sdk-1.4/packages/api-utils/docs/file.md
new file mode 100644
index 0000000..3dd4fb2
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/file.md
@@ -0,0 +1,147 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `file` module provides access to the local filesystem.
+
+
+Paths
+-----
+
+Path specifications in this API are platform-specific. This means that on
+Windows paths are specified using the backslash path separator (`\`), and on
+Unix-like systems like Linux and OS X paths are specified using the forward
+slash path separator (`/`).
+
+If your add-on uses literal Windows-style path specifications with this API,
+your add-on likely won't work when users run it on Unix-like systems. Likewise,
+if your add-on uses literal Unix-style path specifications, it won't work for
+users on Windows.
+
+To ensure your add-on works for everyone, generate paths using the
+[`join`](packages/api-utils/docs/file.html#join(...)) function. Unfortunately
+this API does not currently provide a way to obtain an absolute base path which
+you could then use with `join`. For now, you need to
+[`require("chrome")`](dev-guide/module-development/chrome.html) and use the
+XPCOM directory service as described at
+[MDN](https://developer.mozilla.org/en/Code_snippets/File_I%2F%2FO#Getting_special_files).
+
+Note that if you do decide to hardcode Windows-style paths, be sure to escape
+backslashes in strings. For example, to specify the file at `C:\Users\Myk`, you
+need to use the string `"C:\\Users\\Myk"`, not `"C:\Users\Myk"`. You can read
+more about escaping characters in strings at
+[MDN](https://developer.mozilla.org/en/JavaScript/Guide/Values,_Variables,_and_Literals#Escaping_Characters).
+
+
+<api name="basename">
+@function
+ Returns the last component of the given path. For example,
+ `basename("/foo/bar/baz")` returns `"baz"`. If the path has no components,
+ the empty string is returned.
+@param path {string}
+ The path of a file.
+@returns {string}
+ The last component of the given path.
+</api>
+
+<api name="dirname">
+@function
+ Returns the path of the directory containing the given file. If the file is
+ at the top of the volume, the empty string is returned.
+@param path {string}
+ The path of a file.
+@returns {string}
+ The path of the directory containing the file.
+</api>
+
+<api name="exists">
+@function
+ Returns true if a file exists at the given path and false otherwise.
+@param path {string}
+ The path of a file.
+@returns {boolean}
+ True if the file exists and false otherwise.
+</api>
+
+<api name="join">
+@function
+ Takes a variable number of strings, joins them on the file system's path
+ separator, and returns the result.
+@param ... {strings}
+ A variable number of strings to join. The first string must be an absolute
+ path.
+@returns {string}
+ A single string formed by joining the strings on the file system's path
+ separator.
+</api>
+
+<api name="list">
+@function
+ Returns an array of file names in the given directory.
+@param path {string}
+ The path of the directory.
+@returns {array}
+ An array of file names. Each is a basename, not a full path.
+</api>
+
+<api name="mkpath">
+@function
+ Makes a new directory named by the given path. Any subdirectories that do not
+ exist are also created. `mkpath` can be called multiple times on the same
+ path.
+@param path {string}
+ The path to create.
+</api>
+
+<api name="open">
+@function
+ Returns a stream providing access to the contents of a file.
+@param path {string}
+ The path of the file to open.
+@param [mode] {string}
+ An optional string, each character of which describes a characteristic of the
+ returned stream. If the string contains `"r"`, the file is opened in
+ read-only mode. `"w"` opens the file in write-only mode. `"b"` opens the
+ file in binary mode. If `"b"` is not present, the file is opened in text
+ mode, and its contents are assumed to be UTF-8. If *`mode`* is not given,
+ `"r"` is assumed, and the file is opened in read-only text mode.
+@returns {stream}
+ If the file is opened in text read-only `mode`, a `TextReader` is returned,
+ and if text write-only mode, a `TextWriter` is returned. See
+ [`text-streams`](packages/api-utils/docs/text-streams.html) for information on
+ these text stream objects. If the file is opened in binary read-only `mode`,
+ a `ByteReader` is returned, and if binary write-only mode, a `ByteWriter` is
+ returned. See
+ [`byte-streams`](packages/api-utils/docs/byte-streams.html) for more
+ information on these byte stream objects. Opened files should always be
+ closed after use by calling `close` on the returned stream.
+</api>
+
+<api name="read">
+@function
+ Opens a file and returns a string containing its entire contents.
+@param path {string}
+ The path of the file to read.
+@param [mode] {string}
+ An optional string, each character of which describes a characteristic of the
+ returned stream. If the string contains `"b"`, the contents will be returned
+ in binary mode. If `"b"` is not present or `mode` is not given, the file
+ contents will be returned in text mode.
+@returns {string}
+ A string containing the file's entire contents.
+</api>
+
+<api name="remove">
+@function
+ Removes a file from the file system. To remove directories, use `rmdir`.
+@param path {string}
+ The path of the file to remove.
+</api>
+
+<api name="rmdir">
+@function
+ Removes a directory from the file system. If the directory is not empty, an
+ exception is thrown.
+@param path {string}
+ The path of the directory to remove.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/hidden-frame.md b/tools/addon-sdk-1.4/packages/api-utils/docs/hidden-frame.md
new file mode 100644
index 0000000..47cd23b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/hidden-frame.md
@@ -0,0 +1,79 @@
+<!-- contributed by Myk Melez [myk@mozilla.org] -->
+
+The `hidden-frame` module creates host application frames (i.e. XUL `<iframe>`
+elements) that are not displayed to the user. It is useful in the construction
+of APIs that load web content not intended to be directly seen or accessed
+by users, like `page-worker`. It is also useful in the construction of APIs
+that load web content for intermittent display, such as `panel`.
+
+This module is not intended to be used directly by programs. Rather, it is
+intended to be used by other modules that provide APIs to programs.
+
+Introduction
+------------
+
+The module exports a constructor function, `HiddenFrame`, and two other
+functions, `add` and `remove`.
+
+`HiddenFrame` constructs a new hidden frame. `add` registers a hidden frame,
+preparing it to load content. `remove` unregisters a frame, unloading any
+content that was loaded in it.
+
+Examples
+--------
+
+The following code creates a hidden frame, loads a web page into it, and then
+logs its title:
+
+ var hiddenFrames = require("hidden-frame");
+ let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
+ onReady: function() {
+ this.element.contentWindow.location = "http://www.mozilla.org/";
+ let self = this;
+ this.element.addEventListener("DOMContentLoaded", function() {
+ console.log(self.element.contentDocument.title);
+ }, true, true);
+ }
+ }));
+
+See the `panel` module for a real-world example of usage of this module.
+
+Reference
+---------
+<api name="HiddenFrame">
+@class
+`HiddenFrame` objects represent hidden frames.
+<api name="HiddenFrame">
+@constructor
+Creates a hidden frame.
+@param options {object}
+ Options for the frame, with the following keys:
+ @prop onReady {function,array}
+ Functions to call when the frame is ready to load content. You must specify
+ an `onReady` callback and refrain from using the hidden frame until
+ the callback gets called, because hidden frames are not always ready to load
+ content the moment they are added.
+</api>
+
+<api name="add">
+@function
+Register a hidden frame, preparing it to load content.
+@param hiddenFrame {HiddenFrame} the frame to add
+</api>
+
+<api name="remove">
+@function
+Unregister a hidden frame, unloading any content that was loaded in it.
+@param hiddenFrame {HiddenFrame} the frame to remove
+</api>
+
+<api name="element">
+@property {DOMElement}
+The host application frame in which the page is loaded.
+</api>
+
+<api name="onReady">
+@property {array}
+Functions to call when the frame is ready to load content.
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/httpd.md b/tools/addon-sdk-1.4/packages/api-utils/docs/httpd.md
new file mode 100644
index 0000000..53964a3
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/httpd.md
@@ -0,0 +1,27 @@
+Provides an HTTP server written in JavaScript for the Mozilla platform, which
+can be used in unit tests.
+
+The most basic usage is:
+
+ var {startServerAsync} = require("httpd");
+ var srv = startServerAsync(port, basePath);
+ require("unload").when(function cleanup() {
+ srv.stop(function() { // you should continue execution from this point.
+ })
+ });
+
+This starts a server in background (assuming you're running this code in an
+application that has an event loop, such as Firefox). The server listens at
+http://localhost:port/ and serves files from the specified directory. You
+can serve static content or use SJS scripts, as described in documentation
+on developer.mozilla.org.
+
+You can also use `nsHttpServer` to start the server manually:
+
+ var {nsHttpServer} = require("httpd");
+ var srv = new nsHttpServer();
+ // further documentation on developer.mozilla.org
+
+See
+[HTTP server for unit tests](https://developer.mozilla.org/En/HTTP_server_for_unit_tests)
+for general information.
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/light-traits.md b/tools/addon-sdk-1.4/packages/api-utils/docs/light-traits.md
new file mode 100644
index 0000000..1257c2a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/light-traits.md
@@ -0,0 +1,291 @@
+
+[Traits](http://en.wikipedia.org/wiki/Trait_%28computer_science%29) are a simple
+mechanism for structuring object-oriented programs. They represent reusable and
+composable building blocks of functionality that factor out the common
+attributes and behavior of objects.
+
+They are a more robust alternative to
+[mixins](http://en.wikipedia.org/wiki/Mixins) and
+[multiple inheritance](http://en.wikipedia.org/wiki/Multiple_inheritance),
+because name clashes must be explicitly resolved and composition is commutative
+and associative (i.e. the order of traits in a composition is irrelevant).
+
+Use traits to share functionality between similar objects without duplicating
+code or creating complex inheritance chains.
+
+## Trait Creation ##
+
+To create a trait, call the `Trait` constructor function exported by this
+module, passing it a JavaScript object that specifies the properties of the
+trait.
+
+ let Trait = require('light-traits').Trait;
+ let t = Trait({
+ foo: "foo",
+ bar: function bar() {
+ return "Hi!"
+ },
+ baz: Trait.required
+ });
+
+Traits can both provide and require properties. A *provided* property is a
+property for which the trait itself provides a value. A *required* property is a
+property that the trait needs in order to function correctly but for which
+it doesn't provide a value.
+
+Required properties must be provided by another trait or by an object with a
+trait. Creation of an object with a trait will fail if required properties are
+not provided. Specify a required property by setting the value of the property
+to `Trait.required`.
+
+## Object Creation ##
+
+Create objects with a single trait by calling the trait's `create` method. The
+method takes a single argument, the object to serve as the new object's
+prototype. If no prototype is specified, the new object's prototype will be
+`Object.prototype`.
+
+ let t = Trait({
+ foo: 'foo',
+ bar: 2
+ });
+ let foo1 = t.create();
+ let foo2 = t.create({ name: 'Super' });
+
+## Trait Composition ##
+
+Traits are designed to be composed with other traits to create objects with the
+properties of multiple traits. To compose an object with multiple traits, you
+first create a composite trait and then use it to create the object. A composite
+trait is a trait that contains all of the properties of the traits from which it
+is composed. In the following example, MagnitudeTrait is a composite trait.
+
+ let EqualityTrait = Trait({
+ equal: Trait.required,
+ notEqual: function notEqual(x) {
+ return !this.equal(x)
+ }
+ });
+
+ let ComparisonTrait = Trait({
+ less: Trait.required,
+ notEqual: Trait.required,
+ greater: function greater(x) {
+ return !this.less(x) && this.notEqual(x)
+ }
+ });
+
+ let MagnitudeTrait = Trait.compose(EqualityTrait, ComparisonTrait);
+
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="-11 121 490 190" width="490px" height="190px">
+ <defs>
+ <marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="black">
+ <g>
+ <path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1px"/>
+ </g>
+ </marker>
+ </defs>
+ <g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
+ <g>
+ <rect x="9" y="165.33334" width="141" height="14"/>
+ <rect x="9" y="165.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(14 165.33334)" fill="black">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="47.373047">notEqual</tspan>
+ </text>
+ <rect x="9" y="151.33334" width="141" height="14"/>
+ <rect x="9" y="151.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(14 151.33334)" fill="red">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="29.361328">equal</tspan>
+ </text>
+ <rect x="9" y="137.33334" width="141" height="14"/>
+ <rect x="9" y="137.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(14 137.33334)" fill="black">
+ <tspan font-family="Helvetica" font-size="12" font-weight="bold" x="38.49707" y="11" textLength="54.00586">EqualityTrait</tspan>
+ </text>
+ <rect x="9" y="273" width="141" height="14"/>
+ <rect x="9" y="273" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(14 273)" fill="black">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="38.021484">greater</tspan>
+ </text>
+ <rect x="9" y="259" width="141" height="14"/>
+ <rect x="9" y="259" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(14 259)" fill="red">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="47.373047">notEqual</tspan>
+ </text>
+ <rect x="9" y="245" width="141" height="14"/>
+ <rect x="9" y="245" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(14 245)" fill="red">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="21.339844">less</tspan>
+ </text>
+ <rect x="9" y="231" width="141" height="14"/>
+ <rect x="9" y="231" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(14 231)" fill="black">
+ <tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".15332031" y="11" textLength="112.67578">ComparisonTrait</tspan>
+ </text>
+ <rect x="317.75" y="235.5" width="141" height="14"/>
+ <rect x="317.75" y="235.5" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(322.75 235.5)" fill="black">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="38.021484">greater</tspan>
+ </text>
+ <rect x="317.75" y="221.5" width="141" height="14"/>
+ <rect x="317.75" y="221.5" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(322.75 221.5)" fill="red">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="21.339844">less</tspan>
+ </text>
+ <rect x="317.75" y="207.5" width="141" height="14"/>
+ <rect x="317.75" y="207.5" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(322.75 207.5)" fill="black">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="47.373047">notEqual</tspan>
+ </text>
+ <rect x="317.75" y="193.5" width="141" height="14"/>
+ <rect x="317.75" y="193.5" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(322.75 193.5)" fill="red">
+ <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="29.361328">equal</tspan>
+ </text>
+ <rect x="317.75" y="179.5" width="141" height="14"/>
+ <rect x="317.75" y="179.5" width="141" height="14" stroke="black" stroke-width="1px"/>
+ <text transform="translate(322.75 179.5)" fill="black">
+ <tspan font-family="Helvetica" font-size="12" font-weight="bold" x="31.83789" y="11" textLength="67.32422">MagnitudeTrait</tspan>
+ </text>
+ <path d="M 150 248.83887 L 158.89999 248.83887 L 235.9 248.83887 L 235.9 224.66113 L 308.85 224.66113 L 310.85 224.66113" marker-end="url(#SharpArrow_Marker)" stroke="black" stroke-linecap="butt" stroke-linejoin="miter" stroke-width="1px"/>
+ <path d="M 150 171.15845 L 158.89999 171.15845 L 233.9 171.15845 L 233.9 201.6749 L 308.85 201.6749 L 310.85 201.6749" marker-end="url(#SharpArrow_Marker)" stroke="black" stroke-linecap="butt" stroke-linejoin="miter" stroke-width="1px"/>
+ </g>
+ </g>
+</svg>
+
+## Trait Resolution ##
+
+Composite traits have conflicts when two of the traits in the composition
+provide properties with the same name but different values (when compared using
+the `===` strict equality operator). In the following example, `TC` has a
+conflict because `T1` and `T2` both define a `foo` property:
+
+ let T1 = Trait({
+ foo: function () {
+ // do something
+ },
+ bar: 'bar',
+ t1: 1
+ });
+
+ let T2 = Trait({
+ foo: function() {
+ // do something else
+ },
+ bar: 'bar',
+ t2: 2
+ });
+
+ let TC = Trait.compose(T1, T2);
+
+Attempting to create an object from a composite trait with conflicts throws a
+`remaining conflicting property` exception. To create objects from such traits,
+you must resolve the conflict.
+
+You do so by excluding or renaming the conflicting property of one of the
+traits. Excluding a property removes it from the composition, so the composition
+only acquires the property from the other trait. Renaming a property gives it a
+new, non-conflicting name at which it can be accessed.
+
+In both cases, you call the `resolve` method on the trait whose property you
+want to exclude or rename, passing it an object. Each key in the object is the
+name of a conflicting property; each value is either `null` to exclude the
+property or a string representing the new name of the property.
+
+For example, the conflict in the previous example could be resolved by excluding
+the `foo` property of the second trait.
+
+ let TC = Trait(T1, T2.resolve({ foo: null }));
+
+It could also be resolved by renaming the `foo` property of the first trait to
+`foo2`:
+
+ let TC = Trait(T1.resolve({ foo: "foo2" }), T2);
+
+When you resolve a conflict, the same-named property of the other trait (the one
+that wasn't excluded or renamed) remains available in the composition under its
+original name.
+
+## Constructor Functions ##
+
+When your code is going to create more than one object with traits, you may want
+to define a constructor function to create them. To do so, create a composite
+trait representing the traits the created objects should have, then define a
+constructor function that creates objects with that trait and whose prototype is
+the prototype of the constructor:
+
+ let PointTrait = Trait.compose(T1, T2, T3);
+ function Point(options) {
+ let point = PointTrait.create(Point.prototype);
+ return point;
+ }
+
+## Property Descriptor Maps ##
+
+Traits are designed to work with the new object manipulation APIs defined in
+[ECMAScript-262, Edition
+5](http://www.ecma-international.org/publications/standards/Ecma-262.htm) (ES5).
+Traits are also property descriptor maps that inherit from `Trait.prototype` to
+expose methods for creating objects and resolving conflicts.
+
+The following trait definition:
+
+ let FooTrait = Trait({
+ foo: "foo",
+ bar: function bar() {
+ return "Hi!"
+ },
+ baz: Trait.required
+ });
+
+Creates the following property descriptor map:
+
+ {
+ foo: {
+ value: 'foo',
+ enumerable: true,
+ configurable: true,
+ writable: true
+ },
+
+ bar: {
+ value: function b() {
+ return 'bar'
+ },
+ enumerable: true,
+ configurable: true,
+ writable: true
+ },
+
+ baz: {
+ get baz() { throw new Error('Missing required property: `baz`') }
+ set baz() { throw new Error('Missing required property: `baz`') }
+ },
+
+ __proto__: Trait.prototype
+ }
+
+Since Traits are also property descriptor maps, they can be used with built-in
+`Object.*` methods that accept such maps:
+
+ Object.create(proto, FooTrait);
+ Object.defineProperties(myObject, FooTrait);
+
+Note that conflicting and required properties won't cause exceptions to be
+thrown when traits are used with the `Object.*` methods, since those methods are
+not aware of those constraints. However, such exceptions will be thrown when the
+property with the conflict or the required but missing property is accessed.
+
+Property descriptor maps can also be used in compositions. This may be useful
+for defining non-enumerable properties, for example:
+
+ let TC = Trait.compose(
+ Trait({ foo: 'foo' }),
+ { bar: { value: 'bar', enumerable: false } }
+ );
+
+_When using property descriptor maps in this way, make sure the map is not the
+only argument to `Trait.compose`, since in that case it will be interpreted as
+an object literal with properties to be defined._
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/list.md b/tools/addon-sdk-1.4/packages/api-utils/docs/list.md
new file mode 100644
index 0000000..8e1a522
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/list.md
@@ -0,0 +1,94 @@
+<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
+
+The `"list"` module provides base building blocks for composing lists.
+
+<api name="Iterable">
+@class
+Base trait that can be used to compose traits with non-standard
+enumeration behaviors.
+
+This trait is supposed to be used as part of a composition, since it only
+provides custom enumeration behavior to a composed object.
+It defines one required `_keyValueMap` property, that is used as a hash of
+"key-values" to iterate on during enumeration.
+
+<api name="Iterable">
+@constructor
+Constructs an `Iterable` object.
+</api>
+
+<api name="_keyValueMap">
+@property {Object}
+Hash map of key-values to iterate over. _Required_ property: that is, the
+property must be supplied by objects that compose this trait.
+_Note: That this property can be a getter if you need dynamic behavior._
+</api>
+
+</api>
+
+<api name="List">
+@class
+An ordered collection (also known as a sequence) disallowing duplicate
+elements. List is composed out of `Iterable`, therefore it provides custom
+enumeration behavior that is similar to array (enumerates only on the
+elements of the list).
+
+List is a base trait and is meant to be part of a
+composition, since all of its API is private except for the `length` property.
+
+**Examples:**
+
+ var MyList = List.compose({
+ add: function add(item1, item2, /*item3...*/) {
+ Array.slice(arguments).forEach(this._add.bind(this));
+ },
+ remove: function remove(item1, item2, /*item3...*/) {
+ Array.slice(arguments).forEach(this._remove.bind(this));
+ }
+ });
+ MyList('foo', 'bar', 'baz').length == 3; // true
+ new MyList('new', 'keyword').length == 2; // true
+ MyList.apply(null, [1, 2, 3]).length == 3; // true
+ let list = MyList();
+ list.length == 0; // true
+ list.add(1, 2, 3) == 3; // true
+
+<api name="List">
+@constructor
+Constructor can takes any number of elements and creates an instance of
+`List` populated with the specified elements.
+@param [element1] {Object|String|Number}
+@param [element2] {Object|String|Number}
+@param [...] {Object|String|Number}
+</api>
+
+<api name="length">
+@property {Number}
+Number of elements in this list.
+</api>
+
+<api name="_has">
+@method
+@param element {Object|Number|String}
+Returns `true` if this list contains the specified `element`.
+</api>
+<api name="_add">
+@method
+@param element {Object|Number|String}
+Appends the specified `element` to the end of this list, if it doesn't
+contain it.
+
+_Ignores the call if `element` is already contained._
+</api>
+<api name="_remove">
+@method
+@param element {Object|Number|String}
+Removes specified `element` from this list, if it contains it.
+
+_Ignores the call if `element` is not contained._
+</api>
+<api name="_clear">
+@method
+Removes all of the elements from this list.
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/match-pattern.md b/tools/addon-sdk-1.4/packages/api-utils/docs/match-pattern.md
new file mode 100644
index 0000000..125f89e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/match-pattern.md
@@ -0,0 +1,242 @@
+The `match-pattern` module can be used to test strings containing URLs
+against simple patterns.
+
+## Specifying Patterns ##
+
+There are three ways you can specify patterns:
+
+* as an exact match string
+* using a wildcard in a string
+* using a regular expression
+
+### Exact Matches ###
+
+**A URL** matches only that URL. The URL must start with a scheme, end with a
+slash, and contain no wildcards.
+
+<table>
+
+ <colgroup>
+ <col width="30%">
+ <col width="35%">
+ <col width="35%">
+ </colgroup>
+
+ <tr>
+ <th>Example pattern</th>
+ <th>Example matching URLs</th>
+ <th>Example non-matching URLs</th>
+ </tr>
+
+ <tr>
+ <td><code>"http://example.com/"</code></td>
+ <td><code>http://example.com/</code></td>
+ <td><code>http://example.com</code><br>
+ <code>http://example.com/foo</code><br>
+ <code>https://example.com/</code><br>
+ <code>http://foo.example.com/</code></td>
+ </tr>
+
+</table>
+
+### Wildcards ###
+
+**A single asterisk** matches any URL with an `http`, `https`, or `ftp`
+scheme. For other schemes like `file`, use a scheme followed by an
+asterisk, as below.
+
+<table>
+
+ <colgroup>
+ <col width="30%">
+ <col width="35%">
+ <col width="35%">
+ </colgroup>
+
+ <tr>
+ <th>Example pattern</th>
+ <th>Example matching URLs</th>
+ <th>Example non-matching URLs</th>
+ </tr>
+
+ <tr>
+ <td><code>"*"</code></td>
+ <td><code>http://example.com/</code><br>
+ <code>https://example.com/</code><br>
+ <code>ftp://example.com/</code><br>
+ <code>http://bar.com/foo.js</code><br>
+ <code>http://foo.com/</code></td>
+ <td><code>file://example.js</code></td>
+ </tr>
+
+</table>
+
+**A domain name prefixed with an asterisk and dot** matches any URL of that
+domain or a subdomain, using any of `http`, `https`, `ftp`.
+
+<table>
+
+ <colgroup>
+ <col width="30%">
+ <col width="35%">
+ <col width="35%">
+ </colgroup>
+
+ <tr>
+ <th>Example pattern</th>
+ <th>Example matching URLs</th>
+ <th>Example non-matching URLs</th>
+ </tr>
+
+ <tr>
+ <td><code>"*.example.com"</code></td>
+ <td><code>http://example.com/</code><br>
+ <code>http://foo.example.com/</code><br>
+ <code>https://example.com/</code><br>
+ <code>http://example.com/foo</code><br>
+ <code>ftp://foo.example.com/</code></td>
+ <td><code>ldap://example.com</code><br>
+ <code>http://example.foo.com/</code></td>
+ </tr>
+
+</table>
+
+**A URL followed by an asterisk** matches that URL and any URL prefixed with
+the pattern.
+
+<table>
+
+ <colgroup>
+ <col width="30%">
+ <col width="35%">
+ <col width="35%">
+ </colgroup>
+
+ <tr>
+ <th>Example pattern</th>
+ <th>Example matching URLs</th>
+ <th>Example non-matching URLs</th>
+ </tr>
+
+ <tr>
+ <td><code>"https://foo.com/*"</code></td>
+ <td><code>https://foo.com/</code><br>
+ <code>https://foo.com/bar</code></td>
+ <td><code>http://foo.com/</code><br>
+ <code>https://foo.com</code><br>
+ <code>https://bar.foo.com/</code></td>
+ </tr>
+
+</table>
+
+**A scheme followed by an asterisk** matches all URLs with that scheme. To
+match local files, use `file://*`.
+
+<table>
+
+ <colgroup>
+ <col width="30%">
+ <col width="70%">
+ </colgroup>
+
+ <tr>
+ <th>Example pattern</th>
+ <th>Example matching URLs</th>
+ </tr>
+
+ <tr>
+ <td><code>"file://*"</code></td>
+ <td><code>file://C:/file.html</code><br>
+ <code>file:///home/file.png</code></td>
+ </tr>
+
+</table>
+
+### Regular Expressions ###
+
+You can specify patterns using a
+[regular expression](https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions):
+
+ var { MatchPattern } = require("match-pattern");
+ var pattern = new MatchPattern(/.*example.*/);
+
+The regular expression is subject to restrictions based on those applied to the
+[HTML5 pattern attribute](http://dev.w3.org/html5/spec/common-input-element-attributes.html#attr-input-pattern). In particular:
+
+* The pattern must match the entire value, not just any subset. For example, the
+pattern `/moz.*/` will not match the URL `http://mozilla.org`.
+
+* The expression is compiled with the `global`, `ignoreCase`, and `multiline` flags
+ disabled. The `MatchPattern` constructor will throw an exception
+ if you try to set any of these flags.
+
+<table>
+
+ <colgroup>
+ <col width="30%">
+ <col width="35%">
+ <col width="35%">
+ </colgroup>
+
+ <tr>
+ <th>Example pattern</th>
+ <th>Example matching URLs</th>
+ <th>Example non-matching URLs</th>
+ </tr>
+
+ <tr>
+ <td><code>/.*moz.*/</code></td>
+ <td><code>http://foo.mozilla.org/</code><br>
+ <code>http://mozilla.org</code><br>
+ <code>https://mozilla.org</code><br>
+ <code>http://foo.com/mozilla</code><br>
+ <code>http://hemozoon.org</code><br>
+ <code>mozscheme://foo.org</code><br></td>
+ <td><code>http://foo.org</code><br>
+ </tr>
+
+ <tr>
+ <td><code>/http:\/\/moz.*/</code></td>
+ <td><code>http://mozilla.org</code><br>
+ <code>http://mozzarella.com</code></td>
+ <td><code>https://mozilla.org</code><br>
+ <code>http://foo.mozilla.org/</code><br>
+ <code>http://foo.com/moz</code></td>
+ </tr>
+
+ <tr>
+ <td><code>/http.*moz.*/</code><br></td>
+ <td><code>http://foo.mozilla.org/</code><br>
+ <code>http://mozilla.org</code><br>
+ <code>http://hemozoon.org/</code></td>
+ <td><code>ftp://http/mozilla.org</code></td>
+ </tr>
+
+</table>
+
+## Examples ##
+
+ var { MatchPattern } = require("match-pattern");
+ var pattern = new MatchPattern("http://example.com/*");
+ console.log(pattern.test("http://example.com/")); // true
+ console.log(pattern.test("http://example.com/foo")); // true
+ console.log(pattern.test("http://foo.com/")); // false!
+
+<api name="MatchPattern">
+@class
+<api name="MatchPattern">
+@constructor
+ This constructor creates match pattern objects that can be used to test URLs.
+@param pattern {string}
+ The pattern to use. See Patterns above.
+</api>
+
+<api name="test">
+@method
+ Tests a URL against the match pattern.
+@param url {string}
+ The URL to test.
+@returns {boolean}
+ True if the URL matches the pattern and false otherwise.
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/memory.md b/tools/addon-sdk-1.4/packages/api-utils/docs/memory.md
new file mode 100644
index 0000000..c90f613
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/memory.md
@@ -0,0 +1,3 @@
+The `memory` module provides a concrete default implementation for the SDK's
+`memory` global. For documentation on the `memory` global, see the
+[Globals](dev-guide/module-development/globals.html) reference.
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/namespace.md b/tools/addon-sdk-1.4/packages/api-utils/docs/namespace.md
new file mode 100644
index 0000000..4a308b8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/namespace.md
@@ -0,0 +1,66 @@
+Provides an API for creating namespaces for any given objects, which
+effectively may be used for creating fields that are not part of objects
+public API.
+
+ let { Namespace } = require('api-utils/namespace');
+ let ns = Namespace();
+
+ ns(publicAPI).secret = secret;
+
+One namespace may be used with multiple objects:
+
+ let { Namespace } = require('api-utils/namespace');
+ let dom = Namespace();
+
+ function View(element) {
+ let view = Object.create(View.prototype);
+ dom(view).element = element;
+ // ....
+ }
+ View.prototype.destroy = function destroy() {
+ let { element } = dom(this);
+ element.parentNode.removeChild(element);
+ // ...
+ };
+ // ...
+ exports.View = View;
+ // ...
+
+Also, multiple namespaces can be used with one object:
+
+ // ./widget.js
+
+ let { Cu } = require('chrome');
+ let { Namespace } = require('api-utils/namespace');
+ let { View } = require('./view');
+
+ // Note this is completely independent from View's internal Namespace object.
+ let ns = Namespace();
+
+ function Widget(options) {
+ let { element, contentScript } = options;
+ let widget = Object.create(Widget.prototype);
+ View.call(widget, options.element);
+ ns(widget).sandbox = Cu.Sandbox(element.ownerDocument.defaultView);
+ // ...
+ }
+ Widget.prototype = Object.create(View.prototype);
+ Widget.prototype.postMessage = function postMessage(message) {
+ let { sandbox } = ns(this);
+ sandbox.postMessage(JSON.stringify(JSON.parse(message)));
+ ...
+ };
+ Widget.prototype.destroy = function destroy() {
+ View.prototype.destroy.call(this);
+ // ...
+ delete ns(this).sandbox;
+ };
+ exports.Widget = Widget;
+
+In addition access to the namespace can be shared with other code by just
+handing them a namespace accessor function.
+
+ let { dom } = require('./view');
+ Widget.prototype.setInnerHTML = function setInnerHTML(html) {
+ dom(this).element.innerHTML = String(html);
+ };
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/observer-service.md b/tools/addon-sdk-1.4/packages/api-utils/docs/observer-service.md
new file mode 100644
index 0000000..a2041f3
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/observer-service.md
@@ -0,0 +1,69 @@
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `observer-service` module provides access to the
+application-wide observer service singleton.
+
+For a list of common observer topics across a variety of Mozilla-based
+applications, see the MDC page on
+[Observer Notifications](https://developer.mozilla.org/en/Observer_Notifications).
+
+## Observer Callbacks ##
+
+Observer callbacks are functions of the following form:
+
+ function callback(subject, data) {
+ /* Respond to the event notification here... */
+ }
+
+In the above example, `subject` is any JavaScript object, as is
+`data`. The particulars of what the two contain are specific
+to the notification topic.
+
+<api name="add">
+@function
+ Adds an observer callback to be triggered whenever a notification matching the
+ topic is broadcast throughout the application.
+
+@param topic {string}
+ The topic to observe.
+
+@param callback {function,object}
+ Either a function or an object that implements [`nsIObserver`](http://mxr.mozilla.org/mozilla-central/source/xpcom/ds/nsIObserver.idl).
+ If a function, then it is called when the notification occurs. If an object,
+ then its `observe()` method is called when the notification occurs.
+
+@param [thisObject] {object}
+ An optional object to use as `this` when a function callback is called.
+</api>
+
+<api name="remove">
+@function
+ Unsubscribes a callback from being triggered whenever a notification
+ matching the topic is broadcast throughout the application.
+
+@param topic {string}
+ The topic being observed by the previous call to `add()`.
+
+@param callback {function,object}
+ The callback subscribed in the previous call to `add()`, either a function or
+ object.
+
+@param [thisObject] {object}
+ If `thisObject` was passed to the previous call to `add()`, it should be
+ passed to `remove()` as well.
+</api>
+
+<api name="notify">
+@function
+ Broadcasts a notification event for a topic, passing a subject and data to all
+ applicable observers in the application.
+
+@param topic {string}
+ The topic about which to broadcast a notification.
+
+@param [subject] {value}
+ Optional information about the topic. This can be any JS object or primitive.
+ If you have multiple values to pass to observers, wrap them in an object,
+ e.g., `{ foo: 1, bar: "some string", baz: myObject }`.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/plain-text-console.md b/tools/addon-sdk-1.4/packages/api-utils/docs/plain-text-console.md
new file mode 100644
index 0000000..3c7a5a6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/plain-text-console.md
@@ -0,0 +1,3 @@
+The `plain-text-console` module provides a minimalist implementation
+of the [console](dev-guide/addon-development/console.html) global,
+which simply logs all messages to standard output.
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/preferences-service.md b/tools/addon-sdk-1.4/packages/api-utils/docs/preferences-service.md
new file mode 100644
index 0000000..348fb60
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/preferences-service.md
@@ -0,0 +1,80 @@
+<!-- contributed by Myk Melez [myk@mozilla.org] -->
+<!-- contributed by Daniel Aquino [mr.danielaquino@gmail.com] -->
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `preferences-service` module provides access to the
+application-wide preferences service singleton.
+
+
+<api name="set">
+@function
+Sets the application preference `name` to `value`.
+@param name {string} Preference name.
+@param value {string,number,bool} Preference value.
+
+**Example:**
+
+ var name = "extensions.checkCompatibility.nightly";
+ require("preferences-service").set(name, false);
+</api>
+
+
+<api name="get">
+@function
+Gets the application preference `name`.
+@param name {string}
+@param defaultValue {string,number,bool} Preference value.
+@returns {string,number,bool} Preference value, returns a default value if no
+preference is set.
+
+**Example:**
+
+ var name = "extensions.checkCompatibility.nightly";
+ var nightlyCompatChk = require("preferences-service").get(name);
+</api>
+
+
+<api name="has">
+@function
+@param name {string} Preference name.
+@returns {bool} Returns whether or not the application preference `name` exists.
+
+**Example:**
+
+ var name = "extensions.checkCompatibility.nightly";
+ if (require("preferences-service").has(name)) {
+ // ...
+ }
+</api>
+
+
+<api name="isSet">
+@function
+@param name {string} Preference name.
+@returns {bool}
+Returns whether or not the application preference `name` both exists
+and has been set to a non-default value by the user (or a program
+acting on the user's behalf).
+
+**Example:**
+
+ var name = "extensions.checkCompatibility.nightly";
+ if (require("preferences-service").isSet(name)) {
+ // ...
+ }
+</api>
+
+
+<api name="reset">
+@function
+Clears a non-default, user-set value from the application preference
+`name`. If no user-set value is defined on `name`, the function
+does nothing.
+@param name {string} Preference name.
+
+**Example:**
+
+ var name = "extensions.checkCompatibility.nightly";
+ require("preferences-service").reset(name);
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/runtime.md b/tools/addon-sdk-1.4/packages/api-utils/docs/runtime.md
new file mode 100644
index 0000000..e4a7ee5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/runtime.md
@@ -0,0 +1,71 @@
+<!-- contributed by Wes Kocher [kwierso@gmail.com] -->
+
+The `runtime` module provides access to information about Firefox's
+runtime environment. All properties exposed are read-only.
+
+For more information, see [nsIXULRuntime][nsIXULRuntime].
+[nsIXULRuntime]: https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIXULRuntime
+
+<api name="inSafeMode">
+@property {boolean}
+ This value is `true` if Firefox was started in safe mode,
+ otherwise `false`.
+</api>
+
+<api name="OS">
+@property {string}
+ A string identifying the current operating system. For example, .
+ `"WINNT"`, `"Darwin"`, or `"Linux"`. See [OS_TARGET][OS_TARGET]
+ for a more complete list of possible values.
+
+[OS_TARGET]: https://developer.mozilla.org/en/OS_TARGET
+</api>
+
+<api name="processType">
+@property {long}
+ The type of the caller's process, which will be one of these constants\:
+<table>
+ <tr>
+ <th>Constant</th>
+ <th>Value</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>PROCESS_TYPE_DEFAULT</td>
+ <td>0</td>
+ <td>The default (chrome) process.</td>
+ </tr>
+
+ <tr>
+ <td>PROCESS_TYPE_PLUGIN</td>
+ <td>1</td>
+ <td>A plugin subprocess.</td>
+ </tr>
+
+ <tr>
+ <td>PROCESS_TYPE_CONTENT</td>
+ <td>2</td>
+ <td>A content subprocess.</td>
+ </tr>
+
+ <tr>
+ <td>PROCESS_TYPE_IPDLUNITTEST</td>
+ <td>3</td>
+ <td>An IPDL unit testing subprocess.</td>
+ </tr>
+</table>
+</api>
+
+<api name="widgetToolkit">
+@property {string}
+ A string identifying the target widget toolkit in use.
+</api>
+
+<api name="XPCOMABI">
+@property {string}
+ A string identifying the [ABI][ABI] of the current processor and compiler vtable.
+ This string takes the form \<`processor`\>-\<`compilerABI`\>,
+ for example\: "`x86-msvc`" or "`ppc-gcc3`".
+[ABI]: https://developer.mozilla.org/en/XPCOM_ABI
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/securable-module.md b/tools/addon-sdk-1.4/packages/api-utils/docs/securable-module.md
new file mode 100644
index 0000000..d1d392d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/securable-module.md
@@ -0,0 +1,93 @@
+The `securable-module` module allows for the recursive loading
+and sandboxing of
+[CommonJS Modules](http://wiki.commonjs.org/wiki/Modules/1.0)
+(formerly called SecurableModules). This allows, for instance,
+the creation of "mini platforms" that manage the sandboxed evaluation of code.
+
+## Loader Objects ##
+
+Loader objects encapsulate the sandboxed loading of SecurableModules
+and the execution of code that relies upon them.
+
+<code>Loader.**runScript**(*options*)</code>
+
+Runs JavaScript code in the context of the Loader. *options* is an
+object with the following keys:
+
+<table>
+ <tr>
+ <td><code>contents</code></td>
+ <td>A string of JavaScript code.</td>
+ </tr>
+ <tr>
+ <td><code>filename</code></td>
+ <td>An absolute URL naming the file from which the code
+ originates; useful for error reporting and debugging. If omitted,
+ this option defaults to <code>"&lt;string&gt;"</code>.</td>
+ </tr>
+ <tr>
+ <td><code>lineNo</code></td>
+ <td>An integer representing the line from the file which the
+ beginning of the code corresponds to. If ommitted, this option
+ defaults to <code>1</code>.</td>
+ </tr>
+ <tr>
+ <td><code>jsVersion</code></td>
+ <td>A string representing the JavaScript version that the code
+ should be interpreted under. If omitted, this options defaults to
+ the latest version of JavaScript supported by the platform.</td>
+ </tr>
+</table>
+
+This method returns the most recent value evaluated by the given code.
+
+<code>Loader.**runScript**(*code*)</code>
+
+If *code* is a string of JavaScript code, this is a convenient
+shorthand for `Loader.runScript({contents: code}}`.
+
+<code>Loader.**require**(*module*)</code>
+
+This loads the given module name using the standard `require()`
+semantics and returns the loaded module.
+
+## Functions ##
+
+<code>securable-module.**Loader**(*options*)</code>
+
+Creates a new SecurableModule Loader. *options* is an object with
+the following keys:
+
+<table>
+ <tr>
+ <td><code>rootPaths</code></td>
+ <td>A list of absolute URLs that will be searched, in order, for
+ SecurableModules when <code>require()</code> is called by any code
+ executing within the context of the Loader.</td>
+ </tr>
+ <tr>
+ <td><code>rootPath</code></td>
+ <td>A single absolute URL; this is a convenience option,
+ synonymous with setting <code>rootPaths</code> to an array containing
+ a single URL.</td>
+ </tr>
+ <tr>
+ <td><code>defaultPrincipal</code></td>
+ <td>A string representing the default principal given to any code
+ that is executed by the Loader. This can be <code>"system"</code>, in
+ which case code executed has full chrome access (including access
+ to the <code>Components</code> object which allows it to access the
+ Mozilla platform unrestricted).
+ Alternatively, it can be a URL, such as <code>"http://www.foo.com"</code>,
+ in which case it is treated like web content. If left unspecified,
+ the default value of this option is <code>"http://www.mozilla.org"</code>.
+ </td>
+ </tr>
+ <tr>
+ <td><code>globals</code></td>
+ <td>An object containing the names and values of all variables
+ that will be injected into the global scope of all code executed
+ by the Loader.</td>
+ </tr>
+</table>
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/tab-browser.md b/tools/addon-sdk-1.4/packages/api-utils/docs/tab-browser.md
new file mode 100644
index 0000000..b844206
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/tab-browser.md
@@ -0,0 +1,136 @@
+<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
+
+The `tab-browser` module is a low-level API that provides privileged
+access to browser tab events and actions.
+
+Introduction
+------------
+
+The `tab-browser` module contains helpers for tracking tabbrowser elements
+and tabs, as well as a few utilities for actions such as opening a new
+tab, and catching all tab content loads.
+
+This is a low-level API that has full privileges, and is intended to be used
+by SDK internal modules. If you just need easy access to tab events for your
+add-on, use the Tabs module (JEP 110).
+
+<api name="activeTab">
+@property {element}
+The XUL tab element of the currently active tab.
+</api>
+
+<api name="addTab">
+@function
+Adds a new tab.
+
+**Example**
+
+ var tabBrowser = require("tab-browser");
+ tabBrowser.addTab("http://google.com");
+
+ var tabBrowser = require("tab-browser");
+ tabBrowser.addTab("http://google.com", {
+ inBackground: true
+ });
+
+ var tabBrowser = require("tab-browser");
+ tabBrowser.addTab("http://google.com", {
+ inNewWindow: true,
+ onLoad: function(tab) {
+ console.log("tab is open.");
+ }
+ });
+
+@returns {element}
+The XUL tab element of the newly created tab.
+
+@param URL {string}
+The URL to be opened in the new tab.
+
+@param options {object}
+Options for how and where to open the new tab.
+
+@prop [inNewWindow] {boolean}
+An optional parameter whose key can be set in `options`.
+If true, the tab is opened in a new window. Default is false.
+
+@prop [inBackground] {boolean}
+An optional parameter whose key can be set in `options`.
+If true, the tab is opened adjacent to the active tab, but not
+switched to. Default is false.
+
+@prop [onLoad] {function}
+An optional parameter whose key can be set in `options`.
+A callback function that is called once the tab has loaded.
+The XUL element for the tab is passed as a parameter to
+this function.
+</api>
+
+<api name="Tracker">
+@function
+Register a delegate object to be notified when tabbrowsers are created
+and destroyed.
+
+The onTrack method will be called once per pre-existing tabbrowser, upon
+tracker registration.
+
+**Example**
+
+ var tabBrowser = require("tab-browser");
+ let tracker = {
+ onTrack: function(tabbrowser) {
+ console.log("A new tabbrowser is being tracked.");
+ },
+ onUntrack: function(tabbrowser) {
+ console.log("A tabbrowser is no longer being tracked.");
+ }
+ };
+ tabBrowser.Tracker(tracker);
+
+@param delegate {object}
+Delegate object to be notified each time a tabbrowser is created or destroyed.
+The object should contain the following methods:
+
+@prop [onTrack] {function}
+Method of delegate that is called when a new tabbrowser starts to be tracked.
+The tabbrowser element is passed as a parameter to this method.
+
+@prop [onUntrack] {function}
+Method of delegate that is called when a tabbrowser stops being tracked.
+The tabbrowser element is passed as a parameter to this method.
+</api>
+
+<api name="TabTracker">
+@function
+Register a delegate object to be notified when tabs are opened and closed.
+
+
+The onTrack method will be called once per pre-existing tab, upon
+tracker registration.
+
+**Example**
+
+ var tabBrowser = require("tab-browser");
+ let tracker = {
+ onTrack: function(tab) {
+ console.log("A new tab is being tracked.");
+ },
+ onUntrack: function(tab) {
+ console.log("A tab is no longer being tracked.");
+ }
+ };
+ tabBrowser.TabTracker(tracker);
+
+@param delegate {object}
+Delegate object to be notified each time a tab is opened or closed.
+The object should contain the following methods:
+
+@prop [onTrack] {function}
+Method of delegate that is called when a new tab starts to be tracked.
+The tab element is passed as a parameter to this method.
+
+@prop [onUntrack] {function}
+Method of delegate that is called when a tab stops being tracked.
+The tab element is passed as a parameter to this method.
+</api>
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/text-streams.md b/tools/addon-sdk-1.4/packages/api-utils/docs/text-streams.md
new file mode 100644
index 0000000..c057e06
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/text-streams.md
@@ -0,0 +1,98 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `text-streams` module provides streams for reading and writing text using
+particular character encodings.
+
+<api name="TextReader">
+@class
+<api name="TextReader">
+@constructor
+ Creates a buffered input stream that reads text from a backing stream using a
+ given text encoding.
+@param inputStream {stream}
+ The backing stream, an [`nsIInputStream`](http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsIInputStream.idl).
+ It must already be opened.
+@param [charset] {string}
+ `inputStream` is expected to be in the character encoding named by this value.
+ If not specified, "UTF-8" is assumed. See [`nsICharsetConverterManager.idl`](http://mxr.mozilla.org/mozilla-central/source/intl/uconv/idl/nsICharsetConverterManager.idl)
+ for documentation on how to determine other valid values for this.
+</api>
+
+<api name="closed">
+@property {boolean}
+ True if the stream is closed.
+</api>
+
+<api name="close">
+@method
+ Closes both the stream and its backing stream.
+</api>
+
+<api name="read">
+@method
+ Reads and returns a string from the stream. If the stream is closed, an
+ exception is thrown.
+@param [numChars] {number}
+ The number of characters to read. If not given, the remainder of the stream
+ is read.
+@returns {string}
+ The string read. If the stream is at the end, the empty string is returned.
+</api>
+
+</api>
+
+
+<api name="TextWriter">
+@class
+<api name="TextWriter">
+@constructor
+ Creates a buffered output stream that writes text to a backing stream using a
+ given text encoding.
+@param outputStream {stream}
+ The backing stream, an [`nsIOutputStream`](http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsIOutputStream.idl).
+ It must already be opened.
+@param [charset] {string}
+ Text will be written to `outputStream` using the character encoding named by
+ this value. If not specified, "UTF-8" is assumed. See [`nsICharsetConverterManager.idl`](http://mxr.mozilla.org/mozilla-central/source/intl/uconv/idl/nsICharsetConverterManager.idl)
+ for documentation on how to determine other valid values for this.
+</api>
+
+<api name="closed">
+@property {boolean}
+ True if the stream is closed.
+</api>
+
+<api name="close">
+@method
+ Flushes the backing stream's buffer and closes both the stream and the backing
+ stream. If the stream is already closed, an exception is thrown.
+</api>
+
+<api name="flush">
+@method
+ Flushes the backing stream's buffer.
+</api>
+
+<api name="write">
+@method
+ Writes a string to the stream. If the stream is closed, an exception is
+ thrown.
+@param str {string}
+ The string to write.
+</api>
+
+<api name="writeAsync">
+@method
+ 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 {string}
+ The string to write.
+@param [callback] {callback}
+ *`callback`*, if given, must be a function. 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 `TextWriter` object.
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/traceback.md b/tools/addon-sdk-1.4/packages/api-utils/docs/traceback.md
new file mode 100644
index 0000000..fd7ddb5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/traceback.md
@@ -0,0 +1,62 @@
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+
+The `traceback` module contains functionality similar to
+Python's [traceback](http://docs.python.org/library/traceback.html) module.
+
+## JSON Traceback Objects ##
+
+Tracebacks are stored in JSON format. The stack is represented as an
+array in which the most recent stack frame is the last element; each
+element thus represents a stack frame and has the following keys:
+
+<table>
+ <tr>
+ <td><code>filename</code></td>
+ <td>The name of the file that the stack frame takes place in.</td>
+ </tr>
+ <tr>
+ <td><code>lineNo</code></td>
+ <td>The line number is being executed at the stack frame.</td>
+ </tr>
+ <tr>
+ <td><code>funcName</code></td>
+ <td>The name of the function being executed at the stack frame, or
+ <code>null</code> if the function is anonymous or the stack frame is
+ being executed in a top-level script or module.</td>
+ </tr>
+</table>
+
+<api name="fromException">
+@function
+ Attempts to extract the traceback from *`exception`*.
+
+@returns {traceback}
+ JSON representation of the traceback or `null` if not found.
+
+@param exception {exception}
+ exception where exception is an `nsIException`.
+</api>
+
+See [nsIException](https://developer.mozilla.org/en/NsIException) for more
+information.
+
+<api name="get">
+@function
+
+@returns {JSON}
+ Returns the JSON representation of the stack at the point that this
+ function is called.
+</api>
+
+<api name="format">
+@function
+Given a JSON representation of the stack or an exception instance,
+returns a formatted plain text representation of it, similar to
+Python's formatted stack tracebacks. If no argument is provided, the
+stack at the point this function is called is used.
+
+@param [tbOrException] {object}
+</api>
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/traits.md b/tools/addon-sdk-1.4/packages/api-utils/docs/traits.md
new file mode 100644
index 0000000..66e8f02
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/traits.md
@@ -0,0 +1,240 @@
+<!-- contributed by Irakli Gozalishvil [gozala@mozilla.com] -->
+
+The `traits` module provides base building blocks for secure object
+composition. It exports base trait / constructor function that
+constructs an instance of `Trait`.
+
+[Traits](http://en.wikipedia.org/wiki/Trait_%28computer_science%29) are a
+simple composition mechanism for structuring object-oriented programs. Traits
+are similar to
+[interfaces](http://en.wikipedia.org/wiki/Interface_%28object-oriented_programming%29),
+except that they often define only a part of an object's data and behavior and
+are intended to be used in conjunction with other traits to completely define
+the object.
+
+Traits are also considered to be a more robust alternative to
+[mixins](http://en.wikipedia.org/wiki/Mixins) because, name conflicts have to
+be resolved explicitly by composer & because trait composition is
+order-independent (hence more declarative).
+
+
+There are some other implementations of traits in JavaScript & some ideas /
+APIs are borrowed from them:
+
+- [traitsjs](http://www.traitsjs.org/)
+- [joose](http://code.google.com/p/joose-js/)
+
+Object-capability security model
+--------------------------------
+
+Implementation uses an
+[object-capability security model](http://en.wikipedia.org/wiki/Object-capability_model)
+to allow protection of private APIs. At the same private APIs can be shared
+between among trait composition parties. To put it simply: All the properties
+whose names start with `"_"` are considered to be **private**, and are
+unaccessible from anywhere except other **public** methods / accessors of the
+instance that had been defined during composition.
+
+<api name="Trait">
+@class
+<api name="Trait">
+@constructor
+Creates an instance of Trait and returns it if it has no `constructor` method
+defined. If instance has `constructor` method, then it is called with all the
+arguments passed to this function and returned value is returned instead,
+unless it's `undefined`. In that case instance is returned.
+
+`Trait` function represents a base trait. As with any other trait it represents
+a constructor function for creating instances of its own & a placeholder
+for a trait compositions functions.
+</api>
+
+<api name="compose">
+@method
+Composes new trait out of itself and traits / property maps passed as an
+arguments. If two or more traits / property maps have properties with the same
+name, the new trait will contain a "conflict" property for that name (see
+examples in Examples section to find out more about "conflict" properties).
+This is a commutative and associative operation, and the order of its
+arguments is not significant.
+
+**Examples:**
+
+Let's say we want to define a reusable piece of code for a lists of elements.
+
+ var { Trait } = require('traits');
+ var List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.splice(index, 1);
+ }
+ });
+
+Instances of `List` can be created by calling `List` function with or without
+`new` keyword.
+
+ let l1 = List();
+ l1 instanceof List; // true
+ let l2 = new List();
+ l2 instanceof List; // true
+
+As you can see `add` and `remove` functions are capable of accessing private
+`_list` property, but thats about it, there's nothing else that will be able
+to access this property:
+
+ '_list' in l1; // false
+ '_list' in l2; // false
+ '_list' in List.protoype; // false
+ l1.has = function(name) name in this
+ l1.has('_list'); // false
+ l1.length; // 0
+ l1.add('test')
+ l1.length // 1
+
+@param trait1 {Object|Function}
+ Trait or property map to compose new trait from.
+@param trait2 {Object|Function}
+ Trait or property map to compose new trait from.
+@param ... {Object|Function}
+ Traits or property maps to compose new trait from.
+
+@returns {Function}
+ New trait containing the combined properties of all the traits.
+</api>
+
+<api name="required">
+@property {Object}
+Singleton, used during trait composition to define "required" properties.
+
+**Example:**
+
+ var Enumerable = Trait.compose({
+ list: Trait.required,
+ forEach: function forEach(consumer) {
+ return this.list.forEach(consumer);
+ }
+ });
+
+ let c1 = Enumerable(); // Error: Missing required property: list
+
+ var EnumerableList = List.compose({
+ get list() this._list.slice(0)
+ }, Enumerable);
+
+ let c2 = EnumerableList();
+ c2.add('test')
+ c2.length // 1
+ c2.list[0] // 'test'
+ c2.forEach(console.log) // > info: 'test 0 test'
+
+</api>
+
+
+<api name="resolve">
+@method
+Composes a new trait that has all the same properties
+as the trait on which it is called, except that each property listed
+in the `resolutions` argument will be renamed from the name
+of the property in the `resolutions` argument to its value.
+And if its value is `null`, the property will become required.
+
+**Example:**
+
+ var Range = List.resolve({
+ constructor: null,
+ add: '_add',
+ }).compose({
+ min: null,
+ max: null,
+ get list() this._list.slice(0),
+ constructor: function Range(min, max) {
+ this.min = min;
+ this.max = max;
+ this._list = [];
+ },
+ add: function(item) {
+ if (item <= this.max && item >= this.min)
+ this._add(item)
+ }
+ });
+
+
+ let r = Range(0, 10);
+ r.min; // 0
+ r.max; // 10
+ r.length; // 0;
+ r.add(5);
+ r.length; // 1
+ r.add(12);
+ r.length; // 1 (12 was not in a range)
+
+@param resolutions {Object}
+@returns {Function}
+ New resolved trait.
+</api>
+
+<api name="override">
+@method
+Composes a new trait with all of the combined properties of `this` and the
+argument traits. In contrast to `compose`, `override` immediately resolves
+all conflicts resulting from this composition by overriding the properties of
+later traits. Trait priority is from left to right. I.e. the properties of
+the leftmost trait are never overridden.
+
+**Example:**
+
+ // will compose trait with conflict property 'constructor'
+ var ConstructableList = List.compose({
+ constructor: function List() this._list = Array.slice(arguments)
+ });
+ // throws error with message 'Remaining conflicting property: constructor'
+ ConstructableList(1, 2, 3);
+
+ var ConstructableList = List.override({
+ constructor: function List() this._list = Array.slice(arguments)
+ });
+ ConstructableList(1, 2, 3).length // 3
+
+@param trait1 {Object|Function}
+ Trait or property map to compose new trait from.
+@param trait2 {Object|Function}
+ Trait or property map to compose new trait from.
+@param ... {Object|Function}
+ Traits or property maps to compose new trait from.
+
+@returns {Function}
+ New trait containing the combined properties of all the traits.
+</api>
+
+<api name="_public">
+@property {Object}
+Internal property of instance representing public API that is exposed to the
+consumers of an instance.
+</api>
+
+<api name='toString'>
+@method
+Textual representation of an object. All the traits will return:
+`'[object Trait]'` string, unless they have `constructor` property, in that
+case string `'Trait'` is replaced with the name of `constructor` property.
+
+**Example:**
+
+ var MyTrait = Trait.compose({
+ constructor: function MyTrait() {
+ // do your initialization here
+ }
+ });
+ MyTrait().toString(); // [object MyTrait]
+
+</api>
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/unit-test.md b/tools/addon-sdk-1.4/packages/api-utils/docs/unit-test.md
new file mode 100644
index 0000000..401b551
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/unit-test.md
@@ -0,0 +1,389 @@
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+<!-- edited by Shane Tomlinson[stomlinson@mozilla.com] -->
+
+The `unit-test` module makes it easy to find and run unit tests.
+
+<api name="test">
+@class
+Each function which represents a test case is passed a single argument
+`test`, which represents the test runner.
+
+<api name="pass">
+@method
+ Marks a test as passing, with the given optional message.
+
+@param [message] {string}
+ Optional passing message.
+</api>
+
+
+<api name="fail">
+@method
+ Marks a test as failing, with the given optional message.
+
+@param [message] {string}
+ Optional failure message.
+</api>
+
+<api name="expectFail">
+@method
+ *experimental* Expect the test enclosed within `func` to fail.
+
+@param func {function}
+ A function that should contain a test that is expected to fail.
+</api>
+
+<api name="exception">
+@method
+ Marks a test as failing due to the given exception having been thrown.
+ This can be put in a `catch` clause.
+
+@param e {exception}
+ An exception.
+</api>
+
+<api name="assert">
+@method
+ Ensures that `a` has a truthy value.
+
+@param a {value}
+ Value to verify.
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+
+<api name="assertEqual">
+@method
+ Ensures that `a == b` without recursing into `a` or `b`.
+
+@param a {value}
+ A value.
+
+@param b {value}
+ Another value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+<api name="assertNotEqual">
+@method
+ Ensures that `a != b` without recursing into `a` or `b`.
+
+@param a {value}
+ A value.
+
+@param b {value}
+ Another value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+<api name="assertStrictEqual">
+@method
+ Ensures that `a === b` without recursing into `a` or `b`.
+
+@param a {value}
+ A value.
+
+@param b {value}
+ Another value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+
+<api name="assertNotStrictEqual">
+@method
+ Ensures that `a !== b` without recursing into `a` or `b`.
+
+@param a {value}
+ A value.
+
+@param b {value}
+ Another value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+<api name="assertMatches">
+@method
+ Ensures that the given string matches the given regular expression.
+ If it does, marks a test as passing, otherwise marks a test as
+ failing.
+
+@param string {string}
+ The string to test.
+
+@param regexp {regexp}
+ The string should match this regular expression.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+
+<api name="assertRaises">
+@method
+ Calls the function `func` with no arguments, expecting an exception
+ to be raised. If nothing is raised, marks the test as failing. If an
+ exception is raised, the exception's `message` property is
+ compared with `predicate`: if `predicate` is a string, then a
+ simple equality comparison is done with `message`. Otherwise,
+ if `predicate` is a regular expression, `message` is tested
+ against it.
+
+@param func {function}
+ A function that should raise an exception when called.
+
+@param predicate {string,regexp}
+ A string or regular expression to compare to the exception's message.
+
+@param [message] {string}
+ Depending on the outcome, a test is marked as passing or failing, and
+ *message* is logged.
+</api>
+
+
+<api name="assertFunction">
+@method
+ Ensures that `a` is a function.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="assertUndefined">
+@method
+ Ensures that `a` is `undefined`. `null`, `0`, and `false` will all fail.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="assertNotUndefined">
+@method
+ Ensures that `a` is not `undefined`. `null`, `0`, and `false` will all pass.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="assertNull">
+@method
+ Ensures that `a` is `null`. `undefined`, `0`, and `false` will all fail.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="assertNotNull">
+@method
+ Ensures that `a` is not `null`. `undefined`, `0`, and `false` will all pass.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="assertObject">
+@method
+ Ensures that `a` is an object. A function, string, or number will fail.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="assertString">
+@method
+ Ensures that `a` is a string.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="assertArray">
+@method
+ Ensures that `a` is an array.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="assertNumber">
+@method
+ Ensures that `a` is a number.
+
+@param a {value}
+ A value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+
+</api>
+
+
+<api name="waitUntilDone">
+@method
+ Puts the test runner into asynchronous testing mode, waiting up to
+ *timeout* milliseconds for `test.done()` to be called. This
+ is intended for use in situations where a test suite schedules a
+ callback, calls `test.waitUntilDone()`, and then calls
+ `test.done()` in the callback.
+
+@param [timeout] {integer}
+ If this number of milliseconds elapses and `test.done()` has not yet been
+ called, the test is marked as failing.
+</api>
+
+
+<api name="done">
+@method
+ Marks a test as being complete. Assumes a previous call to
+ `test.waitUntilDone()`.
+</api>
+
+</api>
+
+
+<api name="waitUntil">
+@method
+ Ensures that `a` returns a truthy value within a reasonable amount of time.
+
+@param a {function}
+ Function that returns the value to verify.
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+
+<api name="waitUntilEqual">
+@method
+ Ensures that `a == b` returned values or values without without recursing
+ into `a` or `b`.
+
+@param a {Function}
+ A value, or a function that returns a value.
+
+@param b {value}
+ Another value, or a function that returns value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+<api name="waitUntilNotEqual">
+@method
+ Ensures that `a != b` without recursing into `a` or `b`.
+
+@param a {Function}
+ A value, or a function that returns a value.
+
+@param b {value}
+ Another value, or a function that returns another value.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+
+<api name="waitUntilMatches">
+@method
+ Ensures that the given string matches the given regular expression.
+ If it does, marks the test as passing, otherwise marks the test as
+ failing.
+
+@param string {Function}
+ A function that returns the string to test.
+
+@param regexp {regexp}
+ The string should match this regular expression.
+
+@param [message] {string}
+ The test is marked as passing or failing depending on the result, logging
+ *message* with it.
+</api>
+
+
+
+<api name="findAndRunTests">
+@function
+ The list of directories is searched for SecurableModules that start
+ with the prefix `test-`. Each module matching this criteria is
+ expected to export functions that are test cases or a suite of test
+ cases; each is called with a single argument, which is a Test Runner
+ Object.
+
+@param options {object}
+ An object with the following properties:
+ @prop dirs {string}
+ A list of absolute paths representing directories to search
+ for tests in. It's assumed that all of these directories are also
+ in the module search path, i.e. any JS files found in them are
+ SecurableModules that can be loaded via a call to
+ `require()`.
+ @prop onDone {function}
+ A function to call when testing is complete.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/unload.md b/tools/addon-sdk-1.4/packages/api-utils/docs/unload.md
new file mode 100644
index 0000000..a7e3f3f
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/unload.md
@@ -0,0 +1,57 @@
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `unload` module allows modules to register callbacks that are called
+when they are unloaded. It is similar to the CommonJS module of the same
+name in the [Narwhal][] platform.
+
+[Narwhal]: http://narwhaljs.org/
+
+<api name="ensure">
+@function
+ Calling `ensure()` on an object does two things:
+
+ 1. It replaces a destructor method with a wrapper method that will never call
+ the destructor more than once.
+ 2. It ensures that this wrapper method is called when `send()` is
+ called.
+
+ Therefore, when you register an object with `ensure()`, you can call its
+ destructor method yourself, you can let it happen for you, or you can do both.
+
+ The destructor will be called with a single argument describing the reason
+ for the unload; see `when()`. If `object` does not have the expected
+ destructor method, then an exception is thrown when `ensure()` is called.
+
+@param object {object}
+ An object that defines a destructor method.
+@param [name] {string}
+ Optional name of the destructor method. Default is `unload`.
+</api>
+
+<api name="when">
+@function
+ Registers a function to be called when `send()` is called.
+
+@param callback {function}
+ A function that will be called when `send()` is called. It is called with a
+ single argument, one of the following strings describing the reason for
+ unload: `"uninstall"`, `"disable"`, `"shutdown"`, `"upgrade"`, or
+ `"downgrade"`. (On Gecko 1.9.2-based applications such as Firefox 3.6,
+ `"upgrade"` and `"downgrade"` are not available, and `"shutdown"` will be sent
+ in their place.) If a reason could not be determined, `undefined` will be
+ passed instead. Note that if an add-on is unloaded with reason `"disable"`,
+ it will not be notified about `"uninstall"` while it is disabled. A solution
+ to this issue is being investigated; see bug 571049.
+</api>
+
+<api name="send">
+@function
+ Sends an "unload signal", thereby triggering all callbacks registered via
+ `when()`. In general, this function need not be manually called; it is
+ automatically triggered by the embedder.
+
+@param [reason] {string}
+ An optional string describing the reason for unload; see `unload.when()`.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/url.md b/tools/addon-sdk-1.4/packages/api-utils/docs/url.md
new file mode 100644
index 0000000..dee0580
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/url.md
@@ -0,0 +1,81 @@
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+
+The `url` module provides functionality for the parsing and retrieving of URLs.
+
+<api name="URL">
+@class
+<api name="URL">
+@constructor
+ The URL constructor creates an object that represents a URL, verifying that
+ the provided string is a valid URL in the process. Any API in the SDK which
+ has a URL parameter will accept `URL` objects, not raw strings, unless
+ otherwise noted.
+
+@param source {string}
+ A string to be converted into a URL. If `source` is not a valid URI, this
+ constructor will throw an exception.
+
+@param [base] {string}
+ An optional string used to resolve relative `source` URLs into absolute ones.
+</api>
+
+<api name="scheme">
+@property {string}
+ The name of the protocol in the URL.
+</api>
+
+<api name="userPass">
+@property {string}
+ The username:password part of the URL, `null` if not present.
+</api>
+
+<api name="host">
+@property {string}
+ The host of the URL, `null` if not present.
+</api>
+
+<api name="port">
+@property {integer}
+ The port number of the URL, `null` if none was specified.
+</api>
+
+<api name="path">
+@property {string}
+ The path component of the URL.
+</api>
+
+<api name="toString">
+@method
+ Returns a string representation of the URL.
+@returns {string}
+ The URL as a string.
+</api>
+</api>
+
+<api name="toFilename">
+@function
+ Attempts to convert the given URL to a native file path. This function will
+ automatically attempt to resolve non-file protocols, such as the `resource:`
+ protocol, to their place on the file system. An exception is raised if the URL
+ can't be converted; otherwise, the native file path is returned as a string.
+
+@param url {string}
+ The URL, as a string, to be converted.
+
+@returns {string}
+ The converted native file path as a string.
+</api>
+
+<api name="fromFilename">
+@function
+ Converts the given native file path to a `file:` URL.
+
+@param path {string}
+ The native file path, as a string, to be converted.
+
+@returns {string}
+ The converted URL as a string.
+</api>
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/window-utils.md b/tools/addon-sdk-1.4/packages/api-utils/docs/window-utils.md
new file mode 100644
index 0000000..3de84b5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/window-utils.md
@@ -0,0 +1,84 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+<!-- edited by Erik Vold [erikvvold@gmail.com] -->
+
+The `window-utils` module provides helpers for accessing and tracking
+application windows. These windows implement the [`nsIDOMWindow`][nsIDOMWindow]
+interface.
+
+[nsIDOMWindow]: http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/base/nsIDOMWindow.idl
+
+<api name="WindowTracker">
+@class
+`WindowTracker` objects make it easy to "monkeypatch" windows when a program is
+loaded and "un-monkeypatch" those windows when the program is unloaded. For
+example, if a Firefox add-on needs to add a status bar icon to all browser
+windows, it can use a single `WindowTracker` object to gain access to windows
+when they are opened and closed and also when the add-on is loaded and unloaded.
+
+When a window is opened or closed, a `WindowTracker` notifies its delegate
+object, which is passed to the constructor. The delegate is also notified of
+all windows that are open at the time that the `WindowTracker` is created and
+all windows that are open at the time that the `WindowTracker` is unloaded. The
+caller can therefore use the same code to act on all windows, regardless of
+whether they are currently open or are opened in the future, or whether they are
+closed while the parent program is loaded or remain open when the program is
+unloaded.
+
+When a window is opened or when a window is open at the time that the
+`WindowTracker` is created, the delegate's `onTrack()` method is called and
+passed the window.
+
+When a window is closed or when a window is open at the time that the
+`WindowTracker` is unloaded, the delegate's `onUntrack()` method is called and
+passed the window. (The `WindowTracker` is unloaded when its its `unload()`
+method is called, or when its parent program is unloaded, disabled, or
+uninstalled, whichever comes first.)
+
+**Example**
+
+ var delegate = {
+ onTrack: function (window) {
+ console.log("Tracking a window: " + window.location);
+ // Modify the window!
+ },
+ onUntrack: function (window) {
+ console.log("Untracking a window: " + window.location);
+ // Undo your modifications!
+ }
+ };
+ var winUtils = require("window-utils");
+ var tracker = new winUtils.WindowTracker(delegate);
+
+<api name="WindowTracker">
+@constructor
+ A `WindowTracker` object listens for openings and closings of application
+ windows.
+@param delegate {object}
+ An object that implements `onTrack()` and `onUntrack()` methods.
+@prop onTrack {function}
+ A function to be called when a window is open or loads, with the window as the
+ first and only argument.
+@prop [onUntrack] {function}
+ A function to be called when a window unloads, with the window as the first
+ and only argument.
+</api>
+</api>
+
+<api name="windowIterator">
+@function
+ An iterator for windows currently open in the application.
+
+**Example**
+
+ var winUtils = require("window-utils");
+ for (window in winUtils.windowIterator())
+ console.log("An open window! " + window.location);
+
+</api>
+
+<api name="closeOnUnload">
+@function
+ Marks an application window to be closed when the program is unloaded.
+@param window {window}
+ The window to close.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/xhr.md b/tools/addon-sdk-1.4/packages/api-utils/docs/xhr.md
new file mode 100644
index 0000000..f2d3ee3
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/xhr.md
@@ -0,0 +1,91 @@
+<!-- contributed by Atul Varma [atul@mozilla.com] -->
+<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
+
+The `xhr` module provides access to `XMLHttpRequest` functionality, also known
+as AJAX.
+
+## Limitations ##
+
+The `XMLHttpRequest` object is currently fairly limited, and does not
+yet implement the `addEventListener()` or `removeEventListener()`
+methods. It also doesn't yet implement the `upload` property.
+
+Furthermore, the `XMLHttpRequest` object does not currently support
+the `mozBackgroundRequest` property. All security UI, such as
+username/password prompts, are automatically suppressed, so if
+required authentication information isn't passed to the `open()`
+method, the request will fail.
+
+## Resource Use ##
+
+Whenever this module is unloaded, all in-progress requests are immediately
+aborted.
+
+## Security Concerns ##
+
+By default, the `XMLHttpRequest` object grants full access to any
+protocol scheme, which means that it can be used to read from (but not
+write to) the host system's entire filesystem. It also has unfettered
+access to any local area networks, VPNs, and the internet.
+
+### Threat Model ###
+
+The `XMLHttpRequest` object can be used by an add-on to "phone
+home" and transmit potentially sensitive user data to third
+parties.
+
+If access to the filesystem isn't prevented, it could easily be used
+to access sensitive user data, though this may be inconsequential if
+the client can't access the network.
+
+If access to local area networks isn't prevented, malicious code could access
+sensitive data.
+
+If transmission of cookies isn't prevented, malicious code could access
+sensitive data.
+
+Attenuating access based on a regular expression may be ineffective if
+it's easy to write a regular expression that *looks* safe but contains
+a special character or two that makes it far less secure than it seems
+at first glance.
+
+### Possible Attenuations ###
+
+<span class="aside">
+We may also want to consider attenuating further based on domain name
+and possibly even restricting the protocol to `https:` only, to reduce
+risk.
+</span>
+
+Before being exposed to unprivileged code, this object needs
+to be attenuated in such a way that, at the very least, it can't
+access the user's filesystem. This can probably be done most securely
+by white-listing the protocols that can be used in the URL passed to
+the `open()` method, and limiting them to `http:`, `https:`, and
+possibly a special scheme that can be used to access the add-on's
+packaged, read-only resources.
+
+Finally, we need to also consider attenuating http/https requests such
+that they're "sandboxed" and don't communicate potentially sensitive
+cookie information.
+
+<api name="XMLHttpRequest">
+@class
+
+<api name="XMLHttpRequest">
+@constructor
+ Creates an `XMLHttpRequest`. This is a constructor, so its use should always
+ be preceded by the `new` operator. For more information about
+ `XMLHttpRequest` objects, see the MDC page on
+ [Using XMLHttpRequest](https://developer.mozilla.org/En/Using_XMLHttpRequest)
+ and the Limitations section in this page.
+</api>
+</api>
+
+<api name="getRequestCount">
+@function
+ Returns the number of `XMLHttpRequest` objects that are alive (i.e., currently
+ active or about to be).
+@returns {integer}
+ The number of live `XMLHttpRequest` objects.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/xpcom.md b/tools/addon-sdk-1.4/packages/api-utils/docs/xpcom.md
new file mode 100644
index 0000000..6cb5428
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/xpcom.md
@@ -0,0 +1,223 @@
+Using this module you can:
+
+* register a component with
+[XPCOM](https://developer.mozilla.org/en/Creating_XPCOM_Components),
+making it available to all XPCOM clients
+* retrieve a factory for a given XPCOM component
+* generate a UUID
+
+The module also exposes the
+[XPCOMUtils](https://developer.mozilla.org/en/JavaScript_code_modules/XPCOMUtils.jsm)
+module.
+
+<api name="register">
+@function
+
+Makes a component available through XPCOM.
+
+This function creates and registers a factory for a component given a
+constructor for it and some metadata: a
+[class ID](https://developer.mozilla.org/en/Creating_XPCOM_Components/An_Overview_of_XPCOM#CID), a [contract ID](https://developer.mozilla.org/en/Creating_XPCOM_Components/An_Overview_of_XPCOM#Contract_ID),
+and a name.
+
+<span class="aside">In this example the HelloWorld component is available to JavaScript only, so we use the technique documented under the "Using wrappedJSObject" section of [How to Build an XPCOM Component in JavaScript](https://developer.mozilla.org/en/How_to_Build_an_XPCOM_Component_in_Javascript).</span>
+
+ var xpcom = require("xpcom");
+
+ function HelloWorld() {
+ this.wrappedJSObject = this;
+ }
+
+ HelloWorld.prototype = {
+ QueryInterface: xpcom.utils.generateQI(),
+ hello: function() {
+ return "Hello World!";
+ }
+ };
+
+ xpcom.register({name: "Hello World Component",
+ contractID: "@me.org/myComponent",
+ create: HelloWorld});
+
+XPCOM clients can subsequently access this factory and use it to create
+instances of the component.
+
+ var {Ci} = require("chrome");
+
+ var factory = xpcom.getClass("@me.org/myComponent", Ci.nsIFactory);
+ var helloWorld = factory.createInstance(null, Ci.nsISupports).wrappedJSObject;
+ console.log(helloWorld.hello());
+
+`register()` returns a Factory object for the component which implements
+the `createInstance()` and `QueryInterface()` functions of the
+[`nsIFactory`](https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIFactory) and
+[`nsISupports`](https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsISupports)
+interfaces, as well as defining an `unregister()` function to remove the
+component from XPCOM.
+
+When the module is unloaded, all components registered via the `register()`
+function are automatically unregistered.
+
+@param options {object}
+
+@prop [uuid] {nsIDPtr}
+A [UUID](https://developer.mozilla.org/en/Generating_GUIDs) which will be
+used as the
+[class ID](https://developer.mozilla.org/en/Creating_XPCOM_Components/An_Overview_of_XPCOM#CID)
+for this component. If you don't include this option, the `register()`
+function will generate a new UUID.
+
+@prop create {function}
+The constructor for the component.
+
+@prop name {string}
+A human-readable name for the component.
+
+@prop contractID {string}
+A human-readable string which will be used as the
+[contract ID](https://developer.mozilla.org/en/Creating_XPCOM_Components/An_Overview_of_XPCOM#Contract_ID)
+for the component. An XPCOM client will be able to use this value to access
+the component.
+
+@returns {Factory}
+See the documentation for the `Factory` class in this page.
+</api>
+
+<api name="getClass">
+@function
+Returns the factory object for the class specified by `contractID`.
+
+For example, given a registered XPCOM component which is identified with
+the contract ID "@me.org/myComponent", we can access a factory and then
+use it to instantiate the component in the following way:
+
+ var xpcom = require("xpcom");
+ var {Ci} = require("chrome");
+
+ var factory = xpcom.getClass("@me.org/myComponent", Ci.nsIFactory);
+ var helloWorld = factory.createInstance(null, Ci.nsISupports).wrappedJSObject;
+ console.log(helloWorld.hello());
+
+@param contractID {string}
+The
+[contract ID](https://developer.mozilla.org/en/Creating_XPCOM_Components/An_Overview_of_XPCOM#Contract_ID)
+for the component whose factory will be returned.
+
+@param [iid] {iid}
+The interface type to be returned. These objects are usually accessed through
+the `Components.interfaces`, or `Ci`, object.
+
+The methods of this interface will be callable on the returned factory object.
+Usually you want this to be
+[`Ci.nsIFactory`](https://developer.mozilla.org/En/nsIFactory), but if you know
+a component has a factory that implements a more specific type of factory
+interface, you can pass that interface here. If you don't include this option
+only the methods of
+[`nsISupports`](https://developer.mozilla.org/En/NsISupports)
+will be callable, which is probably not what you want.
+
+@returns {object}
+The factory object. The type of this object will depend on the value of the
+`iid` argument. If no `iid` argument is specified it will be of type
+[`nsISupports`](https://developer.mozilla.org/En/NsISupports).
+
+Note that this object is not a `Factory` object as defined by this module.
+If you previously registered the component by calling the `register()`
+function and you need to access the `Factory` object for the component, for
+example to call the `Factory`'s `unregister()` method, you can do so by
+getting the
+[`wrappedJSObject`](https://developer.mozilla.org/en/wrappedJSObject)
+property of the returned object:
+
+ var factory = xpcom.getClass("@me.org/myComp", Ci.nsIFactory).wrappedJSObject;
+ factory.unregister();
+
+</api>
+
+<api name="utils">
+@property {object}
+The
+[XPCOMUtils](https://developer.mozilla.org/en/JavaScript_code_modules/XPCOMUtils.jsm)
+module.
+</api>
+
+<api name="makeUuid">
+@function
+Generates and returns a new
+[UUID](https://developer.mozilla.org/en/Generating_GUIDs).
+
+Calling `toString()` on this object will yield the UUID in string form.
+@returns {nsIDPtr}
+</api>
+
+<api name="Factory">
+@class
+
+When a component is made available through XPCOM using the `register()`
+function, `register()` returns a `Factory` object that can be used to
+instantiate the component using its `createInstance()` function:
+
+ var factory = require("xpcom").register({
+ name: "My Component",
+ contractID: "@me.org/myComponent",
+ create: MyComponent
+ });
+
+ var {Ci} = require("chrome");
+ var component = factory.createInstance(null, Ci.nsISupports).wrappedJSObject;
+
+In this example we haven't defined a custom interface ID for the component.
+Instead we pass `Ci.nsISupports` as the interface ID, and use `wrappedJSObject`
+to retrieve the component. For more details on this technique see the
+[guide to building XPCOM components in JavaScript](https://developer.mozilla.org/en/How_to_Build_an_XPCOM_Component_in_Javascript).
+
+`Factory` also implements its own `unregister()` function,
+which unregisters the component from XPCOM.
+
+<api name="createInstance">
+@method
+Creates an instance of the component associated with this factory.
+
+@param outer {nsISupports}
+This argument must be `null`, or the function throws
+`Cr.NS_ERROR_NO_AGGREGATION`.
+
+@param iid {iid}
+Interface identifier. These objects are usually accessed through
+the `Components.interfaces`, or `Ci`, object. The methods of this
+interface will be callable on the returned object.
+
+If the object implements an interface that's already defined in XPCOM, you
+can pass that in here:
+
+ var about = aboutFactory.createInstance(null, Ci.nsIAboutModule);
+ // You can now access the nsIAboutModule interface of the 'about' object
+
+If you will be getting the `wrappedJSObject` property from the returned
+object to access its JavaScript implementation, pass `Ci.nsISupports` here:
+
+ var custom = factory.createInstance(null, Ci.nsISupports).wrappedJSObject;
+ // You can now access the interface defined for the 'custom' object
+
+</api>
+
+<api name="QueryInterface">
+@method
+This method is called automatically by XPCOM, so usually you don't need
+to call it yourself. It returns the `Factory` object itself such that the
+methods of the given interface are callable on it.
+
+@param interfaces {iid}
+There are only two legal values for this parameter: `Ci.nsIFactory` and
+`Ci.nsISupports`. Any other value will cause this method to throw
+`Cr.NS_ERROR_NO_INTERFACE`.
+
+@returns {Factory}
+</api>
+
+<api name="unregister">
+@method
+Unregisters the factory's component.
+</api>
+
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/docs/xul-app.md b/tools/addon-sdk-1.4/packages/api-utils/docs/xul-app.md
new file mode 100644
index 0000000..00379db
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/docs/xul-app.md
@@ -0,0 +1,72 @@
+<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
+
+The `xul-app` module provides facilities for introspecting the application on
+which your program is running.
+
+With the exception of `ids`, each of these properties exposes the attribute of
+the same name on the [`nsIXULAppInfo`][nsIXULAppInfo] interface. For more
+information, see the [MDC documentation][].
+
+[nsIXULAppInfo]: http://mxr.mozilla.org/mozilla-central/source/xpcom/system/nsIXULAppInfo.idl
+[MDC documentation]: https://developer.mozilla.org/en/nsIXULAppInfo
+
+<api name="ID">
+@property {string}
+ The GUID of the host application. For example, for Firefox this value is
+ `"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"`.
+</api>
+
+<api name="name">
+@property {string}
+ The host application name. For example, `"Firefox"`.
+</api>
+
+<api name="version">
+@property {string}
+ The host application version.
+</api>
+
+<api name="platformVersion">
+@property {string}
+ The Gecko/XULRunner platform version.
+</api>
+
+<api name="ids">
+@property {object}
+ A mapping of application names to their IDs. For example,
+ `ids["Firefox"] == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"`.
+</api>
+
+<api name="is">
+@function
+ Checks whether the host application is the given application.
+@param name {string}
+ A host application name.
+@returns {boolean}
+ True if the host application is `name` and false otherwise.
+</api>
+
+<api name="isOneOf">
+@function
+ Checks whether the host application is one of the given applications.
+@param names {array}
+ An array of host application names.
+@returns {boolean}
+ True if the host application is one of the `names` and false otherwise.
+</api>
+
+<api name="versionInRange">
+@function
+ Compares a given version to a version range. See the [MDC documentation](https://developer.mozilla.org/en/Toolkit_version_format#Comparing_versions)
+ for details on version comparisons.
+@param version {string}
+ The version to compare.
+@param lowInclusive {string}
+ The lower bound of the version range to compare. The range includes this
+ bound.
+@param highExclusive {string}
+ The upper bound of the version range to compare. The range does not include
+ this bound.
+@returns {boolean}
+ True if `version` falls in the given range and false otherwise.
+</api>
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/api-utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/api-utils.js
new file mode 100644
index 0000000..17d18cc
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/api-utils.js
@@ -0,0 +1,186 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ * Edward Lee <edilee@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+// The possible return values of getTypeOf.
+const VALID_TYPES = [
+ "array",
+ "boolean",
+ "function",
+ "null",
+ "number",
+ "object",
+ "string",
+ "undefined",
+];
+
+/**
+ * Returns a function C that creates instances of privateCtor. C may be called
+ * with or without the new keyword. The prototype of each instance returned
+ * from C is C.prototype, and C.prototype is an object whose prototype is
+ * privateCtor.prototype. Instances returned from C will therefore be instances
+ * of both C and privateCtor. Additionally, the constructor of each instance
+ * returned from C is C.
+ *
+ * @param privateCtor
+ * A constructor.
+ * @return A function that makes new instances of privateCtor.
+ */
+exports.publicConstructor = function publicConstructor(privateCtor) {
+ function PublicCtor() {
+ let obj = { constructor: PublicCtor, __proto__: PublicCtor.prototype };
+ memory.track(obj, privateCtor.name);
+ privateCtor.apply(obj, arguments);
+ return obj;
+ }
+ PublicCtor.prototype = { __proto__: privateCtor.prototype };
+ return PublicCtor;
+};
+
+/**
+ * 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 and null are identified by the special type names
+ * "array" and "null"; "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 = {};
+ let mapThrew = false;
+
+ for (let [key, req] in Iterator(requirements)) {
+ let [optsVal, keyInOpts] = (key in options) ?
+ [options[key], true] :
+ [undefined, false];
+ if (req.map) {
+ try {
+ optsVal = req.map(optsVal);
+ }
+ catch (err) {
+ mapThrew = true;
+ }
+ }
+ if (req.is) {
+ // Sanity check the caller's type names.
+ req.is.forEach(function (typ) {
+ if (VALID_TYPES.indexOf(typ) < 0) {
+ let msg = 'Internal error: invalid requirement type "' + typ + '".';
+ throw new Error(msg);
+ }
+ });
+ if (req.is.indexOf(getTypeOf(optsVal)) < 0)
+ throw requirementError(key, req);
+ }
+ if (req.ok && !req.ok(optsVal))
+ throw requirementError(key, req);
+
+ if (keyInOpts || (req.map && !mapThrew))
+ 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 and null are identified by "array" and
+// "null", not "object".
+let getTypeOf = exports.getTypeOf = function getTypeOf(val) {
+ let typ = typeof(val);
+ if (typ === "object") {
+ if (!val)
+ return "null";
+ if (Array.isArray(val))
+ return "array";
+ }
+ return typ;
+}
+
+// Returns a new Error with a nice message.
+function requirementError(key, requirement) {
+ 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.";
+ }
+ return new Error(msg);
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/app-strings.js b/tools/addon-sdk-1.4/packages/api-utils/lib/app-strings.js
new file mode 100644
index 0000000..120c26e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/app-strings.js
@@ -0,0 +1,95 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is String Bundle.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const apiUtils = require("./api-utils");
+
+/**
+ * A bundle of strings.
+ *
+ * @param url {String}
+ * the URL of the string bundle
+ */
+exports.StringBundle = apiUtils.publicConstructor(function StringBundle(url) {
+
+ let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(url);
+
+ this.__defineGetter__("url", function () url);
+
+ /**
+ * Get a string from the bundle.
+ *
+ * @param name {String}
+ * the name of the string to get
+ * @param args {array} [optional]
+ * an array of arguments that replace occurrences of %S in the string
+ *
+ * @returns {String} the value of the string
+ */
+ this.get = function strings_get(name, args) {
+ try {
+ if (args)
+ return stringBundle.formatStringFromName(name, args, args.length);
+ else
+ return stringBundle.GetStringFromName(name);
+ }
+ catch(ex) {
+ // f.e. "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE)
+ // [nsIStringBundle.GetStringFromName]"
+ throw new Error("String '" + name + "' could not be retrieved from the " +
+ "bundle due to an unknown error (it doesn't exist?).");
+ }
+ },
+
+ /**
+ * Iterate the strings in the bundle.
+ *
+ */
+ apiUtils.addIterator(
+ this,
+ function keysValsGen() {
+ let enumerator = stringBundle.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ yield [elem.key, elem.value];
+ }
+ }
+ );
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/array.js b/tools/addon-sdk-1.4/packages/api-utils/lib/array.js
new file mode 100644
index 0000000..a41acdd
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/array.js
@@ -0,0 +1,102 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+/**
+ * Returns `true` if given `array` contain given `element` or `false`
+ * otherwise.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element being looked up.
+ * @returns {Boolean}
+ */
+var has = exports.has = function has(array, element) {
+ // shorter and faster equivalent of `array.indexOf(element) >= 0`
+ return !!~array.indexOf(element);
+};
+
+/**
+ * Adds given `element` to the given `array` if it does not contain it yet.
+ * `true` is returned if element was added otherwise `false` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be added.
+ * @returns {Boolean}
+ */
+var add = exports.add = function add(array, element) {
+ var result;
+ if ((result = !has(array, element)))
+ array.push(element);
+
+ return result;
+};
+
+/**
+ * Removes first occurrence of the given `element` from the given `array`. If
+ * `array` does not contain given `element` `false` is returned otherwise
+ * `true` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be removed.
+ * @returns {Boolean}
+ */
+exports.remove = function remove(array, element) {
+ var result;
+ if ((result = has(array, element)))
+ array.splice(array.indexOf(element), 1);
+
+ return result;
+};
+
+/**
+ * Produces a duplicate-free version of the given `array`.
+ * @param {Array} array
+ * Source array.
+ * @returns {Array}
+ */
+exports.unique = function unique(array) {
+ var value = [];
+ return array.forEach(function(element) {
+ add(value, element);
+ });
+ return value;
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/byte-streams.js b/tools/addon-sdk-1.4/packages/api-utils/lib/byte-streams.js
new file mode 100644
index 0000000..b44357e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/byte-streams.js
@@ -0,0 +1,135 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+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("./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/tools/addon-sdk-1.4/packages/api-utils/lib/channel.js b/tools/addon-sdk-1.4/packages/api-utils/lib/channel.js
new file mode 100644
index 0000000..b7f1e61
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/channel.js
@@ -0,0 +1,67 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const { jetpackID } = require('@packaging');
+
+// TODO: Create a bug report and remove this workaround once it's fixed.
+// Only function needs defined in the context of the message manager window
+// can be registered via `addMessageListener`.
+function listener(callee) {
+ return function listener() { return callee.apply(this, arguments); };
+}
+function messageListener(scope, callee) {
+ return scope ? scope.eval('(' + listener + ')')(callee) : callee
+}
+
+exports.channel = function channel(scope, messageManager, address, raw) {
+ address = jetpackID + ':' + address
+ return {
+ input: function input(next, stop) {
+ let listener = messageListener(scope, function onMessage(message) {
+ if (false === next(raw ? message : message.json))
+ messageManager.removeMessageListener(address, listener);
+ });
+ messageManager.addMessageListener(address, listener);
+ },
+ output: function output(data) {
+ messageManager.sendAsyncMessage(address, data);
+ },
+ sync: !messageManager.sendSyncMessage ? null : function sync(data) {
+ messageManager.sendSyncMessage(address, data);
+ }
+ };
+};
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/collection.js b/tools/addon-sdk-1.4/packages/api-utils/lib/collection.js
new file mode 100644
index 0000000..5525a5a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/collection.js
@@ -0,0 +1,141 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+exports.Collection = Collection;
+
+/**
+ * Adds a collection property to the given object. Setting the property to a
+ * scalar value empties the collection and adds the value. Setting it to an
+ * array empties the collection and adds all the items in the array.
+ *
+ * @param obj
+ * The property will be defined on this object.
+ * @param propName
+ * The name of the property.
+ * @param array
+ * If given, this will be used as the collection's backing array.
+ */
+exports.addCollectionProperty = function addCollProperty(obj, propName, array) {
+ array = array || [];
+ let publicIface = new Collection(array);
+
+ obj.__defineSetter__(propName, function (itemOrItems) {
+ array.splice(0, array.length);
+ publicIface.add(itemOrItems);
+ });
+
+ obj.__defineGetter__(propName, function () {
+ return publicIface;
+ });
+};
+
+/**
+ * A collection is ordered, like an array, but its items are unique, like a set.
+ *
+ * @param array
+ * The collection is backed by an array. If this is given, it will be
+ * used as the backing array. This way the caller can fully control the
+ * collection. Otherwise a new empty array will be used, and no one but
+ * the collection will have access to it.
+ */
+function Collection(array) {
+ array = array || [];
+
+ /**
+ * Provides iteration over the collection. Items are yielded in the order
+ * they were added.
+ */
+ this.__iterator__ = function Collection___iterator__() {
+ let items = array.slice();
+ for (let i = 0; i < items.length; i++)
+ yield items[i];
+ };
+
+ /**
+ * The number of items in the collection.
+ */
+ this.__defineGetter__("length", function Collection_get_length() {
+ return array.length;
+ });
+
+ /**
+ * Adds a single item or an array of items to the collection. Any items
+ * already contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.add = function Collection_add(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let item = items[i];
+ if (array.indexOf(item) < 0)
+ array.push(item);
+ }
+ return this;
+ };
+
+ /**
+ * Removes a single item or an array of items from the collection. Any items
+ * not contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.remove = function Collection_remove(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let idx = array.indexOf(items[i]);
+ if (idx >= 0)
+ array.splice(idx, 1);
+ }
+ return this;
+ };
+};
+
+function toArray(itemOrItems) {
+ let isArr = itemOrItems &&
+ itemOrItems.constructor &&
+ itemOrItems.constructor.name === "Array";
+ return isArr ? itemOrItems : [itemOrItems];
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/content.js b/tools/addon-sdk-1.4/packages/api-utils/lib/content.js
new file mode 100644
index 0000000..a46a5ff
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/content.js
@@ -0,0 +1,44 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+exports.Loader = require('./content/loader').Loader;
+exports.Symbiont = require('./content/symbiont').Symbiont;
+exports.Worker = require('./content/worker').Worker;
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/content/loader.js b/tools/addon-sdk-1.4/packages/api-utils/lib/content/loader.js
new file mode 100644
index 0000000..25bc651
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/content/loader.js
@@ -0,0 +1,203 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { EventEmitter } = require('../events');
+const { validateOptions, getTypeOf } = require('../api-utils');
+const { URL, toFilename } = require('../url');
+const file = require('../file');
+
+// map of property validations
+const valid = {
+ contentURL: {
+ ok: function (value) {
+ try {
+ URL(value);
+ }
+ catch(e) {
+ return false;
+ }
+ return true;
+ },
+ msg: 'The `contentURL` option must be a valid URL.'
+ },
+ contentScriptFile: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: function(value) 'undefined' === getTypeOf(value) ? null : value,
+ ok: function(value) {
+ if (getTypeOf(value) === 'array') {
+ // Make sure every item is a local file URL.
+ return value.every(function (item) {
+ try {
+ toFilename(item);
+ return true;
+ }
+ catch(e) {
+ return false;
+ }
+ });
+ }
+ return true;
+ },
+ msg: 'The `contentScriptFile` option must be a local file URL or an array of'
+ + 'URLs.'
+ },
+ contentScript: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: function(value) 'undefined' === getTypeOf(value) ? null : value,
+ ok: function(value) 'array' !== getTypeOf(value) ? true :
+ value.every(function(item) 'string' === getTypeOf(item))
+ ,
+ msg: 'The script option must be a string or an array of strings.'
+ },
+ contentScriptWhen: {
+ is: ['string'],
+ ok: function(value) ['start', 'ready', 'end'].indexOf(value) >= 0,
+ map: function(value) {
+ return value || 'end';
+ },
+ msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
+ }
+};
+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) validateOptions(
+ { $: suspect },
+ { $: validation }
+).$
+
+function Allow(script) ({
+ get script() script,
+ set script(value) script = !!value
+})
+
+/**
+ * Trait is intended to be used in some composition. It provides set of core
+ * properties and bounded validations to them. Trait is useful for all the
+ * compositions providing high level APIs for interaction with content.
+ * Property changes emit `"propertyChange"` events on instances.
+ */
+const Loader = EventEmitter.compose({
+ /**
+ * Permissions for the content, with the following keys:
+ * @property {Object} [allow = { script: true }]
+ * @property {Boolean} [allow.script = true]
+ * Whether or not to execute script in the content. Defaults to true.
+ */
+ get allow() this._allow || (this._allow = Allow(true)),
+ set allow(value) this.allow.script = value && value.script,
+ _allow: null,
+ /**
+ * The content to load. Either a string of HTML or a URL.
+ * @type {String}
+ */
+ get contentURL() this._contentURL,
+ set contentURL(value) {
+ value = validate(value, valid.contentURL);
+ if (this._contentURL != value) {
+ this._emit('propertyChange', {
+ contentURL: this._contentURL = value
+ });
+ }
+ },
+ _contentURL: null,
+ /**
+ * When to load the content scripts.
+ * Possible values are "end" (default), which loads them once all page
+ * contents have been loaded, "ready", which loads them once DOM nodes are
+ * ready (ie like DOMContentLoaded event), and "start", which loads them once
+ * the `window` object for the page has been created, but before any scripts
+ * specified by the page have been loaded.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {'start'|'ready'|'end'}
+ */
+ get contentScriptWhen() this._contentScriptWhen,
+ set contentScriptWhen(value) {
+ value = validate(value, valid.contentScriptWhen);
+ if (value !== this._contentScriptWhen) {
+ this._emit('propertyChange', {
+ contentScriptWhen: this._contentScriptWhen = value
+ });
+ }
+ },
+ _contentScriptWhen: 'end',
+ /**
+ * The URLs of content scripts.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {String[]}
+ */
+ get contentScriptFile() this._contentScriptFile,
+ set contentScriptFile(value) {
+ value = validate(value, valid.contentScriptFile);
+ if (value != this._contentScriptFile) {
+ this._emit('propertyChange', {
+ contentScriptFile: this._contentScriptFile = value
+ });
+ }
+ },
+ _contentScriptFile: null,
+ /**
+ * The texts of content script.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {String|undefined}
+ */
+ get contentScript() this._contentScript,
+ set contentScript(value) {
+ value = validate(value, valid.contentScript);
+ if (value != this._contentScript) {
+ this._emit('propertyChange', {
+ contentScript: this._contentScript = value
+ });
+ }
+ },
+ _contentScript: null
+});
+exports.Loader = Loader;
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/content/symbiont.js b/tools/addon-sdk-1.4/packages/api-utils/lib/content/symbiont.js
new file mode 100644
index 0000000..d6dbf38
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/content/symbiont.js
@@ -0,0 +1,217 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Worker } = require('./worker');
+const { Loader } = require('./loader');
+const hiddenFrames = require('../hidden-frame');
+const observers = require('../observer-service');
+const unload = require('../unload');
+
+/**
+ * This trait is layered on top of `Worker` and in contrast to symbiont
+ * Worker constructor requires `content` option that represents content
+ * that will be loaded in the provided frame, if frame is not provided
+ * Worker will create hidden one.
+ */
+const Symbiont = Worker.resolve({
+ constructor: '_initWorker',
+ destroy: '_workerDestroy'
+ }).compose(Loader, {
+
+ /**
+ * The constructor requires all the options that are required by
+ * `require('content').Worker` with the difference that the `frame` option
+ * is optional. If `frame` is not provided, `contentURL` is expected.
+ * @param {Object} options
+ * @param {String} options.contentURL
+ * URL of a content to load into `this._frame` and create worker for.
+ * @param {Element} [options.frame]
+ * iframe element that is used to load `options.contentURL` into.
+ * if frame is not provided hidden iframe will be created.
+ */
+ constructor: function Symbiont(options) {
+ options = options || {};
+
+ if ('contentURL' in options)
+ this.contentURL = options.contentURL;
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('allow' in options)
+ this.allow = options.allow;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+ if ('frame' in options) {
+ this._initFrame(options.frame);
+ }
+ else {
+ let self = this;
+ this._hiddenFrame = hiddenFrames.HiddenFrame({
+ onReady: function onFrame() {
+ self._initFrame(this.element);
+ }
+ });
+ hiddenFrames.add(this._hiddenFrame);
+ }
+
+ unload.ensure(this._public, "destroy");
+ },
+
+ destroy: function destroy() {
+ this._workerDestroy();
+ this._unregisterListener();
+ this._frame = null;
+ if (this._hiddenFrame) {
+ hiddenFrames.remove(this._hiddenFrame);
+ this._hiddenFrame = null;
+ }
+ },
+
+ /**
+ * XUL iframe or browser elements with attribute `type` being `content`.
+ * Used to create `ContentSymbiont` from.
+ * @type {nsIFrame|nsIBrowser}
+ */
+ _frame: null,
+
+ /**
+ * Listener to the `'frameReady"` event (emitted when `iframe` is ready).
+ * Removes listener, sets right permissions to the frame and loads content.
+ */
+ _initFrame: function _initFrame(frame) {
+ if (this._loadListener)
+ this._unregisterListener();
+
+ this._frame = frame;
+ frame.docShell.allowJavascript = this.allow.script;
+ frame.setAttribute("src", this._contentURL);
+
+ // Inject `addon` object in document if we load a document from
+ // one of our addon folder and if no content script are defined. bug 612726
+ let isDataResource =
+ typeof this._contentURL == "string" &&
+ this._contentURL.indexOf(require("@packaging").uriPrefix) == 0;
+ let hasContentScript =
+ (Array.isArray(this.contentScript) ? this.contentScript.length > 0
+ : !!this.contentScript) ||
+ (Array.isArray(this.contentScriptFile) ? this.contentScriptFile.length > 0
+ : !!this.contentScriptFile);
+ // If we have to inject `addon` we have to do it before document
+ // script execution, so during `start`:
+ this._injectInDocument = isDataResource && !hasContentScript;
+ if (this._injectInDocument)
+ this.contentScriptWhen = "start";
+
+ if ((frame.contentDocument.readyState == "complete" ||
+ (frame.contentDocument.readyState == "interactive" &&
+ this.contentScriptWhen != 'end' )) &&
+ frame.contentDocument.location == this._contentURL) {
+ // In some cases src doesn't change and document is already ready
+ // (for ex: when the user moves a widget while customizing toolbars.)
+ this._onInit();
+ return;
+ }
+
+ let self = this;
+
+ if ('start' == this.contentScriptWhen) {
+ this._loadEvent = 'start';
+ observers.add('document-element-inserted',
+ this._loadListener = function onStart(doc) {
+
+ let window = doc.defaultView;
+ if (window && window == frame.contentWindow) {
+ self._unregisterListener();
+ self._onInit();
+ }
+
+ });
+ return;
+ }
+
+ let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded';
+ let self = this;
+ this._loadEvent = eventName;
+ frame.addEventListener(eventName,
+ this._loadListener = function _onReady(event) {
+
+ if (event.target != frame.contentDocument)
+ return;
+ self._unregisterListener();
+
+ self._onInit();
+
+ }, true);
+
+ },
+
+ /**
+ * Unregister listener that watchs for document being ready to be injected.
+ * This listener is registered in `Symbiont._initFrame`.
+ */
+ _unregisterListener: function _unregisterListener() {
+ if (!this._loadListener)
+ return;
+ if (this._loadEvent == "start") {
+ observers.remove('document-element-inserted', this._loadListener);
+ }
+ else {
+ this._frame.removeEventListener(this._loadEvent, this._loadListener,
+ true);
+ }
+ this._loadListener = null;
+ },
+
+ /**
+ * Called by Symbiont itself when the frame is ready to load
+ * content scripts according to contentScriptWhen. Overloaded by Panel.
+ */
+ _onInit: function () {
+ this._initWorker({ window: this._frame.contentWindow });
+ }
+
+});
+exports.Symbiont = Symbiont;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/content/worker.js b/tools/addon-sdk-1.4/packages/api-utils/lib/content/worker.js
new file mode 100644
index 0000000..6ded506
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/content/worker.js
@@ -0,0 +1,662 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Trait } = require('../traits');
+const { EventEmitter, EventEmitterTrait } = require('../events');
+const { Ci, Cu, Cc } = require('chrome');
+const timer = require('../timer');
+const { toFilename } = require('../url');
+const file = require('../file');
+const unload = require('../unload');
+const observers = require('../observer-service');
+const { Cortex } = require('../cortex');
+const { Enqueued } = require('../utils/function');
+const self = require("self");
+const scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+
+const CONTENT_PROXY_URL = self.data.url("content-proxy.js");
+
+const JS_VERSION = '1.8';
+
+const ERR_DESTROYED =
+ "The page has been destroyed and can no longer be used.";
+
+/**
+ * This key is not exported and should only be used for proxy tests.
+ * The following `PRIVATE_KEY` is used in addon module scope in order to tell
+ * Worker API to expose `UNWRAP_ACCESS_KEY` in content script.
+ * This key allows test-content-proxy.js to unwrap proxy with valueOf:
+ * let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY);
+ */
+const PRIVATE_KEY = {};
+
+function ensureArgumentsAreJSON(array, window) {
+ // JSON.stringify is buggy with cross-sandbox values,
+ // it may return "{}" on functions. Use a replacer to match them correctly.
+ function replacer(k, v) {
+ return typeof v === "function" ? undefined : v;
+ }
+ // If a window is given, we use its `JSON.parse` object in order to
+ // create JS objects for its compartments (See bug 714891)
+ let parse = JSON.parse;
+ if (window) {
+ // As we can't directly rely on `window.wrappedJSObject.JSON`, we create
+ // a temporary sandbox in order to get access to a safe `JSON` object:
+ parse = Cu.Sandbox(window).JSON.parse;
+ }
+ return parse(JSON.stringify(array, replacer));
+}
+
+/**
+ * Extended `EventEmitter` allowing us to emit events asynchronously.
+ */
+const AsyncEventEmitter = EventEmitter.compose({
+ /**
+ * Emits event in the next turn of event loop.
+ */
+ _asyncEmit: function _asyncEmit() {
+ timer.setTimeout(function emitter(emit, scope, params) {
+ emit.apply(scope, params);
+ }, 0, this._emit, this, arguments)
+ }
+});
+
+/**
+ * Local trait providing implementation of the workers global scope.
+ * Used to configure global object in the sandbox.
+ * @see http://www.w3.org/TR/workers/#workerglobalscope
+ */
+const WorkerGlobalScope = AsyncEventEmitter.compose({
+ on: Trait.required,
+ _removeAllListeners: Trait.required,
+
+ // wrapped functions from `'timer'` module.
+ // Wrapper adds `try catch` blocks to the callbacks in order to
+ // emit `error` event on a symbiont if exception is thrown in
+ // the Worker global scope.
+ // @see http://www.w3.org/TR/workers/#workerutils
+
+ // List of all living timeouts/intervals
+ _timers: null,
+
+ setTimeout: function setTimeout(callback, delay) {
+ let params = Array.slice(arguments, 2);
+ let id = timer.setTimeout(function(self) {
+ try {
+ delete self._timers[id];
+ callback.apply(null, params);
+ } catch(e) {
+ self._addonWorker._asyncEmit('error', e);
+ }
+ }, delay, this);
+ this._timers[id] = true;
+ return id;
+ },
+ clearTimeout: function clearTimeout(id){
+ delete this._timers[id];
+ return timer.clearTimeout(id);
+ },
+
+ setInterval: function setInterval(callback, delay) {
+ let params = Array.slice(arguments, 2);
+ let id = timer.setInterval(function(self) {
+ try {
+ callback.apply(null, params);
+ } catch(e) {
+ self._addonWorker._asyncEmit('error', e);
+ }
+ }, delay, this);
+ this._timers[id] = true;
+ return id;
+ },
+ clearInterval: function clearInterval(id) {
+ delete this._timers[id];
+ return timer.clearInterval(id);
+ },
+
+ /**
+ * `onMessage` function defined in the global scope of the worker context.
+ */
+ get _onMessage() this.__onMessage,
+ set _onMessage(value) {
+ let listener = this.__onMessage;
+ if (listener && value !== listener) {
+ this.removeListener('message', listener);
+ this.__onMessage = undefined;
+ }
+ if (value)
+ this.on('message', this.__onMessage = value);
+ },
+ __onMessage: undefined,
+
+ /**
+ * Function for sending data to the addon side.
+ * Validates that data is a `JSON` or primitive value and emits
+ * 'message' event on the worker in the next turn of the event loop.
+ * _Later this will be sending data across process boundaries._
+ * @param {JSON|String|Number|Boolean} data
+ */
+ postMessage: function postMessage(data) {
+ if (!this._addonWorker)
+ throw new Error(ERR_DESTROYED);
+ this._addonWorker._asyncEmit('message', ensureArgumentsAreJSON(data));
+ },
+
+ /**
+ * EventEmitter, that behaves (calls listeners) asynchronously.
+ * A way to send customized messages to / from the worker.
+ * Events from in the worker can be observed / emitted via self.on / self.emit
+ */
+ get port() this._port._public,
+
+ /**
+ * Same object than this.port but private API.
+ * Allow access to _asyncEmit, in order to send event to port.
+ */
+ _port: null,
+
+ /**
+ * Alias to the global scope in the context of worker. Similar to
+ * `window` concept.
+ */
+ get self() this._public,
+
+ /**
+ * Configures sandbox and loads content scripts into it.
+ * @param {Worker} worker
+ * content worker
+ */
+ constructor: function WorkerGlobalScope(worker) {
+ this._addonWorker = worker;
+
+ // Hack in order to allow addon worker to access _asyncEmit
+ // as this is the private object of WorkerGlobalScope
+ worker._contentWorker = this;
+
+ // create an event emitter that receive and send events from/to the addon
+ let contentWorker = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () {
+ let addonWorker = contentWorker._addonWorker;
+ if (!addonWorker)
+ throw new Error(ERR_DESTROYED);
+ addonWorker._onContentScriptEvent.apply(addonWorker, arguments);
+ }
+ });
+ // create emit that executes in next turn of event loop.
+ this._port._asyncEmit = Enqueued(this._port._emit);
+ // expose wrapped port, that exposes only public properties.
+ this._port._public = Cortex(this._port);
+
+ // We receive an unwrapped window, with raw js access
+ let window = worker._window;
+
+ let proto = window;
+ let proxySandbox = null;
+ // Build content proxies only if the document has a non-system principal
+ if (window.wrappedJSObject) {
+ // Instantiate the proxy code in another Sandbox in order to prevent
+ // content script from polluting globals used by proxy code
+ proxySandbox = Cu.Sandbox(window, {
+ wantXrays: true
+ });
+ proxySandbox.console = console;
+ // Execute the proxy code
+ scriptLoader.loadSubScript(CONTENT_PROXY_URL, proxySandbox);
+ // Get a reference of the window's proxy
+ proto = proxySandbox.create(window);
+ }
+
+ // Create the sandbox and bind it to window in order for content scripts to
+ // have access to all standard globals (window, document, ...)
+ let sandbox = this._sandbox = new Cu.Sandbox(window, {
+ sandboxPrototype: proto,
+ wantXrays: true
+ });
+ Object.defineProperties(sandbox, {
+ // We need "this === window === top" to be true in toplevel scope:
+ window: { get: function() sandbox },
+ top: { get: function() sandbox },
+ // 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!
+ unsafeWindow: { get: function () window.wrappedJSObject }
+ });
+
+ // Internal feature that is only used by SDK tests:
+ // Expose unlock key to content script context.
+ // See `PRIVATE_KEY` definition for more information.
+ if (proxySandbox && worker._expose_key)
+ sandbox.UNWRAP_ACCESS_KEY = proxySandbox.UNWRAP_ACCESS_KEY;
+ // Initialize timer lists
+ this._timers = {};
+
+ let publicAPI = this._public;
+
+ // List of content script globals:
+ let keys = ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval',
+ 'self'];
+ for each (let key in keys) {
+ Object.defineProperty(
+ sandbox, key, Object.getOwnPropertyDescriptor(publicAPI, key)
+ );
+ }
+ let self = this;
+ Object.defineProperties(sandbox, {
+ onMessage: {
+ get: function() self._onMessage,
+ set: function(value) {
+ console.warn("The global `onMessage` function in content scripts " +
+ "is deprecated in favor of the `self.on()` function. " +
+ "Replace `onMessage = function (data){}` definitions " +
+ "with calls to `self.on('message', function (data){})`. " +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ self._onMessage = value;
+ },
+ configurable: true
+ },
+ console: { value: console, configurable: true },
+
+ // Deprecated use of on/postMessage from globals
+ on: {
+ value: function () {
+ console.warn("The global `on()` function in content scripts is " +
+ "deprecated in favor of the `self.on()` function, " +
+ "which works the same. Replace calls to `on()` with " +
+ "calls to `self.on()`" +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ publicAPI.on.apply(publicAPI, arguments);
+ },
+ configurable: true
+ },
+ postMessage: {
+ value: function () {
+ console.warn("The global `postMessage()` function in content " +
+ "scripts is deprecated in favor of the " +
+ "`self.postMessage()` function, which works the same. " +
+ "Replace calls to `postMessage()` with calls to " +
+ "`self.postMessage()`." +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ publicAPI.postMessage.apply(publicAPI, arguments);
+ },
+ configurable: true
+ }
+ });
+
+ // Temporary fix for test-widget, that pass self.postMessage to proxy code
+ // that first try to access to `___proxy` and then call it through `apply`.
+ // We need to move function given to content script to a sandbox
+ // with same principal than the content script.
+ // In the meantime, we need to allow such access explicitly
+ // by using `__exposedProps__` property, documented here:
+ // https://developer.mozilla.org/en/XPConnect_wrappers
+ sandbox.self.postMessage.__exposedProps__ = {
+ ___proxy: 'rw',
+ apply: 'rw'
+ }
+
+ // Inject `addon` global into target document if document is trusted,
+ // `addon` in document is equivalent to `self` in content script.
+ if (worker._injectInDocument) {
+ let win = window.wrappedJSObject ? window.wrappedJSObject : window;
+ Object.defineProperty(win, "addon", {
+ get: function () publicAPI
+ }
+ );
+ }
+
+ // 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) {
+ if (Array.isArray(contentScriptFile))
+ this._importScripts.apply(this, contentScriptFile);
+ else
+ this._importScripts(contentScriptFile);
+ }
+ if (contentScript) {
+ this._evaluate(
+ Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
+ );
+ }
+ },
+ _destructor: function _destructor() {
+ this._removeAllListeners();
+ // Unregister all setTimeout/setInterval
+ // We can use `clearTimeout` for both setTimeout/setInterval
+ // as internal implementation of timer module use same method for both.
+ for (let id in this._timers)
+ timer.clearTimeout(id);
+ this._sandbox = null;
+ this._addonWorker = null;
+ this.__onMessage = undefined;
+ },
+
+ /**
+ * JavaScript sandbox where all the content scripts are evaluated.
+ * {Sandbox}
+ */
+ _sandbox: null,
+
+ /**
+ * Reference to the addon side of the worker.
+ * @type {Worker}
+ */
+ _addonWorker: null,
+
+ /**
+ * Evaluates code in the sandbox.
+ * @param {String} code
+ * JavaScript source to evaluate.
+ * @param {String} [filename='javascript:' + code]
+ * Name of the file
+ */
+ _evaluate: function(code, filename) {
+ filename = filename || 'javascript:' + code;
+ try {
+ Cu.evalInSandbox(code, this._sandbox, JS_VERSION, filename, 1);
+ }
+ catch(e) {
+ this._addonWorker._asyncEmit('error', e);
+ }
+ },
+ /**
+ * 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.
+ */
+ _importScripts: function _importScripts(url) {
+ let urls = Array.slice(arguments, 0);
+ for each (let contentScriptFile in urls) {
+ try {
+ let filename = toFilename(contentScriptFile);
+ this._evaluate(file.read(filename), filename);
+ }
+ catch(e) {
+ this._addonWorker._asyncEmit('error', e)
+ }
+ }
+ }
+});
+
+/**
+ * Message-passing facility for communication between code running
+ * in the content and add-on process.
+ * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker
+ */
+const Worker = AsyncEventEmitter.compose({
+ on: Trait.required,
+ _asyncEmit: Trait.required,
+ _removeAllListeners: Trait.required,
+
+ /**
+ * 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
+ * symbiont.
+ *
+ * `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 postMessage(data) {
+ if (!this._contentWorker)
+ throw new Error(ERR_DESTROYED);
+ this._contentWorker._asyncEmit('message',
+ ensureArgumentsAreJSON(data, this._window));
+ },
+
+ /**
+ * EventEmitter, that behaves (calls listeners) asynchronously.
+ * A way to send customized messages to / from the worker.
+ * Events from in the worker can be observed / emitted via
+ * worker.on / worker.emit.
+ */
+ get port() {
+ // We generate dynamically this attribute as it needs to be accessible
+ // before Worker.constructor gets called. (For ex: Panel)
+
+ // create an event emitter that receive and send events from/to the worker
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () self._emitEventToContent(arguments)
+ });
+ // create emit that executes in next turn of event loop.
+ this._port._asyncEmit = Enqueued(this._port._emit);
+ // expose wrapped port, that exposes only public properties:
+ // We need to destroy this getter in order to be able to set the
+ // final value. We need to update only public port attribute as we never
+ // try to access port attribute from private API.
+ delete this._public.port;
+ this._public.port = Cortex(this._port);
+ // Replicate public port to the private object
+ delete this.port;
+ this.port = this._public.port;
+
+ return this._port;
+ },
+
+ /**
+ * Same object than this.port but private API.
+ * Allow access to _asyncEmit, in order to send event to port.
+ */
+ _port: null,
+
+ /**
+ * Emit a custom event to the content script,
+ * i.e. emit this event on `self.port`
+ */
+ _emitEventToContent: function _emitEventToContent(args) {
+ // We need to save events that are emitted before the worker is
+ // initialized
+ if (!this._inited) {
+ this._earlyEvents.push(args);
+ return;
+ }
+
+ // We throw exception when the worker has been destroyed
+ if (!this._contentWorker) {
+ throw new Error(ERR_DESTROYED);
+ }
+
+ let scope = this._contentWorker._port;
+ // Ensure that we pass only JSON values
+ let array = Array.prototype.slice.call(args);
+ scope._asyncEmit.apply(scope, ensureArgumentsAreJSON(array, this._window));
+ },
+
+ // Is worker connected to the content worker (i.e. WorkerGlobalScope) ?
+ _inited: false,
+
+ // List of custom events fired before worker is initialized
+ get _earlyEvents() {
+ delete this._earlyEvents;
+ this._earlyEvents = [];
+ return this._earlyEvents;
+ },
+
+ constructor: function Worker(options) {
+ options = options || {};
+
+ if ('window' in options)
+ this._window = options.window;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+ if ('onDetach' in options)
+ this.on('detach', options.onDetach);
+
+ // Internal feature that is only used by SDK unit tests.
+ // See `PRIVATE_KEY` definition for more information.
+ if ('exposeUnlockKey' in options && options.exposeUnlockKey === PRIVATE_KEY)
+ this._expose_key = true;
+
+ // 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
+ this._windowID = this._window.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ currentInnerWindowID;
+ observers.add("inner-window-destroyed",
+ this._documentUnload = this._documentUnload.bind(this));
+
+ unload.ensure(this._public, "destroy");
+
+ // Ensure that worker._port is initialized for contentWorker to be able
+ // to send use event during WorkerGlobalScope(this)
+ this.port;
+
+ // will set this._contentWorker pointing to the private API:
+ WorkerGlobalScope(this);
+
+ // Mainly enable worker.port.emit to send event to the content worker
+ this._inited = true;
+
+ // Flush all events that have been fired before the worker is initialized.
+ this._earlyEvents.forEach((function (args) this._emitEventToContent(args)).
+ bind(this));
+ },
+
+ _documentUnload: function _documentUnload(subject, topic, data) {
+ let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (innerWinID != this._windowID) return false;
+ this._workerCleanup();
+ return true;
+ },
+
+ get url() {
+ // this._window will be null after detach
+ return this._window ? this._window.document.location.href : null;
+ },
+
+ get tab() {
+ if (this._window) {
+ let tab = require("../tabs/tab");
+ // this._window will be null after detach
+ return tab.getTabForWindow(this._window);
+ }
+ return null;
+ },
+
+ /**
+ * Tells content worker to unload itself and
+ * removes all the references from itself.
+ */
+ destroy: function destroy() {
+ this._workerCleanup();
+ this._removeAllListeners('message');
+ this._removeAllListeners('error');
+ this._removeAllListeners('detach');
+ },
+
+ /**
+ * Remove all internal references to the attached document
+ * Tells _port to unload itself and removes all the references from itself.
+ */
+ _workerCleanup: function _workerCleanup() {
+ // maybe unloaded before content side is created
+ // As Symbiont call worker.constructor on document load
+ if (this._contentWorker)
+ this._contentWorker._destructor();
+ this._contentWorker = null;
+ this._window = null;
+ // This method may be called multiple times,
+ // avoid dispatching `detach` event more than once
+ if (this._windowID) {
+ this._windowID = null;
+ observers.remove("inner-window-destroyed", this._documentUnload);
+ this._earlyEvents.slice(0, this._earlyEvents.length);
+ this._emit("detach");
+ }
+ },
+
+ /**
+ * Receive an event from the content script that need to be sent to
+ * worker.port. Provide a way for composed object to catch all events.
+ */
+ _onContentScriptEvent: function _onContentScriptEvent() {
+ // Ensure that we pass only JSON values
+ let array = Array.prototype.slice.call(arguments);
+ this._port._asyncEmit.apply(this._port, ensureArgumentsAreJSON(array));
+ },
+
+ /**
+ * 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,
+
+ /**
+ * Flag to enable `addon` object injection in document. (bug 612726)
+ * @type {Boolean}
+ */
+ _injectInDocument: false
+});
+exports.Worker = Worker;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/cortex.js b/tools/addon-sdk-1.4/packages/api-utils/lib/cortex.js
new file mode 100644
index 0000000..059d9de
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/cortex.js
@@ -0,0 +1,139 @@
+/* vim:set ts=2 sw=2 sts=2
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+// `var` is being used in the module in order to make it reusable in
+// environments in which `let` and `const` is not yet supported.
+
+// Returns `object`'s property value, where `name` is a name of the property.
+function get(object, name) {
+ return object[name];
+}
+
+// Assigns `value` to the `object`'s property, where `name` is the name of the
+// property.
+function set(object, name, value) {
+ return object[name] = value;
+}
+
+/**
+ * Given an `object` containing a property with the given `name`, create
+ * a property descriptor that can be used to define alias/proxy properties
+ * on other objects. A change in the value of an alias will propagate
+ * to the aliased property and vice versa.
+ */
+function createAliasProperty(object, name) {
+ // Getting own property descriptor of an `object` for the given `name` as
+ // we are going to create proxy analog.
+ var property = Object.getOwnPropertyDescriptor(object, name);
+ var descriptor = {
+ configurable: property.configurable,
+ enumerable: property.enumerable,
+ alias: true
+ };
+
+ // If the original property has a getter and/or setter, bind a
+ // corresponding getter/setter in the alias descriptor to the original
+ // object, so the `this` object in the getter/setter is the original object
+ // rather than the alias.
+ if ("get" in property && property.get)
+ descriptor.get = property.get.bind(object);
+ if ("set" in property && property.set)
+ descriptor.set = property.set.bind(object);
+
+ // If original property was a value property.
+ if ("value" in property) {
+ // If original property is a method using it's `object` bounded copy.
+ if (typeof property.value === "function") {
+ descriptor.value = property.value.bind(object);
+ // Also preserving writability of the original property.
+ descriptor.writable = property.writable;
+ }
+
+ // If the original property was just a data property, we create proxy
+ // accessors using our custom get/set functions to propagate changes to the
+ // original `object` and vice versa.
+ else {
+ descriptor.get = get.bind(null, object, name);
+ descriptor.set = set.bind(null, object, name);
+ }
+ }
+ return descriptor;
+}
+
+// Defines property on `object` object with a name `alias` if given if not
+// defaults to `name` that represents an alias of `source[name]`. If aliased
+// property was an assessor or a method `this` pseudo-variable will be `source`
+// when invoked. If aliased property was a data property changes on any of the
+// aliases will propagate to the `source[name]` and also other way round.
+function defineAlias(source, target, name, alias) {
+ return Object.defineProperty(target, alias || name,
+ createAliasProperty(source, name));
+}
+
+/**
+ * Function takes any `object` and returns a proxy for its own public
+ * properties. By default properties are considered to be public if they don't
+ * start with `"_"`, but default behavior can be overridden if needed, by
+ * passing array of public property `names` as a second argument. By default
+ * returned object will be direct decedent of the given `object`'s prototype,
+ * but this can be overridden by passing third optional argument, that will be
+ * used as `prototype` instead.
+ * @param {Object} object
+ * Object to create cortex for.
+ * @param {String[]} [names]
+ * Optional array of public property names.
+ * @param {Object} [prototype]
+ * Optional argument that will be used as `prototype` of the returned object,
+ * if not provided `Object.getPrototypeOf(object)` is used instead.
+ */
+exports.Cortex = function Cortex(object, names, prototype) {
+ // Creating a cortex object from the given `prototype`, if one was not
+ // provided then `prototype` of a given `object` is used. This allows
+ // consumer to define expected behavior `instanceof`. In common case
+ // `prototype` argument can be omitted to preserve same behavior of
+ // `instanceof` as on original `object`.
+ var cortex = Object.create(prototype || Object.getPrototypeOf(object));
+ // Creating alias properties on the `cortex` object for all the own
+ // properties of the original `object` that are contained in `names` array.
+ // If `names` array is not provided then all the properties that don't
+ // start with `"_"` are aliased.
+ Object.getOwnPropertyNames(object).forEach(function (name) {
+ if ((!names && "_" !== name.charAt(0)) || (names && ~names.indexOf(name)))
+ defineAlias(object, cortex, name);
+ });
+ return cortex;
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/cuddlefish.js b/tools/addon-sdk-1.4/packages/api-utils/lib/cuddlefish.js
new file mode 100644
index 0000000..c0c9935
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/cuddlefish.js
@@ -0,0 +1,320 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+var EXPORTED_SYMBOLS = [ 'Loader' ];
+
+!function(exports) {
+
+"use strict";
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
+ results: Cr, manager: Cm } = Components;
+const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
+const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
+ getService(Ci.mozIJSSubScriptLoader);
+
+const Sandbox = {
+ new: function (prototype, principal) {
+ let sandbox = Object.create(Sandbox, {
+ sandbox: {
+ value: Cu.Sandbox(principal || Sandbox.principal, {
+ sandboxPrototype: prototype || Sandbox.prototype,
+ wantXrays: Sandbox.wantXrays
+ })
+ }
+ });
+ // There are few properties (dump, Iterator) that by default appear in
+ // sandboxes shadowing properties provided by a prototype. To workaround
+ // this we override all such properties by copying them directly to the
+ // sandbox.
+ Object.keys(prototype).forEach(function onEach(key) {
+ if (sandbox.sandbox[key] !== prototype[key])
+ sandbox.sandbox[key] = prototype[key];
+ });
+ return sandbox;
+ },
+ evaluate: function evaluate(source, uri, lineNumber) {
+ return Cu.evalInSandbox(
+ source,
+ this.sandbox,
+ this.version,
+ uri,
+ lineNumber || this.lineNumber
+ );
+ },
+ load: function load(uri) {
+ scriptLoader.loadSubScript(uri, this.sandbox);
+ },
+ merge: function merge(properties) {
+ Object.getOwnPropertyNames(properties).forEach(function(name) {
+ Object.defineProperty(this.sandbox, name,
+ Object.getOwnPropertyDescriptor(properties, name));
+ }, this);
+ },
+ principal: systemPrincipal,
+ version: '1.8',
+ lineNumber: 1,
+ wantXrays: false,
+ prototype: {}
+};
+
+// the Module object made available to CommonJS modules when they are
+// evaluated, along with 'exports' and 'uri'
+const Module = {
+ new: function(id, path, uri) {
+ let module = Object.create(this);
+
+ module.id = id;
+ module.path = path;
+ module.uri = uri;
+ module.exports = {};
+
+ return module;
+ },
+ // TODO: I'd like to remove this, it's not used adds complexity and does
+ // not has much adoption in commonjs either.
+ setExports: function setExports(exports) {
+ this.exports = exports;
+ }
+};
+
+const Loader = {
+ new: function (options) {
+ let loader = Object.create(Loader, {
+ // Manifest generated by a linker, containing map of module url's mapped
+ // to it's requirements, comes from harness-options.json
+ manifest: { value: options.manifest || {} },
+
+ // Following property may be passed in (usually for mocking purposes) in
+ // order to override default modules cache.
+ modules: { value: options.modules || Object.create(Loader.modules) },
+ globals: { value: options.globals || {} },
+
+ uriPrefix: { value: options.uriPrefix },
+
+ sandboxes: { value: {} }
+ });
+ loader.require = this.require.bind(loader, options.loader);
+
+ // some 'magic' modules, that have no corresponding .js file
+ loader.modules['@packaging'] = Object.freeze({
+ id: '@packaging',
+ exports: JSON.parse(JSON.stringify(options))
+ });
+ loader.modules['@loader'] = Object.freeze({
+ exports: Object.freeze({ Loader: Loader }),
+ id: '@loader'
+ });
+
+ // This special module defines globals which will be added to every
+ // module this loader creates
+ let globals = loader.require('api-utils/globals!');
+ Object.getOwnPropertyNames(globals).forEach(function(name) {
+ Object.defineProperty(loader.globals, name,
+ Object.getOwnPropertyDescriptor(globals, name));
+ });
+ // Freeze globals so that modules won't have a chance to mutate scope of
+ // other modules.
+ Object.freeze(globals);
+
+ // Override global `dump` so that it behaves same as in any other module (
+ // currently we override dump to write to a file instead of `stdout` so that
+ // python can read it on windows).
+ dump = globals.dump;
+
+ return Object.freeze(loader);
+ },
+ modules: {
+ 'chrome': Object.freeze({
+ exports: Object.freeze({
+ Cc: Cc,
+ CC: CC,
+ Ci: Ci,
+ Cu: Cu,
+ Cr: Cr,
+ Cm: Cm,
+ components: Components,
+ messageManager: 'addMessageListener' in exports ? exports : null
+ }),
+ id: 'chrome'
+ }),
+ 'self': function self(loader, requirer) {
+ return loader.require('api-utils/self!').create(requirer.path);
+ },
+ },
+
+ // populate a Module by evaluating the CommonJS module code in the sandbox
+ load: function load(module) {
+ let require = Loader.require.bind(this, module.path);
+ require.main = this.main;
+ let sandbox = this.sandboxes[module.path] = Sandbox.new(this.globals);
+ sandbox.merge({
+ require: require,
+ module: module,
+ exports: module.exports
+ });
+
+ sandbox.load(module.uri);
+
+ // Workaround for bug 674195. Freezing objects from other sandboxes fail,
+ // so we create descendant and freeze it instead.
+ if (typeof(module.exports) === 'object') {
+ module.exports = Object.prototype.isPrototypeOf(module.exports) ?
+ Object.freeze(module.exports) :
+ Object.freeze(Object.create(module.exports));
+ }
+ },
+
+ // this require() is the main entry point for regular CommonJS modules. The
+ // bind() in load (above) causes those modules to get a very restricted
+ // form of this require(): one which can only ever reference this one
+ // loader, and which always uses their URI as a "base" (so they're limited
+ // to their own manifest entries, and can't import anything off the
+ // manifest).
+ require: function require(base, id) {
+ let module, manifest = this.manifest[base], requirer = this.modules[base];
+
+ if (!id)
+ throw Error("you must provide a module name when calling require() from "
+ + (requirer && requirer.id), base, id);
+
+ // If we have a manifest for requirer, then all it's requirements have been
+ // registered by linker and we should have a `path` to the required module.
+ // Even pseudo-modules like 'chrome', 'self', '@packaging', and '@loader'
+ // have pseudo-paths: exactly those same names.
+ // details see: Bug-697422.
+ let requirement = manifest && manifest.requirements[id];
+ if (!requirement)
+ throw Error("Module: " + (requirer && requirer.id) + ' located at ' +
+ base + " has no authority to load: " + id);
+ let path = requirement.path;
+
+ if (path in this.modules) {
+ module = this.modules[path];
+ }
+ else {
+ let uri = this.uriPrefix + path;
+ module = this.modules[path] = Module.new(id, path, uri);
+ this.load(module);
+ Object.freeze(module);
+ }
+
+ // "magic" modules which have contents that depend upon who imports them
+ // (like "self") are expressed in the Loader's pre-populated 'modules'
+ // table as callable functions, which are given the reference to this
+ // Loader and a copy of the importer's URI
+ //
+ // TODO: Find a better way to implement `self`.
+ // Maybe something like require('self!path/to/data')
+ if (typeof(module) === 'function')
+ module = module(this, requirer);
+
+ return module.exports;
+ },
+
+ // process.process() will eventually cause a call to main() to be evaluated
+ // in the addon's context. This function loads and executes the addon's
+ // entry point module.
+ main: function main(id, path) {
+ try {
+ let uri = this.uriPrefix + path;
+ let module = this.modules[path] = Module.new(id, path, uri);
+ this.load(module); // this is where the addon's main.js finally runs
+ let program = Object.freeze(module).exports;
+
+ if (typeof(program.onUnload) === 'function')
+ this.require('api-utils/unload').when(program.onUnload);
+
+ if (program.main) {
+ let { exit, staticArgs } = this.require('api-utils/system');
+ let { loadReason } = this.require('@packaging');
+ program.main({ loadReason: loadReason, staticArgs: staticArgs },
+ { print: function($) dump($ + '\n'), quit: exit });
+ }
+ } catch (error) {
+ Cu.reportError(error);
+ if (this.globals.console) this.globals.console.exception(error);
+ throw error;
+ }
+ },
+
+ // This is the main entry-point: bootstrap.js calls this when the add-on is
+ // installed. The order of calls is a bit confusing, but here's what
+ // happens (in temporal order):
+ // * process.spawn creates a new XUL 'browser' element which will house the
+ // main addon code. When e10s is active, this uses a real separate OS
+ // process. When e10s is disabled, this element lives in the one original
+ // process. Either way, its API is the same.
+ // * Grab the channel named "require!" and attach a handler which will load
+ // modules (in the chrome process) when requested to by the addon
+ // process. This handler uses Loader.require to import the module, then
+ // calls the module's .initialize() function to connect a new channel.
+ // The remote caller winds up with a channel reference, which they can
+ // use to send messages to the newly loaded module. This is for e10s.
+ // * After the channel handler is attached, process.process() (invoked by
+ // process.spawn()) will use loadScript() to evaluate code in the
+ // 'browser' element (which is where the main addon code starts running),
+ // to do the following:
+ // * create a Loader, initialized with the same manifest and
+ // harness-options.json that we've got
+ // * invoke it's main() method, with the name and path of the addon's
+ // entry module (which comes from linker via harness-options.js, and is
+ // usually main.js). That executes main(), above.
+ // * main() loads the addon's main.js, which executes all top-level
+ // forms. If the module defines an "exports.main=" function, we invoke
+ // that too. This is where the addon finally gets to run.
+ spawn: function spawn(id, path) {
+ let loader = this;
+ let process = this.require('api-utils/process');
+ process.spawn(id, path)(function(addon) {
+ // Listen to `require!` channel's input messages from the add-on process
+ // and load modules being required.
+ addon.channel('require!').input(function({ requirer: { path }, id }) {
+ try {
+ Loader.require.call(loader, path, id).initialize(addon.channel(id));
+ } catch (error) {
+ this.globals.console.exception(error);
+ }
+ });
+ });
+ },
+ unload: function unload(reason, callback) {
+ this.require('api-utils/unload').send(reason, callback);
+ }
+};
+exports.Loader = Loader;
+
+}(this);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events.js b/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events.js
new file mode 100644
index 0000000..da9c93d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events.js
@@ -0,0 +1,169 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+// 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) {
+ // `capture` defaults to `false`.
+ capture = capture || false;
+ 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) {
+ on(element, type, function selfRemovableListener(event) {
+ removeListener(element, type, selfRemovableListener, capture);
+ listener.apply(this, arguments);
+ }, capture);
+}
+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) {
+ 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 }) {
+ category = category || "UIEvents";
+ initializer = initializer || getInitializerName(category);
+ let document = element.ownerDocument;
+ let event = document.createEvent(category);
+ event[initializer].apply(event, [type].concat(settings));
+ element.dispatchEvent(event);
+};
+exports.emit = emit;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events/keys.js b/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events/keys.js
new file mode 100644
index 0000000..155625a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/dom/events/keys.js
@@ -0,0 +1,93 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { emit } = require("../events");
+const { getCodeForKey, toJSON } = require("../../keyboard/utils");
+const { has } = require("../../array");
+const { isString } = require("../../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/tools/addon-sdk-1.4/packages/api-utils/lib/env!.js b/tools/addon-sdk-1.4/packages/api-utils/lib/env!.js
new file mode 100644
index 0000000..642d599
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/env!.js
@@ -0,0 +1,52 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const { messageManager } = require("chrome");
+const { channel } = require("./channel");
+
+module.exports = function load(module) {
+ return {
+ require: function require(id) {
+ // Load required module on the chrome process.
+ channel(messageManager, messageManager, 'require!').sync({
+ requirer: module,
+ id: id
+ });
+ return channel(messageManager, messageManager, id);
+ }
+ };
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/environment.js b/tools/addon-sdk-1.4/packages/api-utils/lib/environment.js
new file mode 100644
index 0000000..5753801
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/environment.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+'use strict';
+
+const { Cc, Ci } = require('chrome');
+const { get, set, exists } = Cc['@mozilla.org/process/environment;1'].
+ getService(Ci.nsIEnvironment);
+
+exports.env = Proxy.create({
+ // XPCOM does not provides a way to enumerate environment variables, so we
+ // just don't support enumeration.
+ getPropertyNames: function() [],
+ getOwnPropertyNames: function() [],
+ enumerate: function() [],
+ keys: function() [],
+ // We do not support freezing, cause it would make it impossible to set new
+ // environment variables.
+ fix: function() undefined,
+ // We present all environment variables as own properties of this object,
+ // so we just delegate this call to `getOwnPropertyDescriptor`.
+ getPropertyDescriptor: function(name) this.getOwnPropertyDescriptor(name),
+ // If environment variable with this name is defined, we generate proprety
+ // descriptor for it, otherwise fall back to `undefined` so that for consumer
+ // this property does not exists.
+ getOwnPropertyDescriptor: function(name) {
+ return !exists(name) ? undefined : {
+ value: get(name),
+ enumerable: false, // Non-enumerable as we don't support enumeration.
+ configurable: true, // Configurable as it may be deleted.
+ writable: true // Writable as we do support set.
+ }
+ },
+
+ // New environment variables can be defined just by defining properties
+ // on this object.
+ defineProperty: function(name, { value }) set(name, value),
+ delete: function(name) set(name, null),
+
+ // We present all properties as own, there for we just delegate to `hasOwn`.
+ has: function(name) this.hasOwn(name),
+ // We do support checks for existence of an environment variable, via `in`
+ // operator on this object.
+ hasOwn: function(name) exists(name),
+
+ // On property get / set we do read / write appropriate environment variables,
+ // please note though, that variables with names of standard object properties
+ // intentionally (so that this behaves as normal object) can not be
+ // read / set.
+ get: function(proxy, name) Object.prototype[name] || get(name) || undefined,
+ set: function(proxy, name, value) Object.prototype[name] || set(name, value)
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/errors.js b/tools/addon-sdk-1.4/packages/api-utils/lib/errors.js
new file mode 100644
index 0000000..56be526
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/errors.js
@@ -0,0 +1,92 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+function logToConsole(e) {
+ console.exception(e);
+}
+
+var catchAndLog = exports.catchAndLog = function(callback,
+ defaultResponse,
+ logException) {
+ if (!logException)
+ logException = logToConsole;
+
+ return function() {
+ try {
+ return callback.apply(this, arguments);
+ } catch (e) {
+ logException(e);
+ return defaultResponse;
+ }
+ };
+};
+
+exports.catchAndLogProps = function catchAndLogProps(object,
+ props,
+ defaultResponse,
+ logException) {
+ if (typeof(props) == "string")
+ props = [props];
+ props.forEach(
+ function(property) {
+ object[property] = catchAndLog(object[property],
+ defaultResponse,
+ logException);
+ });
+};
+
+/**
+ * Catch and return an exception while calling the callback. If the callback
+ * doesn't throw, return the return value of the callback in a way that makes it
+ * possible to distinguish between a return value and an exception.
+ *
+ * This function is useful when you need to pass the result of a call across
+ * a process boundary (across which exceptions don't propagate). It probably
+ * doesn't need to be factored out into this module, since it is only used by
+ * a single caller, but putting it here works around bug 625560.
+ */
+exports.catchAndReturn = function(callback) {
+ return function() {
+ try {
+ return { returnValue: callback.apply(this, arguments) };
+ }
+ catch (exception) {
+ return { exception: exception };
+ }
+ };
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/events.js b/tools/addon-sdk-1.4/packages/api-utils/lib/events.js
new file mode 100644
index 0000000..4e36c00
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/events.js
@@ -0,0 +1,202 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const ERROR_TYPE = 'error',
+ UNCAUGHT_ERROR = 'An error event was dispatched for which there was'
+ + ' no listener.',
+ BAD_LISTENER = 'The event listener must be a function.';
+/**
+ * This object is used to create an `EventEmitter` that, useful for composing
+ * objects that emit events. It implements an interface like `EventTarget` from
+ * DOM Level 2, which is implemented by Node objects in implementations that
+ * support the DOM Event Model.
+ * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget
+ * @see http://nodejs.org/api.html#EventEmitter
+ * @see http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/EventDispatcher.html
+ */
+const eventEmitter = {
+ /**
+ * 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: function on(type, listener) {
+ if ('function' !== typeof listener)
+ throw new Error(BAD_LISTENER);
+ let listeners = this._listeners(type);
+ if (0 > listeners.indexOf(listener))
+ listeners.push(listener);
+ // Use of `_public` is required by the legacy traits code that will go away
+ // once bug-637633 is fixed.
+ return this._public || this;
+ },
+
+ /**
+ * 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: function once(type, listener) {
+ this.on(type, function selfRemovableListener() {
+ this.removeListener(type, selfRemovableListener);
+ listener.apply(this, arguments);
+ });
+ },
+
+ /**
+ * Unregister `listener` for the specified event type.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ removeListener: function removeListener(type, listener) {
+ if ('function' !== typeof listener)
+ throw new Error(BAD_LISTENER);
+ let listeners = this._listeners(type),
+ index = listeners.indexOf(listener);
+ if (0 <= index)
+ listeners.splice(index, 1);
+ // Use of `_public` is required by the legacy traits code, that will go away
+ // once bug-637633 is fixed.
+ return this._public || this;
+ },
+
+ /**
+ * Hash of listeners on this EventEmitter.
+ */
+ _events: null,
+
+ /**
+ * Returns an array of listeners for the specified event `type`. This array
+ * can be manipulated, e.g. to remove listeners.
+ * @param {String} type
+ * The type of event.
+ */
+ _listeners: function listeners(type) {
+ let events = this._events || (this._events = {});
+ return events[type] || (events[type] = []);
+ },
+
+ /**
+ * Execute each of the listeners in order with the supplied arguments.
+ * Returns `true` if listener for this event was called, `false` if there are
+ * no listeners for this event `type`.
+ *
+ * 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 propagate to a
+ * caller of this method.
+ *
+ * **It's recommended to have a default 'error' listener in all the complete
+ * composition that in worst case may dump errors to the console.**
+ *
+ * @param {String} type
+ * The type of event.
+ * @params {Object|Number|String|Boolean}
+ * Arguments that will be passed to listeners.
+ * @returns {Boolean}
+ */
+ _emit: function _emit(type, event) {
+ let args = Array.slice(arguments);
+ // Use of `_public` is required by the legacy traits code that will go away
+ // once bug-637633 is fixed.
+ args.unshift(this._public || this);
+ return this._emitOnObject.apply(this, args);
+ },
+
+ /**
+ * A version of _emit that lets you specify the object on which listeners are
+ * called. This is a hack that is sometimes necessary when such an object
+ * (exports, for example) cannot be an EventEmitter for some reason, but other
+ * object(s) managing events for the object are EventEmitters. Once bug
+ * 577782 is fixed, this method shouldn't be necessary.
+ *
+ * @param {object} targetObj
+ * The object on which listeners will be called.
+ * @param {string} type
+ * The event name.
+ * @param {value} event
+ * The first argument to pass to listeners.
+ * @param {value} ...
+ * More arguments to pass to listeners.
+ * @returns {boolean}
+ */
+ _emitOnObject: function _emitOnObject(targetObj, type, event /* , ... */) {
+ let listeners = this._listeners(type).slice(0);
+ // If there is no 'error' event listener then throw.
+ if (type === ERROR_TYPE && !listeners.length)
+ console.exception(event);
+ if (!listeners.length)
+ return false;
+ let params = Array.slice(arguments, 2);
+ for each (let listener in listeners) {
+ try {
+ listener.apply(targetObj, params);
+ } catch(e) {
+ this._emit('error', e);
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Removes all the event listeners for the specified event `type`.
+ * @param {String} type
+ * The type of event.
+ */
+ _removeAllListeners: function _removeAllListeners(type) {
+ this._listeners(type).splice(0);
+ return this;
+ }
+};
+exports.EventEmitter = require("./traits").Trait.compose(eventEmitter);
+exports.EventEmitterTrait = require('./light-traits').Trait(eventEmitter);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/events/assembler.js b/tools/addon-sdk-1.4/packages/api-utils/lib/events/assembler.js
new file mode 100644
index 0000000..26860d6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/events/assembler.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Trait } = require("../light-traits");
+const { removeListener, on } = require("../dom/events");
+
+/**
+ * Trait may be used for building objects / composing traits that wish to handle
+ * multiple dom events from multiple event targets in one place. 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 = Trait({
+ /**
+ * 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: Trait.required,
+ /**
+ * Array of supported event names.
+ * @type {String[]}
+ */
+ supportedEventsTypes: Trait.required,
+ /**
+ * 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/tools/addon-sdk-1.4/packages/api-utils/lib/file.js b/tools/addon-sdk-1.4/packages/api-utils/lib/file.js
new file mode 100644
index 0000000..30cb356
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/file.js
@@ -0,0 +1,227 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsINarwhal.
+ *
+ * The Initial Developer of the Original Code is
+ * Irakli Gozalishvili <rfobic@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <rfobic@gmail.com>
+ * Atul Varma <atul@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+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 = parseInt("0644"); // 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, parseInt("0755")); // 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/tools/addon-sdk-1.4/packages/api-utils/lib/find-tests.js b/tools/addon-sdk-1.4/packages/api-utils/lib/find-tests.js
new file mode 100644
index 0000000..be29d19
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/find-tests.js
@@ -0,0 +1 @@
+// this file left intentionally blank
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/globals!.js b/tools/addon-sdk-1.4/packages/api-utils/lib/globals!.js
new file mode 100644
index 0000000..27fd8ba
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/globals!.js
@@ -0,0 +1,113 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+let { Cc, Ci } = require('chrome');
+let { PlainTextConsole } = require('./plain-text-console');
+let options = require('@packaging');
+
+// On windows dump does not writes into stdout so cfx can't read thous dumps.
+// To workaround this issue we write to a special file from which cfx will
+// read and print to the console.
+// For more details see: bug-673383
+exports.dump = (function define(global) {
+ const PR_WRONLY = 0x02;
+ const PR_CREATE_FILE = 0x08;
+ const PR_APPEND = 0x10;
+ let print = Object.getPrototypeOf(global).dump
+ if (print) return print;
+ if ('logFile' in options) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(options.logFile);
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, PR_WRONLY|PR_CREATE_FILE|PR_APPEND, -1, 0);
+
+ return function print(message) {
+ message = String(message);
+ stream.write(message, message.length);
+ stream.flush();
+ };
+ }
+ return dump;
+})(this);
+
+// Override the default Iterator function with one that passes
+// a second argument to custom iterator methods that identifies
+// the call as originating from an Iterator function so the custom
+// iterator method can return [key, value] pairs just like default
+// iterators called via the default Iterator function.
+exports.Iterator = (function(DefaultIterator) {
+ return function Iterator(obj, keysOnly) {
+ if ("__iterator__" in obj && !keysOnly)
+ return obj.__iterator__.call(obj, false, true);
+ return DefaultIterator(obj, keysOnly);
+ };
+})(Iterator);
+
+// TODO: Remove memory from the globals, as it raises security concerns and
+// there is no real reason to favor global memory over
+// `require('api-utils/memory')`. For details see: Bug-620559
+exports.memory = require('./memory');
+exports.console = new PlainTextConsole(exports.dump);
+
+// Provide CommonJS `define` to allow authoring modules in a format that can be
+// loaded both into jetpack and into browser via AMD loaders.
+Object.defineProperty(exports, 'define', {
+ // `define` is provided as a lazy getter that binds below defined `define`
+ // function to the module scope, so that require, exports and module
+ // variables remain accessible.
+ configurable: true,
+ get: (function() {
+ function define(factory) {
+ factory = Array.slice(arguments).pop();
+ factory.call(this, this.require, this.exports, this.module);
+ }
+
+ return function getter() {
+ // Redefine `define` as a static property to make sure that module
+ // gets access to the same function so that `define === define` is
+ // `true`.
+ Object.defineProperty(this, 'define', {
+ configurable: false,
+ value: define.bind(this)
+ });
+ return this.define;
+ }
+ })()
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/hidden-frame.js b/tools/addon-sdk-1.4/packages/api-utils/lib/hidden-frame.js
new file mode 100644
index 0000000..241a4bc
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/hidden-frame.js
@@ -0,0 +1,200 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Felipe Gomes <felipc@gmail.com> (Original Author)
+ * Myk Melez <myk@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc, Ci} = require("chrome");
+const errors = require("./errors");
+const apiUtils = require("./api-utils");
+const timer = require("./timer");
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+let hostFrame, hostDocument, hiddenWindow, isHostFrameReady = false;
+
+if (!require("./xul-app").isOneOf(["Firefox", "Thunderbird"])) {
+ throw new Error([
+ "The hidden-frame module currently supports only Firefox and Thunderbird. ",
+ "In the future, we would like it to support other applications, however. ",
+ "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
+ "information."
+ ].join(""));
+}
+
+let appShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+hiddenWindow = appShellService.hiddenDOMWindow;
+
+if (!hiddenWindow) {
+ throw new Error([
+ "The hidden-frame module needs an app that supports a hidden window. ",
+ "We would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more information."
+ ].join(""));
+}
+
+// Check if we can use the hidden window itself to host our iframes.
+// If it's not a suitable host, the hostFrame will be lazily created
+// by the first HiddenFrame instance.
+if (hiddenWindow.location.protocol == "chrome:" &&
+ (hiddenWindow.document.contentType == "application/vnd.mozilla.xul+xml" ||
+ hiddenWindow.document.contentType == "application/xhtml+xml")) {
+ hostFrame = hiddenWindow;
+ hostDocument = hiddenWindow.document;
+ isHostFrameReady = true;
+}
+
+function setHostFrameReady() {
+ hostDocument = hostFrame.contentDocument;
+ hostFrame.removeEventListener("DOMContentLoaded", setHostFrameReady, false);
+ isHostFrameReady = true;
+}
+
+// This cache is used to access friend properties between functions
+// without exposing them on the public API.
+let cache = [];
+
+exports.HiddenFrame = apiUtils.publicConstructor(HiddenFrame);
+
+function HiddenFrame(options) {
+ options = options || {};
+ let self = this;
+
+ for each (let [key, val] in Iterator(apiUtils.validateOptions(options, {
+ onReady: {
+ is: ["undefined", "function", "array"],
+ ok: function(v) {
+ if (apiUtils.getTypeOf(v) === "array") {
+ // make sure every item is a function
+ return v.every(function (item) typeof(item) === "function")
+ }
+ return true;
+ }
+ }
+ }))) {
+ if (typeof(val) != "undefined")
+ options[key] = val;
+ }
+
+ require("./collection").addCollectionProperty(this, "onReady");
+ if (options.onReady)
+ this.onReady.add(options.onReady);
+
+ if (!hostFrame) {
+ hostFrame = hiddenWindow.document.createElement("iframe");
+
+ // ugly ugly hack. This is the most lightweight chrome:// file I could find on the tree
+ // This hack should be removed by proper platform support on bug 565388
+ hostFrame.setAttribute("src", "chrome://global/content/mozilla.xhtml");
+ hostFrame.addEventListener("DOMContentLoaded", setHostFrameReady, false);
+
+ hiddenWindow.document.body.appendChild(hostFrame);
+ }
+
+ this.toString = function toString() "[object Frame]";
+}
+
+exports.add = function JP_SDK_Frame_add(frame) {
+ if (!(frame instanceof HiddenFrame))
+ throw new Error("The object to be added must be a HiddenFrame.");
+
+ // This instance was already added.
+ if (cache.filter(function (v) v.frame === frame)[0])
+ return frame;
+
+ function createElement() {
+ hostFrame.removeEventListener("DOMContentLoaded", createElement, false);
+
+ let element = hostDocument.createElementNS(XUL_NS, "iframe");
+
+ element.setAttribute("type", "content");
+ hostDocument.documentElement.appendChild(element);
+
+ /* Public API: hiddenFrame.element */
+ frame.__defineGetter__("element", function () element);
+
+ // Notify consumers that the frame is ready.
+ function onReadyListener(event) {
+ element.removeEventListener("DOMContentLoaded", onReadyListener, false);
+ if (event.target == element.contentDocument) {
+ for (let handler in frame.onReady)
+ errors.catchAndLog(function () handler.call(frame))();
+ }
+ }
+ element.addEventListener("DOMContentLoaded", onReadyListener, false);
+
+ cache.push({
+ frame: frame,
+ element: element,
+ unload: function unload() {
+ hostDocument.documentElement.removeChild(element);
+ }
+ });
+ }
+
+ /* Begin element construction or schedule it for later */
+ if (isHostFrameReady) {
+ createElement();
+ } else {
+ hostFrame.addEventListener("DOMContentLoaded", createElement, false);
+ }
+
+ return frame;
+}
+
+exports.remove = function remove(frame) {
+ if (!(frame instanceof HiddenFrame))
+ throw new Error("The object to be removed must be a HiddenFrame.");
+
+ let entry = cache.filter(function (v) v.frame === frame)[0];
+ if (!entry)
+ return;
+
+ entry.unload();
+ cache.splice(cache.indexOf(entry), 1);
+}
+
+require("./unload").when(function () {
+ for each (let entry in cache.slice())
+ exports.remove(entry.frame);
+
+ if (hostFrame && hostFrame !== hiddenWindow)
+ hiddenWindow.document.body.removeChild(hostFrame);
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/httpd.js b/tools/addon-sdk-1.4/packages/api-utils/lib/httpd.js
new file mode 100644
index 0000000..ebf3643
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/httpd.js
@@ -0,0 +1,5202 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is the httpd.js server.
+*
+* The Initial Developer of the Original Code is
+* Mozilla Corporation.
+* Portions created by the Initial Developer are Copyright (C) 2006
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+* Darin Fisher (v1, netwerk/test/TestServ.js)
+* Christian Biesinger (v2, netwerk/test/unit/head_http_server.js)
+* Jeff Walden <jwalden+code@mit.edu> (v3, netwerk/test/httpserver/httpd.js)
+* Robert Sayre <sayrer@gmail.com>
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+/*
+* 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.
+*/
+
+
+var {components,Cc,Ci,Cr,Cu} = require("chrome");
+components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const CC = components.Constructor;
+
+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-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 =
+ 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(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.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 "<Connection(" + this.number +
+ (this.request ? ", " + this.request.path : "") +"): " +
+ (this._closed ? "closed" : "open") + ">";
+ }
+};
+
+
+
+/** 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 = '<html>\
+<head>\
+<title>' + path + '</title>\
+</head>\
+<body>\
+<h1>' + path + '</h1>\
+<ol style="list-style-type: none">';
+
+ 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 = '<li><a href="' + encodeURIComponent(name) + sep + '">' +
+ htmlEscape(name) + sep +
+ '</a></li>';
+
+ body += item;
+ }
+ catch (e) { /* some file system error, ignore the file */ }
+ }
+
+ body += ' </ol>\
+</body>\
+</html>';
+
+ 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, parseInt("444", 8),
+ 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 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
+ {
+ 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.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, parseInt("444", 8),
+ 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, parseInt("444", 8),
+ 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;
+ }
+
+ 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
+ // "<file>/foo/../bar" but prevents paths such as "<file>/../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 = "<html>\
+<head><title>403 Forbidden</title></head>\
+<body>\
+<h1>403 Forbidden</h1>\
+</body>\
+</html>";
+ 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 = "<html>\
+<head><title>404 Not Found</title></head>\
+<body>\
+<h1>404 Not Found</h1>\
+<p>\
+<span style='font-family: monospace;'>" +
+ htmlEscape(metadata.path) +
+ "</span> was not found.\
+</p>\
+</body>\
+</html>";
+ 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 = "<html>\
+<head>\
+<title>416 Requested Range Not Satisfiable</title></head>\
+<body>\
+<h1>416 Requested Range Not Satisfiable</h1>\
+<p>The byte range was not valid for the\
+requested resource.\
+</p>\
+</body>\
+</html>";
+ 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 = "<html>\
+<head><title>500 Internal Server Error</title></head>\
+<body>\
+<h1>500 Internal Server Error</h1>\
+<p>Something's broken in this server and\
+needs to be fixed.</p>\
+</body>\
+</html>";
+ 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 = "<html>\
+<head><title>501 Not Implemented</title></head>\
+<body>\
+<h1>501 Not Implemented</h1>\
+<p>This server is not (yet) Apache.</p>\
+</body>\
+</html>";
+ 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 = "<html>\
+<head><title>505 HTTP Version Not Supported</title></head>\
+<body>\
+<h1>505 HTTP Version Not Supported</h1>\
+<p>This server only supports HTTP/1.0 and HTTP/1.1\
+connections.</p>\
+</body>\
+</html>";
+ 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 = "<html>\
+<head><title>httpd.js</title></head>\
+<body>\
+<h1>httpd.js</h1>\
+<p>If you're seeing this page, httpd.js is up and\
+serving requests! Now set a base path and serve some\
+files!</p>\
+</body>\
+</html>";
+
+ 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 = *<any CHAR except CTLs or separators>
+// CHAR = <any US-ASCII character (0-127)>
+// CTL = <any US-ASCII control character (0-31) and DEL (127)>
+// 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, excluding CR, LF>
+ // TEXT = <any OCTET except CTLs, but including LWS>
+ //
+ // 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 = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ // TEXT = <any OCTET except CTLs,
+ // but including LWS>
+ // LWS = [CRLF] 1*( SP | HT )
+ //
+ // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ // qdtext = <any TEXT except <">>
+ // quoted-pair = "\" CHAR
+ // CHAR = <any US-ASCII character (octets 0 - 127)>
+
+ // 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 <http://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
+ 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.identity.setPrimary("http", "localhost", port);
+ 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.identity.setPrimary("http", "localhost", port);
+ srv.start(port);
+ return srv;
+}
+
+exports.nsHttpServer = nsHttpServer;
+exports.ScriptableInputStream = ScriptableInputStream;
+exports.server = server;
+exports.startServerAsync = startServerAsync;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/hotkeys.js b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/hotkeys.js
new file mode 100644
index 0000000..6851671
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/hotkeys.js
@@ -0,0 +1,141 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ * Paul Vet <original.roju@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+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://mxr.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/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/observer.js b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/observer.js
new file mode 100644
index 0000000..6968551
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/observer.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Trait } = require("../light-traits");
+const { EventEmitterTrait: EventEmitter } = require("../events");
+const { DOMEventAssembler } = require("../events/assembler");
+const { browserWindowIterator, 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 = Trait.compose(DOMEventAssembler, EventEmitter).create({
+ /**
+ * Method is implemented by `EventEmitter` and is used just for emitting
+ * events on registered listeners.
+ */
+ _emit: Trait.required,
+ /**
+ * 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: function handleEvent(event) {
+ this._emit(event.type, event, event.target.ownerDocument.defaultView);
+ }
+});
+
+// Adding each opened window to a list of observed windows.
+windowObserver.on("open", function onOpen(window) {
+ if (isBrowser(window))
+ observer.observe(window);
+});
+// Removing each closed window form the list of observed windows.
+windowObserver.on("close", function onClose(window) {
+ if (isBrowser(window))
+ observer.ignore(window);
+});
+
+// Making observer aware of already opened windows.
+for each (let window in browserWindowIterator())
+ observer.observe(window);
+
+exports.observer = observer;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/utils.js
new file mode 100644
index 0000000..0c9a7fb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/keyboard/utils.js
@@ -0,0 +1,220 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ * Henri Wiechers <hwiechers@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const runtime = require("../runtime");
+const { isString } = require("../type");
+const array = require("../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://mxr.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/tools/addon-sdk-1.4/packages/api-utils/lib/light-traits.js b/tools/addon-sdk-1.4/packages/api-utils/lib/light-traits.js
new file mode 100644
index 0000000..1afaad5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/light-traits.js
@@ -0,0 +1,626 @@
+/* vim:ts=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <rfobic@gmail.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+// `var` is being used in the module in order to make it reusable in
+// environments in which `let` is not yet supported.
+
+// Shortcut to `Object.prototype.hasOwnProperty.call`.
+// owns(object, name) would be the same as
+// Object.prototype.hasOwnProperty.call(object, name);
+var owns = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
+
+/**
+ * Whether or not given property descriptors are equivalent. They are
+ * equivalent either if both are marked as 'conflict' or 'required' property
+ * or if all the properties of descriptors are equal.
+ * @param {Object} actual
+ * @param {Object} expected
+ */
+function equivalentDescriptors(actual, expected) {
+ return (actual.conflict && expected.conflict) ||
+ (actual.required && expected.required) ||
+ equalDescriptors(actual, expected);
+}
+/**
+ * Whether or not given property descriptors define equal properties.
+ */
+function equalDescriptors(actual, expected) {
+ return actual.get === expected.get &&
+ actual.set === expected.set &&
+ actual.value === expected.value &&
+ !!actual.enumerable === !!expected.enumerable &&
+ !!actual.configurable === !!expected.configurable &&
+ !!actual.writable === !!expected.writable;
+}
+
+// Utilities that throwing exceptions for a properties that are marked
+// as "required" or "conflict" properties.
+function throwConflictPropertyError(name) {
+ throw new Error("Remaining conflicting property: `" + name + "`");
+}
+function throwRequiredPropertyError(name) {
+ throw new Error("Missing required property: `" + name + "`");
+}
+
+/**
+ * Generates custom **required** property descriptor. Descriptor contains
+ * non-standard property `required` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function RequiredPropertyDescriptor(name) {
+ // Creating function by binding first argument to a property `name` on the
+ // `throwConflictPropertyError` function. Created function is used as a
+ // getter & setter of the created property descriptor. This way we ensure
+ // that we throw exception late (on property access) if object with
+ // `required` property was instantiated using built-in `Object.create`.
+ var accessor = throwRequiredPropertyError.bind(null, name);
+ return { get: accessor, set: accessor, required: true };
+}
+
+/**
+ * Generates custom **conflicting** property descriptor. Descriptor contains
+ * non-standard property `conflict` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function ConflictPropertyDescriptor(name) {
+ // For details see `RequiredPropertyDescriptor` since idea is same.
+ var accessor = throwConflictPropertyError.bind(null, name);
+ return { get: accessor, set: accessor, conflict: true };
+}
+
+/**
+ * Tests if property is marked as `required` property.
+ */
+function isRequiredProperty(object, name) {
+ return !!object[name].required;
+}
+
+/**
+ * Tests if property is marked as `conflict` property.
+ */
+function isConflictProperty(object, name) {
+ return !!object[name].conflict;
+}
+
+/**
+ * Function tests whether or not method of the `source` object with a given
+ * `name` is inherited from `Object.prototype`.
+ */
+function isBuiltInMethod(name, source) {
+ var target = Object.prototype[name];
+
+ // If methods are equal then we know it's `true`.
+ return target == source ||
+ // If `source` object comes form a different sandbox `==` will evaluate
+ // to `false`, in that case we check if functions names and sources match.
+ (String(target) === String(source) && target.name === source.name);
+}
+
+/**
+ * Function overrides `toString` and `constructor` methods of a given `target`
+ * object with a same-named methods of a given `source` if methods of `target`
+ * object are inherited / copied from `Object.prototype`.
+ * @see create
+ */
+function overrideBuiltInMethods(target, source) {
+ if (isBuiltInMethod("toString", target.toString)) {
+ Object.defineProperty(target, "toString", {
+ value: source.toString,
+ configurable: true,
+ enumerable: false
+ });
+ }
+
+ if (isBuiltInMethod("constructor", target.constructor)) {
+ Object.defineProperty(target, "constructor", {
+ value: source.constructor,
+ configurable: true,
+ enumerable: false
+ });
+ }
+}
+
+/**
+ * Composes new trait with the same own properties as the original trait,
+ * except that all property names appearing in the first argument are replaced
+ * by "required" property descriptors.
+ * @param {String[]} keys
+ * Array of strings property names.
+ * @param {Object} trait
+ * A trait some properties of which should be excluded.
+ * @returns {Object}
+ * @example
+ * var newTrait = exclude(["name", ...], trait)
+ */
+function exclude(names, trait) {
+ var map = {};
+
+ Object.keys(trait).forEach(function(name) {
+
+ // If property is not excluded (the array of names does not contain it),
+ // or it is a "required" property, copy it to the property descriptor `map`
+ // that will be used for creation of resulting trait.
+ if (!~names.indexOf(name) || isRequiredProperty(trait, name))
+ map[name] = { value: trait[name], enumerable: true };
+
+ // For all the `names` in the exclude name array we create required
+ // property descriptors and copy them to the `map`.
+ else
+ map[name] = { value: RequiredPropertyDescriptor(name), enumerable: true };
+ });
+
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * Composes new instance of `Trait` with a properties of a given `trait`,
+ * except that all properties whose name is an own property of `renames` will
+ * be renamed to `renames[name]` and a `"required"` property for name will be
+ * added instead.
+ *
+ * For each renamed property, a required property is generated. If
+ * the `renames` map two properties to the same name, a conflict is generated.
+ * If the `renames` map a property to an existing unrenamed property, a
+ * conflict is generated.
+ *
+ * @param {Object} renames
+ * An object whose own properties serve as a mapping from old names to new
+ * names.
+ * @param {Object} trait
+ * A new trait with renamed properties.
+ * @returns {Object}
+ * @example
+ *
+ * // Return trait with `bar` property equal to `trait.foo` and with
+ * // `foo` and `baz` "required" properties.
+ * var renamedTrait = rename({ foo: "bar", baz: null }), trait);
+ *
+ * // t1 and t2 are equivalent traits
+ * var t1 = rename({a: "b"}, t);
+ * var t2 = compose(exclude(["a"], t), { a: { required: true }, b: t[a] });
+ */
+function rename(renames, trait) {
+ var map = {};
+
+ // Loop over all the properties of the given `trait` and copy them to a
+ // property descriptor `map` that will be used for the creation of the
+ // resulting trait. Also, rename properties in the `map` as specified by
+ // `renames`.
+ Object.keys(trait).forEach(function(name) {
+ var alias;
+
+ // If the property is in the `renames` map, and it isn't a "required"
+ // property (which should never need to be aliased because "required"
+ // properties never conflict), then we must try to rename it.
+ if (owns(renames, name) && !isRequiredProperty(trait, name)) {
+ alias = renames[name];
+
+ // If the `map` already has the `alias`, and it isn't a "required"
+ // property, that means the `alias` conflicts with an existing name for a
+ // provided trait (that can happen if >=2 properties are aliased to the
+ // same name). In this case we mark it as a conflicting property.
+ // Otherwise, everything is fine, and we copy property with an `alias`
+ // name.
+ if (owns(map, alias) && !map[alias].value.required) {
+ map[alias] = {
+ value: ConflictPropertyDescriptor(alias),
+ enumerable: true
+ };
+ }
+ else {
+ map[alias] = {
+ value: trait[name],
+ enumerable: true
+ };
+ }
+
+ // Regardless of whether or not the rename was successful, we check to
+ // see if the original `name` exists in the map (such a property
+ // could exist if previous another property was aliased to this `name`).
+ // If it isn't, we mark it as "required", to make sure the caller
+ // provides another value for the old name, which methods of the trait
+ // might continue to reference.
+ if (!owns(map, name)) {
+ map[name] = {
+ value: RequiredPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+
+ // Otherwise, either the property isn't in the `renames` map (thus the
+ // caller is not trying to rename it) or it is a "required" property.
+ // Either way, we don't have to alias the property, we just have to copy it
+ // to the map.
+ else {
+ // The property isn't in the map yet, so we copy it over.
+ if (!owns(map, name)) {
+ map[name] = { value: trait[name], enumerable: true };
+ }
+
+ // The property is already in the map (that means another property was
+ // aliased with this `name`, which creates a conflict if the property is
+ // not marked as "required"), so we have to mark it as a "conflict"
+ // property.
+ else if (!isRequiredProperty(trait, name)) {
+ map[name] = {
+ value: ConflictPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+ });
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * Composes new resolved trait, with all the same properties as the original
+ * `trait`, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to `resolutions[name]`.
+ *
+ * If `resolutions[name]` is `null`, the value is mapped to a property
+ * descriptor that is marked as a "required" property.
+ */
+function resolve(resolutions, trait) {
+ var renames = {};
+ var exclusions = [];
+
+ // Go through each mapping in `resolutions` object and distribute it either
+ // to `renames` or `exclusions`.
+ Object.keys(resolutions).forEach(function(name) {
+
+ // If `resolutions[name]` is a truthy value then it's a mapping old -> new
+ // so we copy it to `renames` map.
+ if (resolutions[name])
+ renames[name] = resolutions[name];
+
+ // Otherwise it's not a mapping but an exclusion instead in which case we
+ // add it to the `exclusions` array.
+ else
+ exclusions.push(name);
+ });
+
+ // First `exclude` **then** `rename` and order is important since
+ // `exclude` and `rename` are not associative.
+ return rename(renames, exclude(exclusions, trait));
+}
+
+/**
+ * Create a Trait (a custom property descriptor map) that represents the given
+ * `object`'s own properties. Property descriptor map is a "custom", because it
+ * inherits from `Trait.prototype` and it's property descriptors may contain
+ * two attributes that is not part of the ES5 specification:
+ *
+ * - "required" (this property must be provided by another trait
+ * before an instance of this trait can be created)
+ * - "conflict" (when the trait is composed with another trait,
+ * a unique value for this property is provided by two or more traits)
+ *
+ * Data properties bound to the `Trait.required` singleton exported by
+ * this module will be marked as "required" properties.
+ *
+ * @param {Object} object
+ * Map of properties to compose trait from.
+ * @returns {Trait}
+ * Trait / Property descriptor map containing all the own properties of the
+ * given argument.
+ */
+function trait(object) {
+ var map;
+ var trait = object;
+
+ if (!(object instanceof Trait)) {
+ // If the passed `object` is not already an instance of `Trait`, we create
+ // a property descriptor `map` containing descriptors for the own properties
+ // of the given `object`. `map` is then used to create a `Trait` instance
+ // after all properties are mapped. Note that we can't create a trait and
+ // then just copy properties into it since that will fail for inherited
+ // read-only properties.
+ map = {};
+
+ // Each own property of the given `object` is mapped to a data property
+ // whose value is a property descriptor.
+ Object.keys(object).forEach(function (name) {
+
+ // If property of an `object` is equal to a `Trait.required`, it means
+ // that it was marked as "required" property, in which case we map it
+ // to "required" property.
+ if (Trait.required ==
+ Object.getOwnPropertyDescriptor(object, name).value) {
+ map[name] = {
+ value: RequiredPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ // Otherwise property is mapped to it's property descriptor.
+ else {
+ map[name] = {
+ value: Object.getOwnPropertyDescriptor(object, name),
+ enumerable: true
+ };
+ }
+ });
+
+ trait = Object.create(Trait.prototype, map);
+ }
+ return trait;
+}
+
+/**
+ * Compose a property descriptor map that inherits from `Trait.prototype` and
+ * contains property descriptors for all the own properties of the passed
+ * traits.
+ *
+ * If two or more traits have own properties with the same name, the returned
+ * trait will contain a "conflict" property for that name. Composition is a
+ * commutative and associative operation, and the order of its arguments is
+ * irrelevant.
+ */
+function compose(trait1, trait2/*, ...*/) {
+ // Create a new property descriptor `map` to which all the own properties
+ // of the passed traits are copied. This map will be used to create a `Trait`
+ // instance that will be the result of this composition.
+ var map = {};
+
+ // Properties of each passed trait are copied to the composition.
+ Array.prototype.forEach.call(arguments, function(trait) {
+ // Copying each property of the given trait.
+ Object.keys(trait).forEach(function(name) {
+
+ // If `map` already owns a property with the `name` and it is not
+ // marked "required".
+ if (owns(map, name) && !map[name].value.required) {
+
+ // If the source trait's property with the `name` is marked as
+ // "required", we do nothing, as the requirement was already resolved
+ // by a property in the `map` (because it already contains a
+ // non-required property with that `name`). But if properties are just
+ // different, we have a name clash and we substitute it with a property
+ // that is marked "conflict".
+ if (!isRequiredProperty(trait, name) &&
+ !equivalentDescriptors(map[name].value, trait[name])
+ ) {
+ map[name] = {
+ value: ConflictPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+
+ // Otherwise, the `map` does not have an own property with the `name`, or
+ // it is marked "required". Either way, the trait's property is copied to
+ // the map (if the property of the `map` is marked "required", it is going
+ // to be resolved by the property that is being copied).
+ else {
+ map[name] = { value: trait[name], enumerable: true };
+ }
+ });
+ });
+
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * `defineProperties` is like `Object.defineProperties`, except that it
+ * ensures that:
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "required" property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "conflict" property.
+ * @param {Object} object
+ * Object to define properties on.
+ * @param {Object} properties
+ * Properties descriptor map.
+ * @returns {Object}
+ * `object` that was passed as a first argument.
+ */
+function defineProperties(object, properties) {
+
+ // Create a map into which we will copy each verified property from the given
+ // `properties` description map. We use it to verify that none of the
+ // provided properties is marked as a "conflict" property and that all
+ // "required" properties are resolved by a property of an `object`, so we
+ // can throw an exception before mutating object if that isn't the case.
+ var verifiedProperties = {};
+
+ // Coping each property from a given `properties` descriptor map to a
+ // verified map of property descriptors.
+ Object.keys(properties).forEach(function(name) {
+
+ // If property is marked as "required" property and we don't have a same
+ // named property in a given `object` we throw an exception. If `object`
+ // has same named property just skip this property since required property
+ // is was inherited and there for requirement was satisfied.
+ if (isRequiredProperty(properties, name)) {
+ if (!(name in object))
+ throwRequiredPropertyError(name);
+ }
+
+ // If property is marked as "conflict" property we throw an exception.
+ else if (isConflictProperty(properties, name)) {
+ throwConflictPropertyError(name);
+ }
+
+ // If property is not marked neither as "required" nor "conflict" property
+ // we copy it to verified properties map.
+ else {
+ verifiedProperties[name] = properties[name];
+ }
+ });
+
+ // If no exceptions were thrown yet, we know that our verified property
+ // descriptor map has no properties marked as "conflict" or "required",
+ // so we just delegate to the built-in `Object.defineProperties`.
+ return Object.defineProperties(object, verifiedProperties);
+}
+
+/**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "required" property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "conflict" property.
+ * @param {Object} prototype
+ * prototype of the composed object
+ * @param {Object} properties
+ * Properties descriptor map.
+ * @returns {Object}
+ * An object that inherits form a given `prototype` and implements all the
+ * properties defined by a given `properties` descriptor map.
+ */
+function create(prototype, properties) {
+
+ // Creating an instance of the given `prototype`.
+ var object = Object.create(prototype);
+
+ // Overriding `toString`, `constructor` methods if they are just inherited
+ // from `Object.prototype` with a same named methods of the `Trait.prototype`
+ // that will have more relevant behavior.
+ overrideBuiltInMethods(object, Trait.prototype);
+
+ // Trying to define given `properties` on the `object`. We use our custom
+ // `defineProperties` function instead of build-in `Object.defineProperties`
+ // that behaves exactly the same, except that it will throw if any
+ // property in the given `properties` descriptor is marked as "required" or
+ // "conflict" property.
+ return defineProperties(object, properties);
+}
+
+/**
+ * Composes new trait. If two or more traits have own properties with the
+ * same name, the new trait will contain a "conflict" property for that name.
+ * "compose" is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ *
+ * **Note:** Use `Trait.compose` instead of calling this function with more
+ * than one argument. The multiple-argument functionality is strictly for
+ * backward compatibility.
+ *
+ * @params {Object} trait
+ * Takes traits as an arguments
+ * @returns {Object}
+ * New trait containing the combined own properties of all the traits.
+ * @example
+ * var newTrait = compose(trait_1, trait_2, ..., trait_N)
+ */
+function Trait(trait1, trait2) {
+
+ // If the function was called with one argument, the argument should be
+ // an object whose properties are mapped to property descriptors on a new
+ // instance of Trait, so we delegate to the trait function.
+ // If the function was called with more than one argument, those arguments
+ // should be instances of Trait or plain property descriptor maps
+ // whose properties should be mixed into a new instance of Trait,
+ // so we delegate to the compose function.
+
+ return trait2 === undefined ? trait(trait1) : compose.apply(null, arguments);
+}
+
+Object.freeze(Object.defineProperties(Trait.prototype, {
+ toString: {
+ value: function toString() {
+ return "[object " + this.constructor.name + "]";
+ }
+ },
+
+ /**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - An exception is thrown if this trait defines a property that is
+ * marked as required property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if this trait contains property that is
+ * marked as "conflict" property.
+ * @param {Object}
+ * prototype of the compared object
+ * @returns {Object}
+ * An object with all of the properties described by the trait.
+ */
+ create: {
+ value: function createTrait(prototype) {
+ return create(undefined === prototype ? Object.prototype : prototype,
+ this);
+ },
+ enumerable: true
+ },
+
+ /**
+ * Composes a new resolved trait, with all the same properties as the original
+ * trait, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to the value of `resolutions[name]`. If
+ * `resolutions[name]` is `null`, the property is marked as "required".
+ * @param {Object} resolutions
+ * An object whose own properties serve as a mapping from old names to new
+ * names, or to `null` if the property should be excluded.
+ * @returns {Object}
+ * New trait with the same own properties as the original trait but renamed.
+ */
+ resolve: {
+ value: function resolveTrait(resolutions) {
+ return resolve(resolutions, this);
+ },
+ enumerable: true
+ }
+}));
+
+/**
+ * @see compose
+ */
+Trait.compose = Object.freeze(compose);
+Object.freeze(compose.prototype);
+
+/**
+ * Constant singleton, representing placeholder for required properties.
+ * @type {Object}
+ */
+Trait.required = Object.freeze(Object.create(Object.prototype, {
+ toString: {
+ value: Object.freeze(function toString() {
+ return "<Trait.required>";
+ })
+ }
+}));
+Object.freeze(Trait.required.toString.prototype);
+
+exports.Trait = Object.freeze(Trait);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/list.js b/tools/addon-sdk-1.4/packages/api-utils/lib/list.js
new file mode 100644
index 0000000..8643a0a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/list.js
@@ -0,0 +1,147 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Trait } = require('./traits');
+
+/**
+ * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/list
+ */
+const Iterable = Trait.compose({
+ /**
+ * Hash map of key-values to iterate over.
+ * Note: That this property can be a getter if you need dynamic behavior.
+ * @type {Object}
+ */
+ _keyValueMap: Trait.required,
+ /**
+ * Custom iterator providing `Iterable`s enumeration behavior.
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let map = this._keyValueMap;
+ for (let key in map)
+ yield onKeyValue ? [key, map[key]] : onKeys ? key : map[key];
+ }
+});
+exports.Iterable = Iterable;
+
+/**
+ * An ordered collection (also known as a sequence) disallowing duplicate
+ * elements. List is composed out of `Iterable` there for it provides custom
+ * enumeration behavior that is similar to array (enumerates only on the
+ * elements of the list). List is a base trait and is meant to be a part of
+ * composition, since all of it's API is private except length property.
+ */
+const List = Trait.resolve({ toString: null }).compose({
+ _keyValueMap: null,
+ /**
+ * List constructor can take any number of element to populate itself.
+ * @params {Object|String|Number} element
+ * @example
+ * List(1,2,3).length == 3 // true
+ */
+ constructor: function List() {
+ this._keyValueMap = [];
+ for (let i = 0, ii = arguments.length; i < ii; i++)
+ this._add(arguments[i]);
+ },
+ /**
+ * Number of elements in this list.
+ * @type {Number}
+ */
+ get length() this._keyValueMap.length,
+ /**
+ * Returns a string representing this list.
+ * @returns {String}
+ */
+ toString: function toString() 'List(' + this._keyValueMap + ')',
+ /**
+ * Returns `true` if this list contains the specified `element`.
+ * @param {Object|Number|String} element
+ * @returns {Boolean}
+ */
+ _has: function _has(element) 0 <= this._keyValueMap.indexOf(element),
+ /**
+ * Appends the specified `element` to the end of this list, if it doesn't
+ * contains it. Ignores the call if `element` is already contained.
+ * @param {Object|Number|String} element
+ */
+ _add: function _add(element) {
+ let list = this._keyValueMap,
+ index = list.indexOf(element);
+ if (0 > index)
+ list.push(this._public[list.length] = element);
+ },
+ /**
+ * Removes specified `element` from this list, if it contains it.
+ * Ignores the call if `element` is not contained.
+ * @param {Object|Number|String} element
+ */
+ _remove: function _remove(element) {
+ let list = this._keyValueMap,
+ index = list.indexOf(element);
+ if (0 <= index) {
+ delete this._public[list.length - 1];
+ list.splice(index, 1);
+ for (let length = list.length; index < length; index++)
+ this._public[index] = list[index];
+ }
+ },
+ /**
+ * Removes all of the elements from this list.
+ */
+ _clear: function _clear() {
+ for (let i = 0, ii = this._keyValueMap.length; i < ii; i ++)
+ delete this._public[i];
+ this._keyValueMap.splice(0);
+ },
+ /**
+ * Custom iterator providing `List`s enumeration behavior.
+ * We cant reuse `_iterator` that is defined by `Iterable` since it provides
+ * iteration in an arbitrary order.
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let array = this._keyValueMap.slice(0),
+ i = -1;
+ for each(let element in array)
+ yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
+ }
+});
+exports.List = List;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/match-pattern.js b/tools/addon-sdk-1.4/packages/api-utils/lib/match-pattern.js
new file mode 100644
index 0000000..802fe3a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/match-pattern.js
@@ -0,0 +1,137 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Nickolay Ponomarev.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Nickolay Ponomarev <asqueella@gmail.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+const { URL } = require("./url");
+
+exports.MatchPattern = MatchPattern;
+
+function MatchPattern(pattern) {
+ if (typeof pattern.test == "function") {
+
+ // For compatibility with -moz-document rules, we require the RegExp's
+ // global, ignoreCase, and multiline flags to be set to false.
+ if (pattern.global) {
+ throw new Error("A RegExp match pattern cannot be set to `global` " +
+ "(i.e. //g).");
+ }
+ if (pattern.ignoreCase) {
+ throw new Error("A RegExp match pattern cannot be set to `ignoreCase` " +
+ "(i.e. //i).");
+ }
+ if (pattern.multiline) {
+ throw new Error("A RegExp match pattern cannot be set to `multiline` " +
+ "(i.e. //m).");
+ }
+
+ this.regexp = pattern;
+ }
+ else {
+ let firstWildcardPosition = pattern.indexOf("*");
+ let lastWildcardPosition = pattern.lastIndexOf("*");
+ if (firstWildcardPosition != lastWildcardPosition)
+ throw new Error("There can be at most one '*' character in a wildcard.");
+
+ if (firstWildcardPosition == 0) {
+ if (pattern.length == 1)
+ this.anyWebPage = true;
+ else if (pattern[1] != ".")
+ throw new Error("Expected a *.<domain name> string, got: " + pattern);
+ else
+ this.domain = pattern.substr(2);
+ }
+ else {
+ if (pattern.indexOf(":") == -1) {
+ throw new Error("When not using *.example.org wildcard, the string " +
+ "supplied is expected to be either an exact URL to " +
+ "match or a URL prefix. The provided string ('" +
+ pattern + "') is unlikely to match any pages.");
+ }
+
+ if (firstWildcardPosition == -1)
+ this.exactURL = pattern;
+ else if (firstWildcardPosition == pattern.length - 1)
+ this.urlPrefix = pattern.substr(0, pattern.length - 1);
+ else {
+ throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
+ "in an unexpected position. It is expected to be the " +
+ "first or the last character in the wildcard.");
+ }
+ }
+ }
+}
+
+MatchPattern.prototype = {
+
+ test: function MatchPattern_test(urlStr) {
+ try {
+ var url = URL(urlStr);
+ }
+ catch (err) {
+ return false;
+ }
+
+ // Test the URL against a RegExp pattern. For compatibility with
+ // -moz-document rules, we require the RegExp to match the entire URL,
+ // so we not only test for a match, we also make sure the matched string
+ // is the entire URL string.
+ //
+ // Assuming most URLs don't match most match patterns, we call `test` for
+ // speed when determining whether or not the URL matches, then call `exec`
+ // for the small subset that match to make sure the entire URL matches.
+ //
+ if (this.regexp && this.regexp.test(urlStr) &&
+ this.regexp.exec(urlStr)[0] == urlStr)
+ return true;
+
+ if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
+ return true;
+ if (this.exactURL && this.exactURL == urlStr)
+ return true;
+ if (this.domain && url.host &&
+ url.host.slice(-this.domain.length) == this.domain)
+ return true;
+ if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
+ return true;
+
+ return false;
+ }
+
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/memory.js b/tools/addon-sdk-1.4/packages/api-utils/lib/memory.js
new file mode 100644
index 0000000..2ff0486
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/memory.js
@@ -0,0 +1,146 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci,Cu,components} = require("chrome");
+var trackedObjects = {};
+
+var Compacter = {
+ INTERVAL: 5000,
+ notify: function(timer) {
+ var newTrackedObjects = {};
+ for (let name in trackedObjects) {
+ var oldBin = trackedObjects[name];
+ var newBin = [];
+ var strongRefs = [];
+ for (var i = 0; i < oldBin.length; i++) {
+ var strongRef = oldBin[i].weakref.get();
+ if (strongRef && strongRefs.indexOf(strongRef) == -1) {
+ strongRefs.push(strongRef);
+ newBin.push(oldBin[i]);
+ }
+ }
+ if (newBin.length)
+ newTrackedObjects[name] = newBin;
+ }
+ trackedObjects = newTrackedObjects;
+ }
+};
+
+var timer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+
+timer.initWithCallback(Compacter,
+ Compacter.INTERVAL,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+
+var track = exports.track = function track(object, bin, stackFrameNumber) {
+ var frame = components.stack.caller;
+ var weakref = Cu.getWeakReference(object);
+ if (!bin)
+ bin = object.constructor.name;
+ if (bin == "Object")
+ bin = frame.name;
+ if (!bin)
+ bin = "generic";
+ if (!(bin in trackedObjects))
+ trackedObjects[bin] = [];
+
+ if (stackFrameNumber > 0)
+ for (var i = 0; i < stackFrameNumber; i++)
+ frame = frame.caller;
+
+ trackedObjects[bin].push({weakref: weakref,
+ created: new Date(),
+ filename: frame.filename,
+ lineNo: frame.lineNumber,
+ bin: bin});
+};
+
+var getBins = exports.getBins = function getBins() {
+ var names = [];
+ for (let name in trackedObjects)
+ names.push(name);
+ return names;
+};
+
+var getObjects = exports.getObjects = function getObjects(bin) {
+ function getLiveObjectsInBin(bin, array) {
+ for (var i = 0; i < bin.length; i++) {
+ var object = bin[i].weakref.get();
+ if (object)
+ array.push(bin[i]);
+ }
+ }
+
+ var results = [];
+ if (bin) {
+ if (bin in trackedObjects)
+ getLiveObjectsInBin(trackedObjects[bin], results);
+ } else
+ for (let name in trackedObjects)
+ getLiveObjectsInBin(trackedObjects[name], results);
+ return results;
+};
+
+var gc = exports.gc = function gc() {
+ // Components.utils.forceGC() doesn't currently perform
+ // cycle collection, which means that e.g. DOM elements
+ // won't be collected by it. Fortunately, there are
+ // other ways...
+
+ var window = Cc["@mozilla.org/appshell/appShellService;1"]
+ .getService(Ci.nsIAppShellService)
+ .hiddenDOMWindow;
+ var test_utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ test_utils.garbageCollect();
+ Compacter.notify();
+
+ // Not sure why, but sometimes it appears that we don't get
+ // them all with just one CC, so let's do it again.
+ test_utils.garbageCollect();
+};
+
+require("./unload").when(
+ function() {
+ trackedObjects = {};
+ if (timer) {
+ timer.cancel();
+ timer = null;
+ }
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/namespace.js b/tools/addon-sdk-1.4/packages/api-utils/lib/namespace.js
new file mode 100644
index 0000000..ae50f36
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/namespace.js
@@ -0,0 +1,55 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+/**
+ * Function creates a new namespace. Optionally `prototype` object may be
+ * passed, in which case namespace objects will inherit from it. Returned value
+ * is a function that can be used to get access to the namespaced properties
+ * for the passed object.
+ * @examples
+ * const ns = Namespace();
+ * ns(myObject).secret = secret;
+ */
+exports.Namespace = function Namespace(prototype) {
+ prototype = prototype || Object.prototype;
+ const map = new WeakMap();
+ return function namespace(target) {
+ return map.get(target) ||
+ map.set(target, Object.create(prototype)), map.get(target);
+ };
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/observer-service.js b/tools/addon-sdk-1.4/packages/api-utils/lib/observer-service.js
new file mode 100644
index 0000000..f408dd5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/observer-service.js
@@ -0,0 +1,212 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Observers.
+ *
+ * The Initial Developer of the Original Code is Daniel Aquino.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Daniel Aquino <mr.danielaquino@gmail.com>
+ * Myk Melez <myk@mozilla.org>
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+var xpcom = require("./xpcom");
+
+/**
+ * A service for adding, removing and notifying observers of notifications.
+ * Wraps the nsIObserverService interface.
+ *
+ * @version 0.2
+ */
+
+var service = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+
+/**
+ * A cache of observers that have been added.
+ *
+ * We use this to remove observers when a caller calls |Observers.remove|.
+ */
+var cache = [];
+
+/**
+ * Topics specifically available to Jetpack-generated extensions.
+ *
+ * Using these predefined consts instead of the platform strings is good:
+ * - allows us to scope topics specifically for Jetpacks
+ * - addons aren't dependent on strings nor behavior of core platform topics
+ * - the core platform topics are not clearly named
+ *
+ */
+exports.topics = {
+ /**
+ * A topic indicating that the application is in a state usable
+ * by add-ons.
+ */
+ get APPLICATION_READY() packaging.jetpackID + "_APPLICATION_READY"
+};
+
+/**
+ * Register the given callback as an observer of the given topic.
+ *
+ * @param topic {String}
+ * the topic to observe
+ *
+ * @param callback {Object}
+ * the callback; an Object that implements nsIObserver or a Function
+ * that gets called when the notification occurs
+ *
+ * @param thisObject {Object} [optional]
+ * the object to use as |this| when calling a Function callback
+ *
+ * @returns the observer
+ */
+var add = exports.add = function add(topic, callback, thisObject) {
+ var observer = new Observer(topic, callback, thisObject);
+ service.addObserver(observer, topic, true);
+ cache.push(observer);
+
+ return observer;
+};
+
+/**
+ * Unregister the given callback as an observer of the given topic.
+ *
+ * @param topic {String}
+ * the topic being observed
+ *
+ * @param callback {Object}
+ * the callback doing the observing
+ *
+ * @param thisObject {Object} [optional]
+ * the object being used as |this| when calling a Function callback
+ */
+var remove = exports.remove = function remove(topic, callback, thisObject) {
+ // This seems fairly inefficient, but I'm not sure how much better
+ // we can make it. We could index by topic, but we can't index by callback
+ // or thisObject, as far as I know, since the keys to JavaScript hashes
+ // (a.k.a. objects) can apparently only be primitive values.
+ let observers = cache.filter(function(v) {
+ return (v.topic == topic &&
+ v.callback == callback &&
+ v.thisObject == thisObject);
+ });
+
+ if (observers.length) {
+ service.removeObserver(observers[0], topic);
+ cache.splice(cache.indexOf(observers[0]), 1);
+ }
+};
+
+/**
+ * Notify observers about something.
+ *
+ * @param topic {String}
+ * the topic to notify observers about
+ *
+ * @param subject {Object} [optional]
+ * some information about the topic; can be any JS object or primitive
+ *
+ * @param data {String} [optional] [deprecated]
+ * some more information about the topic; deprecated as the subject
+ * is sufficient to pass all needed information to the JS observers
+ * that this module targets; if you have multiple values to pass to
+ * the observer, wrap them in an object and pass them via the subject
+ * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
+ */
+var notify = exports.notify = function notify(topic, subject, data) {
+ subject = (typeof subject == "undefined") ? null : new Subject(subject);
+ data = (typeof data == "undefined") ? null : data;
+ service.notifyObservers(subject, topic, data);
+};
+
+function Observer(topic, callback, thisObject) {
+ memory.track(this);
+ this.topic = topic;
+ this.callback = callback;
+ this.thisObject = thisObject;
+}
+
+Observer.prototype = {
+ QueryInterface: xpcom.utils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+ observe: function(subject, topic, data) {
+ // Extract the wrapped object for subjects that are one of our
+ // wrappers around a JS object. This way we support both wrapped
+ // subjects created using this module and those that are real
+ // XPCOM components.
+ if (subject && typeof subject == "object" &&
+ ("wrappedJSObject" in subject) &&
+ ("observersModuleSubjectWrapper" in subject.wrappedJSObject))
+ subject = subject.wrappedJSObject.object;
+
+ try {
+ if (typeof this.callback == "function") {
+ if (this.thisObject)
+ this.callback.call(this.thisObject, subject, data);
+ else
+ this.callback(subject, data);
+ } else // typeof this.callback == "object" (nsIObserver)
+ this.callback.observe(subject, topic, data);
+ } catch (e) {
+ console.exception(e);
+ }
+ }
+};
+
+function Subject(object) {
+ // Double-wrap the object and set a property identifying the
+ // wrappedJSObject as one of our wrappers to distinguish between
+ // subjects that are one of our wrappers (which we should unwrap
+ // when notifying our observers) and those that are real JS XPCOM
+ // components (which we should pass through unaltered).
+ this.wrappedJSObject = {
+ observersModuleSubjectWrapper: true,
+ object: object
+ };
+}
+
+Subject.prototype = {
+ QueryInterface: xpcom.utils.generateQI([]),
+ getHelperForLanguage: function() {},
+ getInterfaces: function() {}
+};
+
+require("./unload").when(
+ function removeAllObservers() {
+ // Make a copy of cache first, since cache will be changing as we
+ // iterate through it.
+ cache.slice().forEach(
+ function(observer) {
+ remove(observer.topic, observer.callback, observer.thisObject);
+ });
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/passwords/utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/passwords/utils.js
new file mode 100644
index 0000000..950aac2
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/passwords/utils.js
@@ -0,0 +1,134 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack Packages.
+ *
+ * The Initial Developer of the Original Code is Red Hat.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Matěj Cepl <mcepl@redhat.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci, components: { Constructor: CConstructor } } = 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 = CConstructor("@mozilla.org/login-manager/loginInfo;1",
+ "nsILoginInfo", "init");
+
+function filterMatchingLogins(loginInfo)
+ Object.keys(this).every(function(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) 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/tools/addon-sdk-1.4/packages/api-utils/lib/plain-text-console.js b/tools/addon-sdk-1.4/packages/api-utils/lib/plain-text-console.js
new file mode 100644
index 0000000..f7ea74e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/plain-text-console.js
@@ -0,0 +1,114 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+
+function stringify(arg) {
+ try {
+ return String(arg);
+ }
+ catch(ex) {
+ return "<toString() error>";
+ }
+}
+
+function stringifyArgs(args) {
+ return Array.map(args, stringify).join(" ");
+}
+
+function message(print, level, args) {
+ print(level + ": " + stringifyArgs(args) + "\n", level);
+}
+
+var Console = exports.PlainTextConsole = function PlainTextConsole(print) {
+ if (!print)
+ print = dump;
+ if (print === dump) {
+ // If we're just using dump(), auto-enable preferences so
+ // that the developer actually sees the console output.
+ var prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefs.setBoolPref("browser.dom.window.dump.enabled", true);
+ }
+ this.print = print;
+
+ // Binding all the public methods to an instance so that they can be used
+ // as callback / listener functions straightaway.
+ this.log = this.log.bind(this);
+ this.info = this.info.bind(this);
+ this.warn = this.warn.bind(this);
+ this.error = this.error.bind(this);
+ this.debug = this.debug.bind(this);
+ this.exception = this.exception.bind(this);
+ this.trace = this.trace.bind(this);
+};
+
+Console.prototype = {
+ log: function log() {
+ message(this.print, "info", arguments);
+ },
+
+ info: function info() {
+ message(this.print, "info", arguments);
+ },
+
+ warn: function warn() {
+ message(this.print, "warning", arguments);
+ },
+
+ error: function error() {
+ message(this.print, "error", arguments);
+ },
+
+ debug: function debug() {
+ message(this.print, "debug", arguments);
+ },
+
+ exception: function exception(e) {
+ var fullString = ("An exception occurred.\n" +
+ require("./traceback").format(e) + "\n" + e);
+ this.error(fullString);
+ },
+
+ trace: function trace() {
+ var traceback = require("./traceback");
+ var stack = traceback.get();
+ stack.splice(-1, 1);
+ message(this.print, "info", [traceback.format(stack)]);
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/preferences-service.js b/tools/addon-sdk-1.4/packages/api-utils/lib/preferences-service.js
new file mode 100644
index 0000000..127a1cf
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/preferences-service.js
@@ -0,0 +1,138 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Preferences.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org>
+ * Daniel Aquino <mr.danielaquino@gmail.com>
+ * Atul Varma <atul@mozilla.com>
+ * Erik Vold <erikvvold@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+// 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");
+
+var prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).getBranch(null);
+
+var get = exports.get = function get(name, defaultValue) {
+ switch (prefSvc.getPrefType(name)) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ return prefSvc.getComplexValue(name, Ci.nsISupportsString).data;
+
+ case Ci.nsIPrefBranch.PREF_INT:
+ return prefSvc.getIntPref(name);
+
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ return prefSvc.getBoolPref(name);
+
+ case Ci.nsIPrefBranch.PREF_INVALID:
+ return defaultValue;
+
+ default:
+ // This should never happen.
+ throw new Error("Error getting pref " + name +
+ "; its value's type is " +
+ prefSvc.getPrefType(name) +
+ ", which I don't know " +
+ "how to handle.");
+ }
+};
+
+var set = exports.set = function set(name, value) {
+ var prefType;
+ if (typeof value != "undefined" && value != null)
+ prefType = value.constructor.name;
+
+ switch (prefType) {
+ case "String":
+ {
+ var string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = value;
+ prefSvc.setComplexValue(name, Ci.nsISupportsString, string);
+ }
+ break;
+
+ case "Number":
+ // We throw if the number is outside the range or not an integer, since
+ // the result will not be what the consumer wanted to store.
+ if (value > MAX_INT || value < MIN_INT)
+ throw new Error("you cannot set the " + name +
+ " pref to the number " + value +
+ ", as number pref values must be in the signed " +
+ "32-bit integer range -(2^31) to 2^31-1. " +
+ "To store numbers outside that range, store " +
+ "them as strings.");
+ if (value % 1 != 0)
+ throw new Error("cannot store non-integer number: " + value);
+ prefSvc.setIntPref(name, value);
+ break;
+
+ case "Boolean":
+ prefSvc.setBoolPref(name, value);
+ break;
+
+ default:
+ throw new Error("can't set pref " + name + " to value '" + value +
+ "'; it isn't a string, integer, or boolean");
+ }
+};
+
+var has = exports.has = function has(name) {
+ return (prefSvc.getPrefType(name) != Ci.nsIPrefBranch.PREF_INVALID);
+};
+
+var isSet = exports.isSet = function isSet(name) {
+ return (has(name) && prefSvc.prefHasUserValue(name));
+};
+
+var reset = exports.reset = function reset(name) {
+ try {
+ prefSvc.clearUserPref(name);
+ } catch (e if e.result == Cr.NS_ERROR_UNEXPECTED) {
+ // 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.
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/process.js b/tools/addon-sdk-1.4/packages/api-utils/lib/process.js
new file mode 100644
index 0000000..3f429eb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/process.js
@@ -0,0 +1,95 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const { createRemoteBrowser } = require("api-utils/window-utils");
+const { channel } = require("./channel");
+const { setTimout } = require('./timer');
+const packaging = require('@packaging');
+const { when } = require('./unload');
+
+const addonService = '@mozilla.org/addon/service;1' in Cc ?
+ Cc['@mozilla.org/addon/service;1'].getService(Ci.nsIAddonService) : null
+
+const ENABLE_E10S = packaging.enable_e10s;
+
+function loadScript(target, uri, sync) {
+ return 'loadScript' in target ? target.loadScript(uri, sync)
+ : target.loadFrameScript(uri, sync)
+}
+
+function process(target, id, path, scope) {
+ // Please note that even though `loadScript`, is executed before channel is
+ // returned, users still are able to subscribe for messages before any message
+ // will be sent. That's because `loadScript` queues script execution on the
+ // other process, which means they will execute async (on the next turn of
+ // event loop), while the channel for messages is returned immediately (in
+ // the same turn of event loop).
+
+ loadScript(target, packaging.uriPrefix + packaging.loader, false);
+ loadScript(target, 'data:,let loader = Loader.new(' +
+ JSON.stringify(packaging) + ');\n' +
+ 'loader.main("' + id + '", "' + path + '");', false);
+
+ when(function (reason) {
+ // Please note that it's important to unload remote loader
+ // synchronously (using synchronous frame script), to make sure that we
+ // don't stop during unload.
+ loadScript(target, 'data:,loader.unload("' + reason + '")', true);
+ });
+
+ return { channel: channel.bind(null, scope, target) }
+}
+
+exports.spawn = function spawn(id, path) {
+ return function promise(deliver) {
+ // If `nsIAddonService` is available we use it to create an add-on process,
+ // otherwise we fallback to the remote browser's message manager.
+ if (ENABLE_E10S && addonService) {
+ console.log('!!!!!!!!!!!!!!!!!!!! Using addon process !!!!!!!!!!!!!!!!!!');
+ deliver(process(addonService.createAddon(), id, path));
+ } else {
+ createRemoteBrowser(ENABLE_E10S)(function(browser) {
+ let messageManager = browser.QueryInterface(Ci.nsIFrameLoaderOwner).
+ frameLoader.messageManager
+ let window = browser.ownerDocument.defaultView;
+ deliver(process(messageManager, id, path, window));
+ });
+ }
+ };
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/runtime.js b/tools/addon-sdk-1.4/packages/api-utils/lib/runtime.js
new file mode 100644
index 0000000..135617e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/runtime.js
@@ -0,0 +1,48 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+exports.inSafeMode = runtime.inSafeMode;
+exports.OS = runtime.OS;
+exports.processType = runtime.processType;
+exports.widgetToolkit = runtime.widgetToolkit;
+exports.XPCOMABI = runtime.XPCOMABI;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/self!.js b/tools/addon-sdk-1.4/packages/api-utils/lib/self!.js
new file mode 100644
index 0000000..bd9b6da
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/self!.js
@@ -0,0 +1,82 @@
+/* vim:st=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian Warner <warner@mozilla.com>
+ * Erik Vold <erikvvold@gmail.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { CC } = require('chrome');
+const { jetpackID, name, manifest, metadata, uriPrefix } = require('@packaging');
+
+const XMLHttpRequest = CC('@mozilla.org/xmlextras/xmlhttprequest;1',
+ 'nsIXMLHttpRequest');
+
+// Utility function that synchronously reads local resource from the given
+// `uri` and returns content string.
+function readURI(uri) {
+ let request = XMLHttpRequest();
+ request.open('GET', uri, false);
+ request.overrideMimeType('text/plain');
+ request.send();
+ return request.responseText;
+}
+
+// Some XPCOM APIs require valid URIs as an argument for certain operations (see
+// `nsILoginManager` for example). This property represents add-on associated
+// unique URI string that can be used for that.
+const uri = 'addon:' + jetpackID
+
+function url(root, path) root + (path || "")
+function read(root, path) readURI(url(root, path))
+
+exports.create = function create(base) {
+ let moduleData = manifest[base] && manifest[base].requirements['self'];
+ let root = uriPrefix + moduleData.dataURIPrefix;
+ return Object.freeze({
+ id: 'self',
+ exports: Object.freeze({
+ id: jetpackID,
+ uri: uri,
+ name: name,
+ version: metadata[name].version,
+ data: {
+ url: url.bind(null, root),
+ load: read.bind(null, root)
+ }
+ })
+ });
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/system.js b/tools/addon-sdk-1.4/packages/api-utils/lib/system.js
new file mode 100644
index 0000000..70896e7
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/system.js
@@ -0,0 +1,131 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+'use strict';
+
+const { Cc, Ci, CC } = require('chrome');
+const options = require('@packaging');
+const file = require('./file');
+
+const appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
+ getService(Ci.nsIAppStartup);
+const appInfo = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo);
+const runtime = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULRuntime);
+
+const { eAttemptQuit: E_ATTEMPT, eForceQuit: E_FORCE } = appStartup;
+
+/**
+ * Parsed JSON object that was passed via `cfx --static-args "{ foo: 'bar' }"`
+ */
+exports.staticArgs = options.staticArgs;
+
+/**
+ * Environment variables. Environment variables are non-enumerable properties
+ * of this object (key is name and value is value).
+ */
+exports.env = require('./environment').env;
+
+/**
+ * Ends the process with the specified `code`. If omitted, exit uses the
+ * 'success' code 0. To exit with failure use `1`.
+ * TODO: Improve platform to actually quit with an exit code.
+ */
+exports.exit = function exit(code) {
+ // This is used by 'cfx' to find out exit code.
+ if ('resultFile' in options) {
+ let stream = file.open(options.resultFile, 'w');
+ stream.write(code ? 'FAIL' : 'OK');
+ stream.close();
+ }
+
+ appStartup.quit(code ? E_ATTEMPT : E_FORCE);
+};
+
+/**
+ * What platform you're running on (all lower case string).
+ * For possible values see:
+ * https://developer.mozilla.org/en/OS_TARGET
+ */
+exports.platform = runtime.OS.toLowerCase();
+
+/**
+ * What processor architecture you're running on:
+ * `'arm', 'ia32', or 'x64'`.
+ */
+exports.architecture = runtime.XPCOMABI.split('_')[0];
+
+/**
+ * What compiler used for build:
+ * `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
+ */
+exports.compiler = runtime.XPCOMABI.split('_')[1];
+
+/**
+ * The application's build ID/date, for example "2004051604".
+ */
+exports.build = appInfo.appBuildID;
+
+/**
+ * The XUL application's UUID.
+ * This has traditionally been in the form
+ * `{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}` but for some applications it may
+ * be: "appname@vendor.tld".
+ */
+exports.id = appInfo.ID;
+
+/**
+ * The name of the application.
+ */
+exports.name = appInfo.name;
+
+/**
+ * The XUL application's version, for example "0.8.0+" or "3.7a1pre".
+ */
+exports.version = appInfo.version;
+
+/**
+ * XULRunner version.
+ */
+exports.platformVersion = runtime.platformVersion;
+
+
+/**
+ * The name of the application vendor, for example "Mozilla".
+ */
+exports.vendor = appInfo.vendor;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/tab-browser.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tab-browser.js
new file mode 100644
index 0000000..272a7c6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tab-browser.js
@@ -0,0 +1,761 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Dietrich Ayala <dietrich@mozilla.com>
+ * Felipe Gomes <felipc@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+const {Cc,Ci,Cu} = require("chrome");
+var NetUtil = {};
+Cu.import("resource://gre/modules/NetUtil.jsm", NetUtil);
+NetUtil = NetUtil.NetUtil;
+const errors = require("./errors");
+const windowUtils = require("./window-utils");
+const apiUtils = require("./api-utils");
+const collection = require("./collection");
+
+// TODO: The hard-coding of app-specific info here isn't very nice;
+// ideally such app-specific info should be more decoupled, and the
+// module should be extensible, allowing for support of new apps at
+// runtime, perhaps by inspecting supported packages (e.g. via
+// dynamically-named modules or package-defined extension points).
+
+if (!require("./xul-app").is("Firefox")) {
+ throw new Error([
+ "The tab-browser module currently supports only Firefox. In the future ",
+ "it will support other applications. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+function onBrowserLoad(callback, event) {
+ if (event.target && event.target.defaultView == this) {
+ this.removeEventListener("load", onBrowserLoad, true);
+ try {
+ require("timer").setTimeout(function () {
+ callback(event);
+ }, 10);
+ } catch (e) { console.exception(e); }
+ }
+}
+
+// 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", onBrowserLoad.bind(window, callback), true);
+
+ return window;
+}
+
+// Open a URL in a new tab
+exports.addTab = function addTab(url, options) {
+ if (!options)
+ options = {};
+ options.url = url;
+
+ options = apiUtils.validateOptions(options, {
+ // TODO: take URL object instead of string (bug 564524)
+ url: {
+ is: ["string"],
+ ok: function (v) !!v,
+ msg: "The url parameter must have be a non-empty string."
+ },
+ inNewWindow: {
+ is: ["undefined", "null", "boolean"]
+ },
+ inBackground: {
+ is: ["undefined", "null", "boolean"]
+ },
+ onLoad: {
+ is: ["undefined", "null", "function"]
+ },
+ isPinned: {
+ is: ["undefined", "boolean"]
+ }
+ });
+
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ if (!win || options.inNewWindow) {
+ openBrowserWindow(function(e) {
+ if(options.isPinned) {
+ //get the active tab in the recently created window
+ let mainWindow = e.target.defaultView;
+ mainWindow.gBrowser.pinTab(mainWindow.gBrowser.selectedTab);
+ }
+ require("./errors").catchAndLog(function(e) options.onLoad(e))(e);
+ }, options.url);
+ } else {
+ let tab = win.gBrowser.addTab(options.url);
+ if (!options.inBackground)
+ win.gBrowser.selectedTab = tab;
+ if (options.onLoad) {
+ let tabBrowser = win.gBrowser.getBrowserForTab(tab);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ if (e.target.defaultView.content.location == "about:blank")
+ return;
+ // remove event handler from addTab - don't want notified
+ // for subsequent loads in same tab.
+ tabBrowser.removeEventListener("load", onLoad, true);
+ require("./errors").catchAndLog(function(e) options.onLoad(e))(e);
+ }, true);
+ }
+ }
+}
+
+// Iterate over a window's tabbrowsers
+function tabBrowserIterator(window) {
+ var browsers = window.document.querySelectorAll("tabbrowser");
+ for (var i = 0; i < browsers.length; i++)
+ yield browsers[i];
+}
+
+// Iterate over a tabbrowser's tabs
+function tabIterator(tabbrowser) {
+ var tabs = tabbrowser.tabContainer;
+ for (var i = 0; i < tabs.children.length; i++) {
+ yield tabs.children[i];
+ }
+}
+
+// Tracker for all tabbrowsers across all windows,
+// or a single tabbrowser if the window is given.
+function Tracker(delegate, window) {
+ this._delegate = delegate;
+ this._browsers = [];
+ this._window = window;
+ this._windowTracker = new windowUtils.WindowTracker(this);
+
+ require("./unload").ensure(this);
+}
+Tracker.prototype = {
+ __iterator__: function __iterator__() {
+ for (var i = 0; i < this._browsers.length; i++)
+ yield this._browsers[i];
+ },
+ get: function get(index) {
+ return this._browsers[index];
+ },
+ onTrack: function onTrack(window) {
+ if (this._window && window != this._window)
+ return;
+
+ for (let browser in tabBrowserIterator(window))
+ this._browsers.push(browser);
+ if (this._delegate)
+ for (let browser in tabBrowserIterator(window))
+ this._delegate.onTrack(browser);
+ },
+ onUntrack: function onUntrack(window) {
+ if (this._window && window != this._window)
+ return;
+
+ for (let browser in tabBrowserIterator(window)) {
+ let index = this._browsers.indexOf(browser);
+ if (index != -1)
+ this._browsers.splice(index, 1);
+ else
+ console.error("internal error: browser tab not found");
+ }
+ if (this._delegate)
+ for (let browser in tabBrowserIterator(window))
+ this._delegate.onUntrack(browser);
+ },
+ get length() {
+ return this._browsers.length;
+ },
+ unload: function unload() {
+ this._windowTracker.unload();
+ }
+};
+exports.Tracker = apiUtils.publicConstructor(Tracker);
+
+// Tracker for all tabs across all windows,
+// or a single window if it's given.
+function TabTracker(delegate, window) {
+ this._delegate = delegate;
+ this._tabs = [];
+ this._tracker = new Tracker(this, window);
+ require("./unload").ensure(this);
+}
+TabTracker.prototype = {
+ _TAB_EVENTS: ["TabOpen", "TabClose"],
+ _safeTrackTab: function safeTrackTab(tab) {
+ this._tabs.push(tab);
+ try {
+ this._delegate.onTrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeUntrackTab: function safeUntrackTab(tab) {
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ this._tabs.splice(index, 1);
+ try {
+ this._delegate.onUntrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ handleEvent: function handleEvent(event) {
+ switch (event.type) {
+ case "TabOpen":
+ this._safeTrackTab(event.target);
+ break;
+ case "TabClose":
+ this._safeUntrackTab(event.target);
+ break;
+ default:
+ throw new Error("internal error: unknown event type: " +
+ event.type);
+ }
+ },
+ onTrack: function onTrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeTrackTab(tab);
+ var self = this;
+ this._TAB_EVENTS.forEach(
+ function(eventName) {
+ tabbrowser.tabContainer.addEventListener(eventName, self, true);
+ });
+ },
+ onUntrack: function onUntrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeUntrackTab(tab);
+ var self = this;
+ this._TAB_EVENTS.forEach(
+ function(eventName) {
+ tabbrowser.tabContainer.removeEventListener(eventName, self, true);
+ });
+ },
+ unload: function unload() {
+ this._tracker.unload();
+ }
+};
+exports.TabTracker = apiUtils.publicConstructor(TabTracker);
+
+exports.whenContentLoaded = function whenContentLoaded(callback) {
+ var cb = require("./errors").catchAndLog(function eventHandler(event) {
+ if (event.target && event.target.defaultView)
+ callback(event.target.defaultView);
+ });
+
+ var tracker = new Tracker({
+ onTrack: function(tabBrowser) {
+ tabBrowser.addEventListener("DOMContentLoaded", cb, false);
+ },
+ onUntrack: function(tabBrowser) {
+ tabBrowser.removeEventListener("DOMContentLoaded", cb, false);
+ }
+ });
+
+ return tracker;
+};
+
+exports.__defineGetter__("activeTab", function() {
+ const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ let mainWindow = wm.getMostRecentWindow("navigator:browser");
+ return mainWindow.gBrowser.selectedTab;
+});
+
+/******************* TabModule *********************/
+
+// Supported tab events
+const events = [
+ "onActivate",
+ "onDeactivate",
+ "onOpen",
+ "onClose",
+ "onReady",
+ "onLoad",
+ "onPaint"
+];
+exports.tabEvents = events;
+
+/**
+ * TabModule
+ *
+ * Constructor for a module that implements the tabs API
+ */
+let TabModule = exports.TabModule = function TabModule(window) {
+ let self = this;
+ /**
+ * Tab
+ *
+ * Safe object representing a tab.
+ */
+ let tabConstructor = apiUtils.publicConstructor(function(element) {
+ if (!element)
+ throw new Error("no tab element.");
+ let win = element.ownerDocument.defaultView;
+ if (!win)
+ throw new Error("element has no window.");
+ if (window && win != window)
+ throw new Error("module's window and element's window don't match.");
+ let browser = win.gBrowser.getBrowserForTab(element);
+
+ this.__defineGetter__("title", function() browser.contentDocument.title);
+ this.__defineGetter__("location", function() browser.contentDocument.location);
+ this.__defineSetter__("location", function(val) browser.contentDocument.location = val);
+ this.__defineGetter__("contentWindow", function() browser.contentWindow);
+ this.__defineGetter__("contentDocument", function() browser.contentDocument);
+ this.__defineGetter__("favicon", function() {
+ let pageURI = NetUtil.newURI(browser.contentDocument.location);
+ let fs = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+ let faviconURL;
+ try {
+ let faviconURI = fs.getFaviconForPage(pageURI);
+ faviconURL = fs.getFaviconDataAsDataURL(faviconURI);
+ } catch(ex) {
+ let data = getChromeURLContents("chrome://mozapps/skin/places/defaultFavicon.png");
+ let encoded = browser.contentWindow.btoa(data);
+ faviconURL = "data:image/png;base64," + encoded;
+ }
+ return faviconURL;
+ });
+ this.__defineGetter__("style", function() null); // TODO
+ this.__defineGetter__("index", function() win.gBrowser.getBrowserIndexForDocument(browser.contentDocument));
+ this.__defineGetter__("thumbnail", function() getThumbnailCanvasForTab(element, browser.contentWindow));
+
+ this.close = function() win.gBrowser.removeTab(element);
+ this.move = function(index) {
+ win.gBrowser.moveTabTo(element, index);
+ };
+
+ this.__defineGetter__("isPinned", function() element.pinned);
+ this.pin = function() win.gBrowser.pinTab(element);
+ this.unpin = function() win.gBrowser.unpinTab(element);
+
+ // Set up the event handlers
+ let tab = this;
+ events.filter(function(e) e != "onOpen").forEach(function(e) {
+ // create a collection for each event
+ collection.addCollectionProperty(tab, e);
+ // make tabs setter for each event, for adding via property assignment
+ tab.__defineSetter__(e, function(val) tab[e].add(val));
+ });
+
+ // listen for events, filtered on this tab
+ eventsTabDelegate.addTabDelegate(this);
+ });
+
+ /**
+ * tabs.activeTab
+ */
+ this.__defineGetter__("activeTab", function() {
+ try {
+ return window ? tabConstructor(window.gBrowser.selectedTab)
+ : tabConstructor(exports.activeTab);
+ }
+ catch (e) { }
+ return null;
+ });
+ this.__defineSetter__("activeTab", function(tab) {
+ let [tabElement, win] = getElementAndWindowForTab(tab, window);
+ if (tabElement) {
+ // set as active tab
+ win.gBrowser.selectedTab = tabElement;
+ // focus the window
+ win.focus();
+ }
+ });
+
+ this.open = function TM_open(options) {
+ open(options, tabConstructor, window);
+ }
+
+ // Set up the event handlers
+ events.forEach(function(eventHandler) {
+ // create a collection for each event
+ collection.addCollectionProperty(self, eventHandler);
+ // make tabs setter for each event, for adding via property assignment
+ self.__defineSetter__(eventHandler, function(val) self[eventHandler].add(val));
+ });
+
+ // Tracker that listens for tab events, and proxies
+ // them to registered event listeners.
+ let eventsTabDelegate = {
+ selectedTab: null,
+ tabs: [],
+ addTabDelegate: function TETT_addTabDelegate(tabObj) {
+ this.tabs.push(tabObj);
+ },
+ pushTabEvent: function TETT_pushTabEvent(event, tab) {
+ for (let callback in self[event]) {
+ require("./errors").catchAndLog(function(tab) {
+ callback(new tabConstructor(tab));
+ })(tab);
+ }
+
+ if (event != "onOpen") {
+ this.tabs.forEach(function(tabObj) {
+ if (tabObj[event].length) {
+ let [tabEl,] = getElementAndWindowForTab(tabObj, window);
+ if (tabEl == tab) {
+ for (let callback in tabObj[event])
+ require("./errors").catchAndLog(function() callback())();
+ }
+ }
+ // if being closed, remove the tab object from the cache
+ // of tabs to notify about events.
+ if (event == "onClose")
+ this.tabs.splice(this.tabs.indexOf(tabObj), 1);
+ }, this);
+ }
+ },
+ unload: function() {
+ this.selectedTab = null;
+ this.tabs.splice(0);
+ }
+ };
+ require("./unload").ensure(eventsTabDelegate);
+
+ let eventsTabTracker = new ModuleTabTracker({
+ onTrack: function TETT_onTrack(tab) {
+ eventsTabDelegate.pushTabEvent("onOpen", tab);
+ },
+ onUntrack: function TETT_onUntrack(tab) {
+ eventsTabDelegate.pushTabEvent("onClose", tab);
+ },
+ onSelect: function TETT_onSelect(tab) {
+ if (eventsTabDelegate.selectedTab)
+ eventsTabDelegate.pushTabEvent("onDeactivate", tab);
+
+ eventsTabDelegate.selectedTab = new tabConstructor(tab);
+
+ eventsTabDelegate.pushTabEvent("onActivate", tab);
+ },
+ onReady: function TETT_onReady(tab) {
+ eventsTabDelegate.pushTabEvent("onReady", tab);
+ },
+ onLoad: function TETT_onLoad(tab) {
+ eventsTabDelegate.pushTabEvent("onLoad", tab);
+ },
+ onPaint: function TETT_onPaint(tab) {
+ eventsTabDelegate.pushTabEvent("onPaint", tab);
+ }
+ }, window);
+ require("./unload").ensure(eventsTabTracker);
+
+ // Iterator for all tabs
+ this.__iterator__ = function tabsIterator() {
+ for (let i = 0; i < eventsTabTracker._tabs.length; i++)
+ yield tabConstructor(eventsTabTracker._tabs[i]);
+ }
+
+ this.__defineGetter__("length", function() eventsTabTracker._tabs.length);
+
+ // Cleanup when unloaded
+ this.unload = function TM_unload() {
+ // Unregister tabs event listeners
+ events.forEach(function(e) self[e] = []);
+ }
+ require("./unload").ensure(this);
+
+} // End of TabModule constructor
+
+/**
+ * tabs.open - open a URL in a new tab
+ */
+function open(options, tabConstructor, window) {
+ if (typeof options === "string")
+ options = { url: options };
+
+ options = apiUtils.validateOptions(options, {
+ url: {
+ is: ["string"]
+ },
+ inNewWindow: {
+ is: ["undefined", "boolean"]
+ },
+ inBackground: {
+ is: ["undefined", "boolean"]
+ },
+ isPinned: {
+ is: ["undefined", "boolean"]
+ },
+ onOpen: {
+ is: ["undefined", "function"]
+ }
+ });
+
+ if (window)
+ options.inNewWindow = false;
+
+ let win = window || require("./window-utils").activeBrowserWindow;
+
+ if (!win || options.inNewWindow)
+ openURLInNewWindow(options, tabConstructor);
+ else
+ openURLInNewTab(options, win, tabConstructor);
+}
+
+function openURLInNewWindow(options, tabConstructor) {
+ let addTabOptions = {
+ inNewWindow: true
+ };
+ if (options.onOpen) {
+ addTabOptions.onLoad = function(e) {
+ let win = e.target.defaultView;
+ let tabEl = win.gBrowser.tabContainer.childNodes[0];
+ let tabBrowser = win.gBrowser.getBrowserForTab(tabEl);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ tabBrowser.removeEventListener("load", onLoad, true);
+ let tab = tabConstructor(tabEl);
+ require("./errors").catchAndLog(function(e) options.onOpen(e))(tab);
+ }, true);
+ };
+ }
+ if (options.isPinned) {
+ addTabOptions.isPinned = true;
+ }
+ exports.addTab(options.url.toString(), addTabOptions);
+}
+
+function openURLInNewTab(options, window, tabConstructor) {
+ window.focus();
+ let tabEl = window.gBrowser.addTab(options.url.toString());
+ if (!options.inBackground)
+ window.gBrowser.selectedTab = tabEl;
+ if (options.isPinned)
+ window.gBrowser.pinTab(tabEl);
+ if (options.onOpen) {
+ let tabBrowser = window.gBrowser.getBrowserForTab(tabEl);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ // remove event handler from addTab - don't want to be notified
+ // for subsequent loads in same tab.
+ tabBrowser.removeEventListener("load", onLoad, true);
+ let tab = tabConstructor(tabEl);
+ require("./timer").setTimeout(function() {
+ require("./errors").catchAndLog(function(tab) options.onOpen(tab))(tab);
+ }, 10);
+ }, true);
+ }
+}
+
+function getElementAndWindowForTab(tabObj, window) {
+ // iterate over open windows, or use single window if provided
+ let windowIterator = window ? function() { yield window; }
+ : require("./window-utils").windowIterator;
+ for (let win in windowIterator()) {
+ if (win.gBrowser) {
+ // find the tab element at tab.index
+ let index = win.gBrowser.getBrowserIndexForDocument(tabObj.contentDocument);
+ if (index > -1)
+ return [win.gBrowser.tabContainer.getItemAtIndex(index), win];
+ }
+ }
+ return [null, null];
+}
+
+// Tracker for all tabs across all windows
+// This is tab-browser.TabTracker, but with
+// support for additional events added.
+function ModuleTabTracker(delegate, window) {
+ this._delegate = delegate;
+ this._tabs = [];
+ this._tracker = new Tracker(this, window);
+ require("./unload").ensure(this);
+}
+ModuleTabTracker.prototype = {
+ _TAB_EVENTS: ["TabOpen", "TabClose", "TabSelect", "DOMContentLoaded",
+ "load", "MozAfterPaint"],
+ _safeTrackTab: function safeTrackTab(tab) {
+ tab.addEventListener("load", this, false);
+ tab.linkedBrowser.addEventListener("MozAfterPaint", this, false);
+ this._tabs.push(tab);
+ try {
+ this._delegate.onTrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeUntrackTab: function safeUntrackTab(tab) {
+ tab.removeEventListener("load", this, false);
+ tab.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ throw new Error("internal error: tab not found");
+ this._tabs.splice(index, 1);
+ try {
+ this._delegate.onUntrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeSelectTab: function safeSelectTab(tab) {
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onSelect)
+ this._delegate.onSelect(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeDOMContentLoaded: function safeDOMContentLoaded(event) {
+ let tabBrowser = event.currentTarget;
+ let tabBrowserIndex = tabBrowser.getBrowserIndexForDocument(event.target);
+ // TODO: I'm seeing this when loading data url images
+ if (tabBrowserIndex == -1)
+ return;
+ let tab = tabBrowser.tabContainer.getItemAtIndex(tabBrowserIndex);
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onReady)
+ this._delegate.onReady(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeLoad: function safeLoad(event) {
+ let tab = event.target;
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onLoad)
+ this._delegate.onLoad(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeMozAfterPaint: function safeMozAfterPaint(event) {
+ let win = event.currentTarget.ownerDocument.defaultView;
+ let tabIndex = win.gBrowser.getBrowserIndexForDocument(event.target.document);
+ if (tabIndex == -1)
+ return;
+ let tab = win.gBrowser.tabContainer.getItemAtIndex(tabIndex);
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onPaint)
+ this._delegate.onPaint(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ handleEvent: function handleEvent(event) {
+ switch (event.type) {
+ case "TabOpen":
+ this._safeTrackTab(event.target);
+ break;
+ case "TabClose":
+ this._safeUntrackTab(event.target);
+ break;
+ case "TabSelect":
+ this._safeSelectTab(event.target);
+ break;
+ case "DOMContentLoaded":
+ this._safeDOMContentLoaded(event);
+ break;
+ case "load":
+ this._safeLoad(event);
+ break;
+ case "MozAfterPaint":
+ this._safeMozAfterPaint(event);
+ break;
+ default:
+ throw new Error("internal error: unknown event type: " +
+ event.type);
+ }
+ },
+ onTrack: function onTrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeTrackTab(tab);
+ tabbrowser.tabContainer.addEventListener("TabOpen", this, false);
+ tabbrowser.tabContainer.addEventListener("TabClose", this, false);
+ tabbrowser.tabContainer.addEventListener("TabSelect", this, false);
+ tabbrowser.ownerDocument.defaultView.gBrowser.addEventListener("DOMContentLoaded", this, false);
+ },
+ onUntrack: function onUntrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeUntrackTab(tab);
+ tabbrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ tabbrowser.tabContainer.removeEventListener("TabClose", this, false);
+ tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
+ tabbrowser.ownerDocument.defaultView.gBrowser.removeEventListener("DOMContentLoaded", this, false);
+ },
+ unload: function unload() {
+ this._tracker.unload();
+ }
+};
+
+// Utility to get a thumbnail canvas from a tab object
+function getThumbnailCanvasForTab(tabEl, window) {
+ var thumbnail = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ thumbnail.mozOpaque = true;
+ window = tabEl.linkedBrowser.contentWindow;
+ thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
+ var aspectRatio = 0.5625; // 16:9
+ thumbnail.height = Math.round(thumbnail.width * aspectRatio);
+ var ctx = thumbnail.getContext("2d");
+ var snippetWidth = window.innerWidth * .6;
+ var scale = thumbnail.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth, snippetWidth * aspectRatio, "rgb(255,255,255)");
+ return thumbnail;
+}
+
+// Utility to return the contents of the target of a chrome URL
+function getChromeURLContents(chromeURL) {
+ let io = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let channel = io.newChannel(chromeURL, null, null);
+ let input = channel.open();
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(input);
+ let str = stream.readBytes(input.available());
+ stream.close();
+ input.close();
+ return str;
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/events.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/events.js
new file mode 100644
index 0000000..96309bb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/events.js
@@ -0,0 +1,56 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const ON_PREFIX = "on";
+const TAB_PREFIX = "Tab";
+
+const EVENTS = {
+ ready: "DOMContentLoaded",
+ open: "TabOpen",
+ close: "TabClose",
+ activate: "TabSelect",
+ deactivate: null
+}
+exports.EVENTS = EVENTS;
+
+Object.keys(EVENTS).forEach(function(name) {
+ EVENTS[name] = {
+ name: name,
+ listener: ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1),
+ dom: EVENTS[name]
+ }
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/observer.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/observer.js
new file mode 100644
index 0000000..f679917
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/observer.js
@@ -0,0 +1,126 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { EventEmitterTrait: EventEmitter } = require("../events");
+const { DOMEventAssembler } = require("../events/assembler");
+const { Trait } = require("../light-traits");
+const { getActiveTab, getTabs, getTabContainers } = require("./utils");
+const { browserWindowIterator, isBrowser } = require("../window-utils");
+const { observer: windowObserver } = require("../windows/observer");
+
+const EVENTS = {
+ "TabOpen": "open",
+ "TabClose": "close",
+ "TabSelect": "select",
+ "TabMove": "move",
+ "TabPinned": "pin",
+ "TabUnpinned": "unpin"
+};
+
+
+// Event emitter objects used to register listeners and emit events on them
+// when they occur.
+const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
+ /**
+ * Method is implemented by `EventEmitter` and is used just for emitting
+ * events on registered listeners.
+ */
+ _emit: Trait.required,
+ /**
+ * Events that are supported and emitted by the module.
+ */
+ supportedEventsTypes: Object.keys(EVENTS),
+ /**
+ * 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: function handleEvent(event) {
+ this._emit(EVENTS[event.type], event.target, event);
+ }
+});
+
+// Currently gecko does not dispatches any event on the previously selected
+// tab before / after "TabSelect" is dispatched. In order to work around this
+// limitation we keep track of selected tab and emit "deactivate" event with
+// that before emitting "activate" on selected tab.
+var selectedTab = null;
+function onTabSelect(tab) {
+ if (selectedTab !== tab) {
+ if (selectedTab) observer._emit("deactivate", selectedTab);
+ if (tab) observer._emit("activate", selectedTab = tab);
+ }
+};
+observer.on("select", onTabSelect);
+
+// We also observe opening / closing windows in order to add / remove it's
+// containers to the observed list.
+function onWindowOpen(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
+ getTabContainers(chromeWindow).forEach(function (container) {
+ observer.observe(container);
+ });
+}
+windowObserver.on("open", onWindowOpen);
+
+function onWindowClose(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
+ getTabContainers(chromeWindow).forEach(function (container) {
+ observer.ignore(container);
+ });
+}
+windowObserver.on("close", onWindowClose);
+
+
+// Currently gecko does not dispatches "TabSelect" events when different
+// window gets activated. To work around this limitation we emulate "select"
+// event for this case.
+windowObserver.on("activate", function onWindowActivate(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
+ observer._emit("select", getActiveTab(chromeWindow));
+});
+
+// We should synchronize state, since probably we already have at least one
+// window open.
+for each (let window in browserWindowIterator()) onWindowOpen(window);
+
+exports.observer = observer;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/tab.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/tab.js
new file mode 100644
index 0000000..17e2ff6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/tab.js
@@ -0,0 +1,297 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Ci } = require('chrome');
+const { Trait } = require("../traits");
+const { EventEmitter } = require("../events");
+const { validateOptions } = require("../api-utils");
+const { Enqueued } = require("../utils/function");
+const { EVENTS } = require("./events");
+const { getThumbnailURIForWindow } = require("../utils/thumbnail");
+const { getFaviconURIForLocation } = require("../utils/data");
+
+
+
+// Array of the inner instances of all the wrapped tabs.
+const TABS = [];
+
+/**
+ * Trait used to create tab wrappers.
+ */
+const TabTrait = Trait.compose(EventEmitter, {
+ on: Trait.required,
+ _emit: Trait.required,
+ /**
+ * Tab DOM element that is being wrapped.
+ */
+ _tab: null,
+ /**
+ * Window wrapper whose tab this object represents.
+ */
+ window: null,
+ constructor: function Tab(options) {
+ this._onReady = this._onReady.bind(this);
+ this._tab = options.tab;
+ let window = this.window = options.window;
+ // Setting event listener if was passed.
+ for each (let type in EVENTS) {
+ let listener = options[type.listener];
+ if (listener)
+ this.on(type.name, options[type.listener]);
+ if ('ready' != type.name) // window spreads this event.
+ window.tabs.on(type.name, this._onEvent.bind(this, type.name));
+ }
+
+ this.on(EVENTS.close.name, this.destroy.bind(this));
+ this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true);
+
+ if (options.isPinned)
+ this.pin();
+
+ // Since we will have to identify tabs by a DOM elements facade function
+ // is used as constructor that collects all the instances and makes sure
+ // that they more then one wrapper is not created per tab.
+ return this;
+ },
+ destroy: function destroy() {
+ for each (let type in EVENTS)
+ this._removeAllListeners(type.name);
+ this._browser.removeEventListener(EVENTS.ready.dom, this._onReady,
+ true);
+ },
+
+ /**
+ * Internal listener that emits public event 'ready' when the page of this
+ * tab is loaded.
+ */
+ _onReady: function _onReady(event) {
+ // IFrames events will bubble so we need to ignore those.
+ if (event.target == this._contentDocument)
+ this._emit(EVENTS.ready.name, this._public);
+ },
+ /**
+ * Internal tab event router. Window will emit tab related events for all it's
+ * tabs, this listener will propagate all the events for this tab to it's
+ * listeners.
+ */
+ _onEvent: function _onEvent(type, tab) {
+ if (tab == this._public)
+ this._emit(type, tab);
+ },
+ /**
+ * Browser DOM element where page of this tab is currently loaded.
+ */
+ get _browser() this._window.gBrowser.getBrowserForTab(this._tab),
+ /**
+ * Window DOM element containing this tab.
+ */
+ get _window() this._tab.ownerDocument.defaultView,
+ /**
+ * Document object of the page that is currently loaded in this tab.
+ */
+ get _contentDocument() this._browser.contentDocument,
+ /**
+ * Window object of the page that is currently loaded in this tab.
+ */
+ get _contentWindow() this._browser.contentWindow,
+
+ /**
+ * The title of the page currently loaded in the tab.
+ * Changing this property changes an actual title.
+ * @type {String}
+ */
+ get title() this._contentDocument.title,
+ set title(value) this._contentDocument.title = String(value),
+ /**
+ * Location of the page currently loaded in this tab.
+ * Changing this property will loads page under under the specified location.
+ * @type {String}
+ */
+ get url() String(this._browser.currentURI.spec),
+ set url(value) this._changeLocation(String(value)),
+ // "TabOpen" event is fired when it's still "about:blank" is loaded in the
+ // changing `location` property of the `contentDocument` has no effect since
+ // seems to be either ignored or overridden by internal listener, there for
+ // location change is enqueued for the next turn of event loop.
+ _changeLocation: Enqueued(function(url) this._browser.loadURI(url)),
+ /**
+ * URI of the favicon for the page currently loaded in this tab.
+ * @type {String}
+ */
+ get favicon() getFaviconURIForLocation(this.url),
+ /**
+ * The CSS style for the tab
+ */
+ get style() null, // TODO
+ /**
+ * The index of the tab relative to other tabs in the application window.
+ * Changing this property will change order of the actual position of the tab.
+ * @type {Number}
+ */
+ get index()
+ this._window.gBrowser.getBrowserIndexForDocument(this._contentDocument),
+ set index(value) this._window.gBrowser.moveTabTo(this._tab, value),
+ /**
+ * Thumbnail data URI of the page currently loaded in this tab.
+ * @type {String}
+ */
+ getThumbnail: function getThumbnail()
+ getThumbnailURIForWindow(this._contentWindow),
+ /**
+ * Whether or not tab is pinned (Is an app-tab).
+ * @type {Boolean}
+ */
+ get isPinned() this._tab.pinned,
+ pin: function pin() {
+ this._window.gBrowser.pinTab(this._tab);
+ },
+ unpin: function unpin() {
+ this._window.gBrowser.unpinTab(this._tab);
+ },
+
+ /**
+ * Create a worker for this tab, first argument is options given to Worker.
+ * @type {Worker}
+ */
+ attach: function attach(options) {
+ let { Worker } = require("../content/worker");
+ options.window = this._contentWindow;
+ let worker = Worker(options);
+ worker.once("detach", function detach() {
+ worker.destroy();
+ });
+ return worker;
+ },
+
+ /**
+ * Make this tab active.
+ * Please note: That this function is called synchronous since in E10S that
+ * will be the case. Besides this function is called from a constructor where
+ * we would like to return instance before firing a 'TabActivated' event.
+ */
+ activate: Enqueued(function activate() {
+ if (this._window) // Ignore if window is closed by the time this is invoked.
+ this._window.gBrowser.selectedTab = this._tab;
+ }),
+ /**
+ * Close the tab
+ */
+ close: function close(callback) {
+ if (callback)
+ this.once(EVENTS.close.name, callback);
+ this._window.gBrowser.removeTab(this._tab);
+ },
+ /**
+ * Reload the tab
+ */
+ reload: function reload() {
+ this._window.gBrowser.reloadTab(this._tab);
+ }
+});
+
+function Tab(options) {
+ let chromeTab = options.tab;
+ for each (let tab in TABS) {
+ if (chromeTab == tab._tab)
+ return tab._public;
+ }
+ let tab = TabTrait(options);
+ TABS.push(tab);
+ return tab._public;
+}
+Tab.prototype = TabTrait.prototype;
+exports.Tab = Tab;
+
+function Options(options) {
+ if ("string" === typeof options)
+ options = { url: options };
+
+ return validateOptions(options, {
+ url: { is: ["string"] },
+ inBackground: { is: ["undefined", "boolean"] },
+ isPinned: { is: ["undefined", "boolean"] },
+ onOpen: { is: ["undefined", "function"] },
+ onClose: { is: ["undefined", "function"] },
+ onReady: { is: ["undefined", "function"] },
+ onActivate: { is: ["undefined", "function"] },
+ onDeactivate: { is: ["undefined", "function"] }
+ });
+}
+exports.Options = Options;
+
+
+exports.getTabForWindow = function (win) {
+ // Get browser window
+ let topWindow = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ if (!topWindow.gBrowser) return null;
+
+ // Get top window object, in case we are in a content iframe
+ let topContentWindow;
+ try {
+ topContentWindow = win.top;
+ } catch(e) {
+ // It may throw if win is not a valid content window
+ return null;
+ }
+
+ function getWindowID(obj) {
+ return obj.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .currentInnerWindowID;
+ }
+
+ // Search for related Tab
+ let topWindowId = getWindowID(topContentWindow);
+ for (let i = 0; i < topWindow.gBrowser.browsers.length; i++) {
+ let w = topWindow.gBrowser.browsers[i].contentWindow;
+ if (getWindowID(w) == topWindowId) {
+ return Tab({
+ // TODO: api-utils should not depend on addon-kit!
+ window: require("addon-kit/windows").BrowserWindow({ window: topWindow }),
+ tab: topWindow.gBrowser.tabs[i]
+ });
+ }
+ }
+
+ // We were unable to find the related tab!
+ return null;
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/utils.js
new file mode 100644
index 0000000..030956c
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/tabs/utils.js
@@ -0,0 +1,87 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+function getTabContainer(tabBrowser) {
+ return tabBrowser.tabContainer;
+}
+exports.getTabContainer = getTabContainer;
+
+function getTabBrowsers(window) {
+ return Array.slice(window.document.getElementsByTagName("tabbrowser"));
+}
+exports.getTabBrowsers = getTabBrowsers;
+
+function getTabContainers(window) {
+ return getTabBrowsers(window).map(getTabContainer);
+}
+exports.getTabContainers = getTabContainers;
+
+function getTabs(window) {
+ return getTabContainers(window).reduce(function (tabs, container) {
+ tabs.push.apply(tabs, container.children);
+ return tabs;
+ }, []);
+}
+exports.getTabs = getTabs;
+
+function getActiveTab(window) {
+ return window.gBrowser.selectedTab;
+}
+exports.getActiveTab = getActiveTab;
+
+function getOwnerWindow(tab) {
+ return tab.ownerDocument.defaultView;
+}
+exports.getOwnerWindow = getOwnerWindow;
+
+function openTab(window, url) {
+ return window.gBrowser.addTab(url);
+}
+exports.openTab = openTab;
+
+function closeTab(tab) {
+ return getOwnerWindow(tab).gBrowser.removeTab(tab);
+}
+exports.closeTab = closeTab;
+
+function activateTab(tab) {
+ getOwnerWindow(tab).gBrowser.selectedTab = tab;
+}
+exports.activateTab = activateTab;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/test.js b/tools/addon-sdk-1.4/packages/api-utils/lib/test.js
new file mode 100644
index 0000000..6490630
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/test.js
@@ -0,0 +1,140 @@
+/* vim:ts=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const BaseAssert = require("./test/assert").Assert;
+const { isFunction, isObject } = require("./type");
+
+function extend(target) {
+ let descriptor = {}
+ Array.slice(arguments, 1).forEach(function(source) {
+ Object.getOwnPropertyNames(source).forEach(function onEach(name) {
+ descriptor[name] = Object.getOwnPropertyDescriptor(source, name);
+ });
+ });
+ return Object.create(target, descriptor);
+}
+
+/**
+ * Function takes test `suite` object in CommonJS format and defines all of the
+ * tests from that suite and nested suites in a jetpack format on a given
+ * `target` object. Optionally third argument `prefix` can be passed to prefix
+ * all the test names.
+ */
+function defineTestSuite(target, suite, prefix) {
+ prefix = prefix || "";
+ // If suite defines `Assert` that's what `assert` object have to be created
+ // from and passed to a test function (This allows custom assertion functions)
+ // See for details: http://wiki.commonjs.org/wiki/Unit_Testing/1.1
+ let Assert = suite.Assert || BaseAssert;
+ // Going through each item in the test suite and wrapping it into a
+ // Jetpack test format.
+ Object.keys(suite).forEach(function(key) {
+ // If name starts with test then it's a test function or suite.
+ if (key.indexOf("test") === 0) {
+ let test = suite[key];
+
+ // For each test function so we create a wrapper test function in a
+ // jetpack format and copy that to a `target` exports.
+ if (isFunction(test)) {
+
+ // Since names of the test may match across suites we use full object
+ // path as a name to avoid overriding same function.
+ target[prefix + key] = function(options) {
+
+ // Creating `assert` functions for this test.
+ let assert = Assert(options);
+
+ // If CommonJS test function expects more than one argument
+ // it means that test is async and second argument is a callback
+ // to notify that test is finished.
+ if (1 < test.length) {
+
+ // Letting test runner know that test is executed async and
+ // creating a callback function that CommonJS tests will call
+ // once it's done.
+ options.waitUntilDone();
+ test(assert, function() {
+ options.done();
+ });
+ }
+
+ // Otherwise CommonJS test is synchronous so we call it only with
+ // one argument.
+ else {
+ test(assert);
+ }
+ }
+ }
+
+ // If it's an object then it's a test suite containing test function
+ // and / or nested test suites. In that case we just extend prefix used
+ // and call this function to copy and wrap tests from nested suite.
+ else if (isObject(test)) {
+ // We need to clone `tests` instead of modifying it, since it's very
+ // likely that it is frozen (usually test suites imported modules).
+ test = extend(Object.prototype, test, {
+ Assert: test.Assert || Assert
+ });
+ defineTestSuite(target, test, prefix + key + ".");
+ }
+ }
+ });
+}
+
+/**
+ * This function is a CommonJS test runner function, but since Jetpack test
+ * runner and test format is different from CommonJS this function shims given
+ * `exports` with all its tests into a Jetpack test format so that the built-in
+ * test runner will be able to run CommonJS test without manual changes.
+ */
+exports.run = function run(exports) {
+
+ // We can't leave old properties on exports since those are test in a CommonJS
+ // format that why we move everything to a new `suite` object.
+ let suite = {};
+ Object.keys(exports).forEach(function(key) {
+ suite[key] = exports[key];
+ delete exports[key];
+ });
+
+ // Now we wrap all the CommonJS tests to a Jetpack format and define
+ // those to a given `exports` object since that where jetpack test runner
+ // will look for them.
+ defineTestSuite(exports, suite);
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/test/assert.js b/tools/addon-sdk-1.4/packages/api-utils/lib/test/assert.js
new file mode 100644
index 0000000..0c29d62
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/test/assert.js
@@ -0,0 +1,360 @@
+/* vim:ts=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { isFunction, isNull, isObject, isString, isRegExp, isArray, isDate,
+ isPrimitive, isUndefined, instanceOf, source } = require("../type");
+
+/**
+ * The `AssertionError` is defined in assert.
+ * @extends Error
+ * @example
+ * new assert.AssertionError({
+ * message: message,
+ * actual: actual,
+ * expected: expected
+ * })
+ */
+function AssertionError(options) {
+ let assertionError = Object.create(AssertionError.prototype);
+
+ if (isString(options))
+ options = { message: options };
+ if ("actual" in options)
+ assertionError.actual = options.actual;
+ if ("expected" in options)
+ assertionError.expected = options.expected;
+ if ("operator" in options)
+ assertionError.operator = options.operator;
+
+ assertionError.message = options.message;
+ assertionError.stack = new Error().stack;
+ return assertionError;
+}
+AssertionError.prototype = Object.create(Error.prototype, {
+ constructor: { value: AssertionError },
+ name: { value: "AssertionError", enumerable: true },
+ toString: { value: function toString() {
+ let value;
+ if (this.message) {
+ value = this.name + " : " + this.message;
+ }
+ else {
+ value = [
+ this.name + " : ",
+ source(this.expected),
+ this.operator,
+ source(this.actual)
+ ].join(" ");
+ }
+ return value;
+ }}
+});
+exports.AssertionError = AssertionError;
+
+function Assert(logger) {
+ return Object.create(Assert.prototype, { _log: { value: logger }});
+}
+Assert.prototype = {
+ fail: function fail(e) {
+ this._log.fail(e.message);
+ },
+ pass: function pass(message) {
+ this._log.pass(message);
+ },
+ error: function error(e) {
+ this._log.exception(e);
+ },
+ ok: function ok(value, message) {
+ if (!!!value) {
+ this.fail({
+ actual: value,
+ expected: true,
+ message: message,
+ operator: "=="
+ });
+ }
+ else {
+ this.pass(message);
+ }
+ },
+
+ /**
+ * The equality assertion tests shallow, coercive equality with `==`.
+ * @example
+ * assert.equal(1, 1, "one is one");
+ */
+ equal: function equal(actual, expected, message) {
+ if (actual == expected) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "=="
+ });
+ }
+ },
+
+ /**
+ * The non-equality assertion tests for whether two objects are not equal
+ * with `!=`.
+ * @example
+ * assert.notEqual(1, 2, "one is not two");
+ */
+ notEqual: function notEqual(actual, expected, message) {
+ if (actual != expected) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "!=",
+ });
+ }
+ },
+
+ /**
+ * The equivalence assertion tests a deep (with `===`) equality relation.
+ * @example
+ * assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects")
+ */
+ deepEqual: function deepEqual(actual, expected, message) {
+ if (isDeepEqual(actual, expected)) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "deepEqual"
+ });
+ }
+ },
+
+ /**
+ * The non-equivalence assertion tests for any deep (with `===`) inequality.
+ * @example
+ * assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }),
+ * "object's inherit from different prototypes");
+ */
+ notDeepEqual: function notDeepEqual(actual, expected, message) {
+ if (!isDeepEqual(actual, expected)) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "notDeepEqual"
+ });
+ }
+ },
+
+ /**
+ * The strict equality assertion tests strict equality, as determined by
+ * `===`.
+ * @example
+ * assert.strictEqual(null, null, "`null` is `null`")
+ */
+ strictEqual: function strictEqual(actual, expected, message) {
+ if (actual === expected) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "==="
+ });
+ }
+ },
+
+ /**
+ * The strict non-equality assertion tests for strict inequality, as
+ * determined by `!==`.
+ * @example
+ * assert.notStrictEqual(null, undefined, "`null` is not `undefined`");
+ */
+ notStrictEqual: function notStrictEqual(actual, expected, message) {
+ if (actual !== expected) {
+ this.pass(message);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "!=="
+ })
+ }
+ },
+
+ /**
+ * The assertion whether or not given `block` throws an exception. If optional
+ * `Error` argument is provided and it's type of function thrown error is
+ * asserted to be an instance of it, if type of `Error` is string then message
+ * of throw exception is asserted to contain it.
+ * @param {Function} block
+ * Function that is expected to throw.
+ * @param {Error|RegExp} [Error]
+ * Error constructor that is expected to be thrown or a string that
+ * must be contained by a message of the thrown exception, or a RegExp
+ * matching a message of the thrown exception.
+ * @param {String} message
+ * Description message
+ *
+ * @examples
+ *
+ * assert.throws(function block() {
+ * doSomething(4)
+ * }, "Object is expected", "Incorrect argument is passed");
+ *
+ * assert.throws(function block() {
+ * Object.create(5)
+ * }, TypeError, "TypeError is thrown");
+ */
+ throws: function throws(block, Error, message) {
+ let threw = false;
+ let exception = null;
+
+ // If third argument is not provided and second argument is a string it
+ // means that optional `Error` argument was not passed, so we shift
+ // arguments.
+ if (isString(Error) && isUndefined(message)) {
+ message = Error;
+ Error = undefined;
+ }
+
+ // Executing given `block`.
+ try {
+ block();
+ }
+ catch (e) {
+ threw = true;
+ exception = e;
+ }
+
+ // If exception was thrown and `Error` argument was not passed assert is
+ // passed.
+ if (threw && (isUndefined(Error) ||
+ // If passed `Error` is RegExp using it's test method to
+ // assert thrown exception message.
+ (isRegExp(Error) && Error.test(exception.message)) ||
+ // If passed `Error` is a constructor function testing if
+ // thrown exception is an instance of it.
+ (isFunction(Error) && instanceOf(exception, Error))))
+ {
+ this.pass(message);
+ }
+
+ // Otherwise we report assertion failure.
+ else {
+ let failure = {
+ message: message,
+ operator: "throws"
+ };
+
+ if (exception)
+ failure.actual = exception;
+
+ if (Error)
+ failure.expected = Error;
+
+ this.fail(failure);
+ }
+ }
+};
+exports.Assert = Assert;
+
+function isDeepEqual(actual, expected) {
+
+ // 7.1. All identical values are equivalent, as determined by ===.
+ if (actual === expected) {
+ return true;
+ }
+
+ // 7.2. If the expected value is a Date object, the actual value is
+ // equivalent if it is also a Date object that refers to the same time.
+ else if (isDate(actual) && isDate(expected)) {
+ return actual.getTime() === expected.getTime();
+ }
+
+ // XXX specification bug: this should be specified
+ else if (isPrimitive(actual) || isPrimitive(expected)) {
+ return expected === actual;
+ }
+
+ // 7.3. Other pairs that do not both pass typeof value == "object",
+ // equivalence is determined by ==.
+ else if (!isObject(actual) && !isObject(expected)) {
+ return actual == expected;
+ }
+
+ // 7.4. For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical "prototype" property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ else {
+ return actual.prototype === expected.prototype &&
+ isEquivalent(actual, expected);
+ }
+}
+
+function isEquivalent(a, b, stack) {
+ return isArrayEquivalent(Object.keys(a).sort(),
+ Object.keys(b).sort()) &&
+ Object.keys(a).every(function(key) {
+ return isDeepEqual(a[key], b[key], stack)
+ });
+}
+
+function isArrayEquivalent(a, b, stack) {
+ return isArray(a) && isArray(b) &&
+ a.every(function(value, index) {
+ return isDeepEqual(value, b[index]);
+ });
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/text-streams.js b/tools/addon-sdk-1.4/packages/api-utils/lib/text-streams.js
new file mode 100644
index 0000000..87d375d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/text-streams.js
@@ -0,0 +1,273 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci,Cu,components} = require("chrome");
+var NetUtil = {};
+Cu.import("resource://gre/modules/NetUtil.jsm", NetUtil);
+NetUtil = NetUtil.NetUtil;
+
+// 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";
+
+exports.TextReader = TextReader;
+exports.TextWriter = TextWriter;
+
+/**
+ * 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) {
+ const self = this;
+ 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;
+ };
+}
+
+/**
+ * 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) {
+ const self = this;
+ 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, function (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(self, err);
+ }
+ catch (exc) {
+ console.exception(exc);
+ }
+ }
+ });
+ };
+}
+
+// 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) {
+ const self = this;
+ this.rawStream = rawStream;
+ this.opened = true;
+
+ /**
+ * True iff the stream is closed.
+ */
+ stream.__defineGetter__("closed", function stream_closed() {
+ return !self.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 = function stream_close() {
+ self.ensureOpened();
+ self.unload();
+ };
+
+ require("./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/tools/addon-sdk-1.4/packages/api-utils/lib/timer.js b/tools/addon-sdk-1.4/packages/api-utils/lib/timer.js
new file mode 100644
index 0000000..052f506
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/timer.js
@@ -0,0 +1,141 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ * Erik Vold <erikvvold@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+var xpcom = require("./xpcom");
+
+var timerClass = Cc["@mozilla.org/timer;1"];
+var nextID = 1;
+var timers = {};
+
+function TimerCallback(timerID, callback, params) {
+ this._callback = callback;
+ this._params = params;
+};
+TimerCallback.prototype = {
+ QueryInterface : xpcom.utils.generateQI([Ci.nsITimerCallback])
+};
+
+function TimeoutCallback(timerID, callback, params) {
+ memory.track(this);
+ TimerCallback.apply(this, arguments)
+ this._timerID = timerID;
+};
+TimeoutCallback.prototype = new TimerCallback();
+TimeoutCallback.prototype.notify = function notifyOnTimeout(timer) {
+ try {
+ delete timers[this._timerID];
+ this._callback.apply(null, this._params);
+ } catch (e) {
+ console.exception(e);
+ }
+};
+
+function IntervalCallback(timerID, callback, params) {
+ memory.track(this);
+ TimerCallback.apply(this, arguments)
+};
+IntervalCallback.prototype = new TimerCallback();
+IntervalCallback.prototype.notify = function notifyOnInterval() {
+ try {
+ this._callback.apply(null, this._params);
+ } catch (e) {
+ console.exception(e);
+ }
+};
+
+
+var setTimeout = exports.setTimeout = function setTimeout(callback, delay) {
+ return makeTimer(
+ Ci.nsITimer.TYPE_ONE_SHOT,
+ callback,
+ TimeoutCallback,
+ delay,
+ Array.slice(arguments, 2));
+};
+
+var clearTimeout = exports.clearTimeout = function clearTimeout(timerID) {
+ cancelTimer(timerID);
+};
+
+var setInterval = exports.setInterval = function setInterval(callback, delay) {
+ return makeTimer(
+ Ci.nsITimer.TYPE_REPEATING_SLACK,
+ callback,
+ IntervalCallback,
+ delay,
+ Array.slice(arguments, 2));
+};
+
+var clearInterval = exports.clearInterval = function clearInterval(timerID) {
+ cancelTimer(timerID);
+};
+
+function makeTimer(type, callback, callbackType, delay, params) {
+ var timer = timerClass.createInstance(Ci.nsITimer);
+
+ memory.track(timer, "nsITimer");
+
+ var timerID = nextID++;
+ timers[timerID] = timer;
+
+ timer.initWithCallback(
+ new callbackType(timerID, callback, params),
+ delay || 0,
+ type
+ );
+ return timerID;
+}
+
+function cancelTimer(timerID) {
+ var timer = timers[timerID];
+ if (timer) {
+ timer.cancel();
+ delete timers[timerID];
+ }
+}
+
+require("./unload").when(
+ function cancelAllPendingTimers() {
+ var timerIDs = [timerID for (timerID in timers)];
+ timerIDs.forEach(function(timerID) { cancelTimer(timerID); });
+ });
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/traceback.js b/tools/addon-sdk-1.4/packages/api-utils/lib/traceback.js
new file mode 100644
index 0000000..fdedd99
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/traceback.js
@@ -0,0 +1,155 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci,components} = require("chrome");
+
+// Undo the auto-parentification of URLs done in bug 418356.
+function deParentifyURL(url) {
+ return url ? url.split(" -> ").slice(-1)[0] : url;
+}
+
+// TODO: We might want to move this function to url or some similar
+// module.
+function getLocalFile(path) {
+ var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+ var channel = ios.newChannel(path, null, null);
+ var iStream = channel.open();
+ var siStream = Cc['@mozilla.org/scriptableinputstream;1']
+ .createInstance(Ci.nsIScriptableInputStream);
+ siStream.init(iStream);
+ var data = new String();
+ data += siStream.read(-1);
+ siStream.close();
+ iStream.close();
+ return data;
+}
+
+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 getLocalFile(path).split("\n")[line - 1];
+ } catch (e) {}
+ return null;
+}
+
+function errorStackToJSON(stack) {
+ var lines = stack.split("\n");
+
+ var frames = [];
+ lines.forEach(
+ function(line) {
+ if (!line)
+ return;
+ var atIndex = line.indexOf("@");
+ var colonIndex = line.lastIndexOf(":");
+ var filename = deParentifyURL(line.slice(atIndex + 1, colonIndex));
+ var lineNo = parseInt(line.slice(colonIndex + 1));
+ var funcSig = line.slice(0, atIndex);
+ var funcName = funcSig.slice(0, funcSig.indexOf("("));
+ frames.unshift({filename: filename,
+ funcName: funcName,
+ lineNo: lineNo});
+ });
+
+ return frames;
+};
+
+function nsIStackFramesToJSON(frame) {
+ var stack = [];
+
+ while (frame) {
+ if (frame.filename) {
+ var filename = deParentifyURL(frame.filename);
+ stack.splice(0, 0, {filename: filename,
+ lineNo: frame.lineNumber,
+ funcName: 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 errorStackToJSON(e.stack);
+ if (e.fileName && typeof(e.lineNumber == "number"))
+ return [{filename: deParentifyURL(e.fileName),
+ lineNo: e.lineNumber,
+ funcName: 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.splice(-1, 1);
+ }
+
+ 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.lineNo || frame.funcName))
+ return;
+ lines.push(' File "' + frame.filename + '", line ' +
+ frame.lineNo + ', in ' + frame.funcName);
+ var sourceLine = safeGetFileLine(frame.filename, frame.lineNo);
+ if (sourceLine)
+ lines.push(' ' + sourceLine.trim());
+ });
+
+ return lines.join("\n");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/traits.js b/tools/addon-sdk-1.4/packages/api-utils/lib/traits.js
new file mode 100644
index 0000000..59193aa
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/traits.js
@@ -0,0 +1,215 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {
+ compose: _compose,
+ override: _override,
+ resolve: _resolve,
+ trait: _trait,
+ //create: _create,
+ required,
+} = require('./traits/core');
+
+const defineProperties = Object.defineProperties,
+ freeze = Object.freeze,
+ create = Object.create;
+
+/**
+ * Work around bug 608959 by defining the _create function here instead of
+ * importing it from traits/core. For docs on this function, see the create
+ * function in that module.
+ *
+ * FIXME: remove this workaround in favor of importing the function once that
+ * bug has been fixed.
+ */
+function _create(proto, trait) {
+ let properties = {},
+ keys = Object.getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (descriptor.required &&
+ !Object.prototype.hasOwnProperty.call(proto, key))
+ throw new Error('Missing required property: ' + key);
+ else if (descriptor.conflict)
+ throw new Error('Remaining conflicting property: ' + key);
+ else
+ properties[key] = descriptor;
+ }
+ return Object.create(proto, properties);
+}
+
+/**
+ * Placeholder for `Trait.prototype`
+ */
+let TraitProto = Object.prototype;
+
+function Get(key) this[key]
+function Set(key, value) this[key] = value
+
+/**
+ * Creates anonymous trait descriptor from the passed argument, unless argument
+ * is a trait constructor. In later case trait's already existing properties
+ * descriptor is returned.
+ * This is module's internal function and is used as a gateway to a trait's
+ * internal properties descriptor.
+ * @param {Function} $
+ * Composed trait's constructor.
+ * @returns {Object}
+ * Private trait property of the composition.
+ */
+function TraitDescriptor(object)
+ (
+ 'function' == typeof object &&
+ (object.prototype == TraitProto || object.prototype instanceof Trait)
+ ) ? object._trait(TraitDescriptor) : _trait(object)
+
+function Public(instance, trait) {
+ let result = {},
+ keys = Object.getOwnPropertyNames(trait);
+ for each (let key in keys) {
+ if ('_' === key.charAt(0) && '__iterator__' !== key )
+ continue;
+ let property = trait[key],
+ descriptor = {
+ configurable: property.configurable,
+ enumerable: property.enumerable
+ };
+ if (property.get)
+ descriptor.get = property.get.bind(instance);
+ if (property.set)
+ descriptor.set = property.set.bind(instance);
+ if ('value' in property) {
+ let value = property.value;
+ if ('function' === typeof value) {
+ descriptor.value = property.value.bind(instance);
+ descriptor.writable = property.writable;
+ } else {
+ descriptor.get = Get.bind(instance, key);
+ descriptor.set = Set.bind(instance, key);
+ }
+ }
+ result[key] = descriptor;
+ }
+ return result;
+}
+
+/**
+ * This is private function that composes new trait with privates.
+ */
+function Composition(trait) {
+ function Trait() {
+ let self = _create({}, trait);
+ self._public = create(Trait.prototype, Public(self, trait));
+ delete self._public.constructor;
+ if (Object === self.constructor)
+ self.constructor = Trait;
+ else
+ return self.constructor.apply(self, arguments) || self._public;
+ return self._public;
+ }
+ defineProperties(Trait, {
+ prototype: { value: freeze(create(TraitProto, {
+ constructor: { value: constructor, writable: true }
+ }))}, // writable is `true` to avoid getters in custom ES5
+ displayName: { value: (trait.constructor || constructor).name },
+ compose: { value: compose, enumerable: true },
+ override: { value: override, enumerable: true },
+ resolve: { value: resolve, enumerable: true },
+ required: { value: required, enumerable: true },
+ _trait: { value: function _trait(caller)
+ caller === TraitDescriptor ? trait : undefined
+ }
+ });
+ return freeze(Trait);
+}
+
+/**
+ * Composes new trait out of itself and traits / property maps passed as an
+ * arguments. If two or more traits / property maps have properties with the
+ * same name, the new trait will contain a "conflict" property for that name.
+ * This is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ * @params {Object|Function}
+ * List of Traits or property maps to create traits from.
+ * @returns {Function}
+ * New trait containing the combined properties of all the traits.
+ */
+function compose() {
+ let traits = Array.slice(arguments, 0);
+ traits.push(this);
+ return Composition(_compose.apply(null, traits.map(TraitDescriptor)));
+}
+
+/**
+ * Composes a new trait with all of the combined properties of `this` and the
+ * argument traits. In contrast to `compose`, `override` immediately resolves
+ * all conflicts resulting from this composition by overriding the properties of
+ * later traits. Trait priority is from left to right. I.e. the properties of
+ * the leftmost trait are never overridden.
+ * @params {Object} trait
+ * @returns {Object}
+ */
+function override() {
+ let traits = Array.slice(arguments, 0);
+ traits.push(this);
+ return Composition(_override.apply(null, traits.map(TraitDescriptor)));
+}
+
+/**
+ * Composes new resolved trait, with all the same properties as this
+ * trait, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to `resolutions[name]`. If it is
+ * `resolutions[name]` is `null` value is changed into a required property
+ * descriptor.
+ */
+function resolve(resolutions)
+ Composition(_resolve(resolutions, TraitDescriptor(this)))
+
+/**
+ * Base Trait, that all the traits are composed of.
+ */
+const Trait = Composition({
+ /**
+ * Internal property holding public API of this instance.
+ */
+ _public: { value: null, configurable: true, writable: true },
+ toString: { value: function() '[object ' + this.constructor.name + ']' }
+});
+TraitProto = Trait.prototype;
+exports.Trait = Trait;
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/traits/core.js b/tools/addon-sdk-1.4/packages/api-utils/lib/traits/core.js
new file mode 100644
index 0000000..bc8ae14
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/traits/core.js
@@ -0,0 +1,349 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+// Design inspired by: http://www.traitsjs.org/
+
+// shortcuts
+const getOwnPropertyNames = Object.getOwnPropertyNames,
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
+ hasOwn = Object.prototype.hasOwnProperty,
+ _create = Object.create;
+
+function doPropertiesMatch(object1, object2, name) {
+ // If `object1` has property with the given `name`
+ return name in object1 ?
+ // then `object2` should have it with the same value.
+ name in object2 && object1[name] === object2[name] :
+ // otherwise `object2` should not have property with the given `name`.
+ !(name in object2);
+}
+
+/**
+ * Compares two trait custom property descriptors if they are the same. If
+ * both are `conflict` or all the properties of descriptor are equal returned
+ * value will be `true`, otherwise it will be `false`.
+ * @param {Object} desc1
+ * @param {Object} desc2
+ */
+function areSame(desc1, desc2) {
+ return ('conflict' in desc1 && desc1.conflict &&
+ 'conflict' in desc2 && desc2.conflict) ||
+ (doPropertiesMatch(desc1, desc2, 'get') &&
+ doPropertiesMatch(desc1, desc2, 'set') &&
+ doPropertiesMatch(desc1, desc2, 'value') &&
+ doPropertiesMatch(desc1, desc2, 'enumerable') &&
+ doPropertiesMatch(desc1, desc2, 'required') &&
+ doPropertiesMatch(desc1, desc2, 'conflict'));
+}
+
+/**
+ * Converts array to an object whose own property names represent
+ * values of array.
+ * @param {String[]} names
+ * @returns {Object}
+ * @example
+ * Map(['foo', ...]) => { foo: true, ...}
+ */
+function Map(names) {
+ let map = {};
+ for each (let name in names)
+ map[name] = true;
+ return map;
+}
+
+
+const ERR_CONFLICT = 'Remaining conflicting property: ',
+ ERR_REQUIRED = 'Missing required property: ';
+/**
+ * Constant singleton, representing placeholder for required properties.
+ * @type {Object}
+ */
+const required = { toString: function()'<Trait.required>' };
+exports.required = required;
+
+/**
+ * Generates custom **required** property descriptor. Descriptor contains
+ * non-standard property `required` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function Required(name) {
+ function required() { throw new Error(ERR_REQUIRED + name) }
+ return {
+ get: required,
+ set: required,
+ required: true
+ };
+}
+
+/**
+ * Generates custom **conflicting** property descriptor. Descriptor contains
+ * non-standard property `conflict` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function Conflict(name) {
+ function conflict() { throw new Error(ERR_CONFLICT + name) }
+ return {
+ get: conflict,
+ set: conflict,
+ conflict: true
+ };
+}
+
+/**
+ * Function generates custom properties descriptor of the `object`s own
+ * properties. All the inherited properties are going to be ignored.
+ * Properties with values matching `required` singleton will be marked as
+ * 'required' properties.
+ * @param {Object} object
+ * Set of properties to generate trait from.
+ * @returns {Object}
+ * Properties descriptor of all of the `object`'s own properties.
+ */
+function trait(properties) {
+ let result = {},
+ keys = getOwnPropertyNames(properties);
+ for each (let key in keys) {
+ let descriptor = getOwnPropertyDescriptor(properties, key);
+ result[key] = (required === descriptor.value) ? Required(key) : descriptor;
+ }
+ return result;
+}
+exports.Trait = exports.trait = trait;
+
+/**
+ * Composes new trait. If two or more traits have own properties with the
+ * same name, the new trait will contain a 'conflict' property for that name.
+ * 'compose' is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ *
+ * @params {Object} trait
+ * Takes traits as an arguments
+ * @returns {Object}
+ * New trait containing the combined own properties of all the traits.
+ * @example
+ * var newTrait = compose(trait_1, trait_2, ..., trait_N);
+ */
+function compose(trait1, trait2) {
+ let traits = Array.slice(arguments, 0),
+ result = {};
+ for each (let trait in traits) {
+ let keys = getOwnPropertyNames(trait);
+ for each (let key in keys) {
+ let descriptor = trait[key];
+ // if property already exists and it's not a requirement
+ if (hasOwn.call(result, key) && !result[key].required) {
+ if (descriptor.required)
+ continue;
+ if (!areSame(descriptor, result[key]))
+ result[key] = Conflict(key);
+ } else {
+ result[key] = descriptor;
+ }
+ }
+ }
+ return result;
+}
+exports.compose = compose;
+
+/**
+ * Composes new trait with the same own properties as the original trait,
+ * except that all property names appearing in the first argument are replaced
+ * by 'required' property descriptors.
+ * @param {String[]} keys
+ * Array of strings property names.
+ * @param {Object} trait
+ * A trait some properties of which should be excluded.
+ * @returns {Object}
+ * @example
+ * var newTrait = exclude(['name', ...], trait)
+ */
+function exclude(keys, trait) {
+ let exclusions = Map(keys),
+ result = {};
+
+ keys = getOwnPropertyNames(trait);
+
+ for each (let key in keys) {
+ if (!hasOwn.call(exclusions, key) || trait[key].required)
+ result[key] = trait[key];
+ else
+ result[key] = Required(key);
+ }
+ return result;
+}
+
+/**
+ * Composes a new trait with all of the combined properties of the argument
+ * traits. In contrast to `compose`, `override` immediately resolves all
+ * conflicts resulting from this composition by overriding the properties of
+ * later traits. Trait priority is from left to right. I.e. the properties of
+ * the leftmost trait are never overridden.
+ * @params {Object} trait
+ * @returns {Object}
+ * @examples
+ * // override is associative:
+ * override(t1,t2,t3)
+ * // is equivalent to
+ * override(t1, override(t2, t3))
+ * // or
+ * to override(override(t1, t2), t3)
+ *
+ * // override is not commutative:
+ * override(t1,t2)
+ * // is not equivalent to
+ * override(t2,t1)
+ */
+function override() {
+ let traits = Array.slice(arguments, 0),
+ result = {};
+ for each (let trait in traits) {
+ let keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (!hasOwn.call(result, key) || result[key].required)
+ result[key] = descriptor;
+ }
+ }
+ return result;
+}
+exports.override = override;
+
+/**
+ * Composes a new trait with the same properties as the original trait, except
+ * that all properties whose name is an own property of map will be renamed to
+ * map[name], and a 'required' property for name will be added instead.
+ * @param {Object} map
+ * An object whose own properties serve as a mapping from old names to new
+ * names.
+ * @param {Object} trait
+ * A trait object
+ * @returns {Object}
+ * @example
+ * var newTrait = rename(map, trait);
+ */
+function rename(map, trait) {
+ let result = {},
+ keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ // must be renamed & it's not requirement
+ if (hasOwn.call(map, key) && !trait[key].required) {
+ let alias = map[key];
+ if (hasOwn.call(result, alias) && !result[alias].required)
+ result[alias] = Conflict(alias);
+ else
+ result[alias] = trait[key];
+ if (!hasOwn.call(result, key))
+ result[key] = Required(key);
+ } else { // must not be renamed or its a requirement
+ // property is not in result trait yet
+ if (!hasOwn.call(result, key))
+ result[key] = trait[key];
+ // property is already in resulted trait & it's not requirement
+ else if (!trait[key].required)
+ result[key] = Conflict(key);
+ }
+ }
+ return result;
+}
+
+/**
+* Composes new resolved trait, with all the same properties as the original
+* trait, except that all properties whose name is an own property of
+* resolutions will be renamed to `resolutions[name]`. If it is
+* `resolutions[name]` is `null` value is changed into a required property
+* descriptor.
+* function can be implemented as `rename(map,exclude(exclusions, trait))`
+* where map is the subset of mappings from oldName to newName and exclusions
+* is an array of all the keys that map to `null`.
+* Note: it's important to **first** `exclude`, **then** `rename`, since
+* `exclude` and rename are not associative.
+* @param {Object} resolutions
+* An object whose own properties serve as a mapping from old names to new
+* names, or to `null` if the property should be excluded.
+* @param {Object} trait
+* A trait object
+* @returns {Object}
+* Resolved trait with the same own properties as the original trait.
+*/
+function resolve(resolutions, trait) {
+ let renames = {},
+ exclusions = [],
+ keys = getOwnPropertyNames(resolutions);
+ for each (let key in keys) { // pre-process renamed and excluded properties
+ if (resolutions[key]) // old name -> new name
+ renames[key] = resolutions[key];
+ else // name -> undefined
+ exclusions.push(key);
+ }
+ return rename(renames, exclude(exclusions, trait));
+}
+exports.resolve = resolve;
+
+/**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - an exception is thrown if 'trait' still contains required properties
+ * - an exception is thrown if 'trait' still contains conflicting
+ * properties
+ * @param {Object}
+ * prototype of the completed object
+ * @param {Object} trait
+ * trait object to be turned into a complete object
+ * @returns {Object}
+ * An object with all of the properties described by the trait.
+ */
+function create(proto, trait) {
+ let properties = {},
+ keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (descriptor.required && !hasOwn.call(proto, key))
+ throw new Error(ERR_REQUIRED + key);
+ else if (descriptor.conflict)
+ throw new Error(ERR_CONFLICT + key);
+ else
+ properties[key] = descriptor;
+ }
+ return _create(proto, properties);
+}
+exports.create = create;
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/type.js b/tools/addon-sdk-1.4/packages/api-utils/lib/type.js
new file mode 100644
index 0000000..012d2d1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/type.js
@@ -0,0 +1,372 @@
+/* vim:ts=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+/**
+ * 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 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;
+
+/**
+ * Returns true if `value` is an Array.
+ * @examples
+ * isArray([1, 2, 3]) // true
+ * isArray({ 0: 'foo', length: 1 }) // false
+ */
+var isArray = Array.isArray || function isArray(value) {
+ Object.prototype.toString.call(value) === "[object Array]";
+}
+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) {
+ Object.prototype.toString.call(value) === "[object Arguments]";
+}
+exports.isArguments = isArguments;
+
+/**
+ * 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 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/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test-finder.js b/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test-finder.js
new file mode 100644
index 0000000..a8dc14a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test-finder.js
@@ -0,0 +1,106 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const file = require("./file");
+const packaging = require('@packaging');
+const suites = packaging.allTestModules;
+
+const NOT_TESTS = ['setup', 'teardown'];
+
+var TestFinder = exports.TestFinder = function TestFinder(options) {
+ memory.track(this);
+ this.filter = options.filter;
+ this.testInProcess = options.testInProcess === false ? false : true;
+ this.testOutOfProcess = options.testOutOfProcess === true ? true : false;
+};
+
+TestFinder.prototype = {
+ _makeTest: function _makeTest(suite, name, test) {
+ function runTest(runner) {
+ console.info("executing '" + suite + "." + name + "'");
+ test(runner);
+ }
+ return runTest;
+ },
+
+ findTests: function findTests(cb) {
+ var self = this;
+ var tests = [];
+ var filter;
+ // A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon
+ // optionally separates a regex for the test fileName from a regex for the
+ // testName.
+ if (this.filter) {
+ var colonPos = this.filter.indexOf(':');
+ var filterFileRegex, filterNameRegex;
+ if (colonPos === -1) {
+ filterFileRegex = new RegExp(self.filter);
+ } else {
+ filterFileRegex = new RegExp(self.filter.substr(0, colonPos));
+ filterNameRegex = new RegExp(self.filter.substr(colonPos + 1));
+ }
+ // This function will first be called with just the filename; if
+ // it returns true the module will be loaded then the function
+ // called again with both the filename and the testname.
+ filter = function(filename, testname) {
+ return filterFileRegex.test(filename) &&
+ ((testname && filterNameRegex) ? filterNameRegex.test(testname)
+ : true);
+ };
+ } else
+ filter = function() {return true};
+
+ suites.forEach(
+ function(suite) {
+ var module = require(suite);
+ if (self.testInProcess)
+ for each (let name in Object.keys(module).sort()) {
+ if(NOT_TESTS.indexOf(name) === -1 && filter(suite, name)) {
+ tests.push({
+ setup: module.setup,
+ teardown: module.teardown,
+ testFunction: self._makeTest(suite, name, module[name]),
+ name: suite + "." + name
+ });
+ }
+ }
+ });
+
+ cb(tests);
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test.js b/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test.js
new file mode 100644
index 0000000..4b3ac89
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/unit-test.js
@@ -0,0 +1,466 @@
+/* vim:st=2:sts=2:sw=2:
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+var timer = require("./timer");
+
+exports.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(
+ function (tests) {
+ runner.startMany({tests: tests,
+ onDone: options.onDone});
+ });
+};
+
+var TestRunner = exports.TestRunner = function TestRunner(options) {
+ if (options) {
+ this.fs = options.fs;
+ }
+ this.console = (options && "console" in options) ? options.console : console;
+ memory.track(this);
+ this.passed = 0;
+ this.failed = 0;
+ this.testRunSummary = [];
+ this.expectFailNesting = 0;
+};
+
+TestRunner.prototype = {
+ toString: function toString() "[object TestRunner]",
+
+ DEFAULT_PAUSE_TIMEOUT: 10000,
+ PAUSE_DELAY: 500,
+
+ _logTestFailed: function _logTestFailed(why) {
+ this.test.errors[why]++;
+ if (!this.testFailureLogged) {
+ this.console.error("TEST FAILED: " + this.test.name + " (" + why + ")");
+ this.testFailureLogged = true;
+ }
+ },
+
+ pass: function pass(message) {
+ if(!this.expectFailure) {
+ this.console.info("pass:", message);
+ this.passed++;
+ this.test.passed++;
+ }
+ else {
+ this.expectFailure = false;
+ this.fail('Failure Expected: ' + message);
+ }
+ },
+
+ fail: function fail(message) {
+ if(!this.expectFailure) {
+ this._logTestFailed("failure");
+ this.console.error("fail:", message);
+ this.console.trace();
+ this.failed++;
+ this.test.failed++;
+ }
+ else {
+ this.expectFailure = false;
+ this.pass(message);
+ }
+ },
+
+ expectFail: function(callback) {
+ this.expectFailure = true;
+ callback();
+ this.expectFailure = false;
+ },
+
+ exception: function exception(e) {
+ this._logTestFailed("exception");
+ 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) {
+ this.isDone = true;
+ if(this.test.teardown) {
+ this.test.teardown(this);
+ }
+ if (this.waitTimeout !== null) {
+ timer.clearTimeout(this.waitTimeout);
+ this.waitTimeout = null;
+ }
+ if (this.test.passed == 0 && this.test.failed == 0) {
+ this._logTestFailed("empty test");
+ this.failed++;
+ this.test.failed++;
+ }
+
+ this.testRunSummary.push({
+ name: this.test.name,
+ passed: this.test.passed,
+ failed: this.test.failed,
+ errors: [error for (error in this.test.errors)].join(", ")
+ });
+
+ if (this.onDone !== null) {
+ var onDone = this.onDone;
+ var self = this;
+ this.onDone = null;
+ timer.setTimeout(function() { onDone(self); }, 0);
+ }
+ }
+ },
+
+ // 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 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 callback = null;
+ let finished = false;
+
+ let test = this;
+
+ // capture a traceback before we go async.
+ let traceback = require("./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 (callback && !stopIt)
+ callback();
+ finished = true;
+ },
+ 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);
+ finished = true;
+ return;
+ }
+ }
+ appliedArgs.push(a);
+ }
+
+ // Finally call assertion function with current assertion values
+ assertionMethod.apply(mock, appliedArgs);
+ }
+ loop();
+ this.waitUntilCallback = loop;
+
+ // Return an object with `then` method, to offer a way to execute
+ // some code when the assertion passed or failed
+ return {
+ then: function (c) {
+ callback = c;
+
+ // In case of immediate positive result, we need to execute callback
+ // immediately here:
+ if (finished)
+ callback();
+ }
+ };
+ },
+
+ waitUntilDone: function waitUntilDone(ms) {
+ if (ms === undefined)
+ ms = this.DEFAULT_PAUSE_TIMEOUT;
+
+ var self = this;
+
+ function tiredOfWaiting() {
+ self._logTestFailed("timed out");
+ 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) {
+ var test = options.tests.shift();
+ if (test)
+ self.start({test: test, onDone: runNextTest});
+ else
+ options.onDone(self);
+ }
+ runNextTest(this);
+ },
+
+ start: function start(options) {
+ this.test = options.test;
+ this.test.passed = 0;
+ this.test.failed = 0;
+ this.test.errors = {};
+
+ this.isDone = false;
+ this.onDone = options.onDone;
+ this.waitTimeout = null;
+ this.testFailureLogged = false;
+
+ try {
+ if(this.test.setup) {
+ this.test.setup(this);
+ }
+ this.test.testFunction(this);
+ } catch (e) {
+ this.exception(e);
+ }
+ if (this.waitTimeout === null)
+ this.done();
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/unload.js b/tools/addon-sdk-1.4/packages/api-utils/lib/unload.js
new file mode 100644
index 0000000..3bbeb38
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/unload.js
@@ -0,0 +1,59 @@
+// Parts of this module were taken from narwhal:
+//
+// http://narwhaljs.org
+
+var observers = [];
+var unloaders = [];
+
+var when = exports.when = function when(observer) {
+ if (observers.indexOf(observer) != -1)
+ return;
+ observers.unshift(observer);
+};
+
+var send = exports.send = function send(reason, onError) {
+ onError = onError || console.exception;
+ observers.forEach(function (observer) {
+ try {
+ observer(reason);
+ } catch (e) {
+ onError(e);
+ }
+ });
+};
+
+var ensure = exports.ensure = function ensure(obj, destructorName) {
+ if (!destructorName)
+ destructorName = "unload";
+ if (!(destructorName in obj))
+ throw new Error("object has no '" + destructorName + "' property");
+
+ let called = false;
+ let originalDestructor = obj[destructorName];
+
+ function unloadWrapper(reason) {
+ if (!called) {
+ called = true;
+ let index = unloaders.indexOf(unloadWrapper);
+ if (index == -1)
+ throw new Error("internal error: unloader not found");
+ unloaders.splice(index, 1);
+ originalDestructor.call(obj, reason);
+ originalDestructor = null;
+ destructorName = null;
+ obj = null;
+ }
+ };
+
+ unloaders.push(unloadWrapper);
+
+ obj[destructorName] = unloadWrapper;
+};
+
+when(
+ function(reason) {
+ unloaders.slice().forEach(
+ function(unloadWrapper) {
+ unloadWrapper(reason);
+ });
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/url.js b/tools/addon-sdk-1.4/packages/api-utils/lib/url.js
new file mode 100644
index 0000000..4502129
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/url.js
@@ -0,0 +1,123 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci,Cr} = require("chrome");
+
+var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+
+var resProt = ios.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+function newURI(uriStr, base) {
+ try {
+ let baseURI = base ? ios.newURI(base, null, null) : null;
+ return ios.newURI(uriStr, null, baseURI);
+ }
+ catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
+ throw new Error("malformed URI: " + uriStr);
+ }
+ catch (e if (e.result == Cr.NS_ERROR_FAILURE ||
+ e.result == Cr.NS_ERROR_ILLEGAL_VALUE)) {
+ throw new Error("invalid URI: " + uriStr);
+ }
+}
+
+function resolveResourceURI(uri) {
+ var resolved;
+ try {
+ resolved = resProt.resolveURI(uri);
+ } catch (e if e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw new Error("resource does not exist: " + uri.spec);
+ };
+ return resolved;
+}
+
+let fromFilename = exports.fromFilename = function fromFilename(path) {
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ return ios.newFileURI(file).spec;
+};
+
+let toFilename = exports.toFilename = function toFilename(url) {
+ var uri = newURI(url);
+ if (uri.scheme == "resource")
+ uri = newURI(resolveResourceURI(uri));
+ if (uri.scheme == "chrome") {
+ var channel = ios.newChannelFromURI(uri);
+ try {
+ channel = channel.QueryInterface(Ci.nsIFileChannel);
+ return channel.file.path;
+ } catch (e if e.result == Cr.NS_NOINTERFACE) {
+ throw new Error("chrome url isn't on filesystem: " + url);
+ }
+ }
+ if (uri.scheme == "file") {
+ var file = uri.QueryInterface(Ci.nsIFileURL).file;
+ return file.path;
+ }
+ throw new Error("cannot map to filename: " + url);
+};
+
+function URL(url, base) {
+ var uri = newURI(url, base);
+
+ var userPass = null;
+ try {
+ userPass = uri.userPass ? uri.userPass : null;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ var host = null;
+ try {
+ host = uri.host;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ var port = null;
+ try {
+ port = uri.port == -1 ? null : uri.port;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ this.__defineGetter__("scheme", function() uri.scheme);
+ this.__defineGetter__("userPass", function() userPass);
+ this.__defineGetter__("host", function() host);
+ this.__defineGetter__("port", function() port);
+ this.__defineGetter__("path", function() uri.path);
+ this.toString = function URL_toString() uri.spec;
+};
+exports.URL = require("./api-utils").publicConstructor(URL);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/utils/data.js b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/data.js
new file mode 100644
index 0000000..bf621ad
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/data.js
@@ -0,0 +1,104 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const IOService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+const AppShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm");
+const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+
+const PNG_B64 = "data:image/png;base64,";
+const DEF_FAVICON_URI = "chrome://mozapps/skin/places/defaultFavicon.png";
+let DEF_FAVICON = null;
+
+/**
+ * Takes URI of the page and returns associated favicon URI.
+ * If page under passed uri has no favicon then base64 encoded data URI of
+ * default faveicon is returned.
+ * @param {String} uri
+ * @returns {String}
+ */
+exports.getFaviconURIForLocation = function getFaviconURIForLocation(uri) {
+ let pageURI = NetUtil.newURI(uri);
+ try {
+ return FaviconService.getFaviconDataAsDataURL(
+ FaviconService.getFaviconForPage(pageURI));
+ }
+ catch(e) {
+ if (!DEF_FAVICON) {
+ DEF_FAVICON = PNG_B64 +
+ base64Encode(getChromeURIContent(DEF_FAVICON_URI));
+ }
+ return DEF_FAVICON;
+ }
+}
+
+/**
+ * Takes chrome URI and returns content under that URI.
+ * @param {String} chromeURI
+ * @returns {String}
+ */
+function getChromeURIContent(chromeURI) {
+ let channel = IOService.newChannel(chromeURI, null, null);
+ let input = channel.open();
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(input);
+ let content = stream.readBytes(input.available());
+ stream.close();
+ input.close();
+ return content;
+}
+exports.getChromeURIContent = getChromeURIContent;
+
+/**
+ * Creates a base-64 encoded ASCII string from a string of binary data.
+ */
+function base64Encode(data) AppShellService.hiddenDOMWindow.btoa(String(data));
+exports.base64Encode = base64Encode;
+
+/**
+ * Decodes a string of data which has been encoded using base-64 encoding.
+ */
+function base64Decode(data) AppShellService.hiddenDOMWindow.atob(String(data));
+exports.base64Decode = base64Decode;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/utils/function.js b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/function.js
new file mode 100644
index 0000000..ba43d59
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/function.js
@@ -0,0 +1,64 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+var { setTimeout } = require("../timer");
+
+/**
+ * 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 `setTimeout(function() { ... }, 0)`, 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.
+ */
+function Enqueued(callee) {
+ return function enqueued()
+ setTimeout(invoke, 0, callee, arguments, this);
+}
+exports.Enqueued = Enqueued;
+
+/**
+ * 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.
+ */
+function invoke(callee, params, self) callee.apply(self, params);
+exports.invoke = invoke;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/utils/registry.js b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/registry.js
new file mode 100644
index 0000000..6cd11b2
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/registry.js
@@ -0,0 +1,90 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { EventEmitter } = require('../events');
+const unload = require('../unload');
+
+const Registry = EventEmitter.compose({
+ _registry: null,
+ _constructor: null,
+ constructor: function Registry(constructor) {
+ this._registry = [];
+ this._constructor = constructor;
+ this.on('error', this._onError = this._onError.bind(this));
+ unload.ensure(this, "_destructor");
+ },
+ _destructor: function _destructor() {
+ let _registry = this._registry.slice(0);
+ for each (let instance in _registry)
+ this._emit('remove', instance);
+ this._registry.splice(0);
+ },
+ _onError: function _onError(e) {
+ if (!this._listeners('error').length)
+ console.error(e);
+ },
+ has: function has(instance) {
+ let _registry = this._registry;
+ return (
+ (0 <= _registry.indexOf(instance)) ||
+ (instance && instance._public && 0 <= _registry.indexOf(instance._public))
+ );
+ },
+ add: function add(instance) {
+ let { _constructor, _registry } = this;
+ if (!(instance instanceof _constructor))
+ instance = new _constructor(instance);
+ if (0 > _registry.indexOf(instance)) {
+ _registry.push(instance);
+ this._emit('add', instance);
+ }
+ return instance;
+ },
+ remove: function remove(instance) {
+ let _registry = this._registry;
+ let index = _registry.indexOf(instance)
+ if (0 <= index) {
+ this._emit('remove', instance);
+ _registry.splice(index, 1);
+ }
+ }
+});
+exports.Registry = Registry;
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/utils/thumbnail.js b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/thumbnail.js
new file mode 100644
index 0000000..d9012e1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/utils/thumbnail.js
@@ -0,0 +1,76 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+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()
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/window-utils.js b/tools/addon-sdk-1.4/packages/api-utils/lib/window-utils.js
new file mode 100644
index 0000000..2d123a8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/window-utils.js
@@ -0,0 +1,270 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const { EventEmitter } = require('./events'),
+ { Trait } = require('./traits');
+const errors = require("./errors");
+
+const gWindowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+
+const XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+/**
+ * An iterator for XUL windows currently in the application.
+ *
+ * @return A generator that yields XUL windows exposing the
+ * nsIDOMWindow interface.
+ */
+var windowIterator = exports.windowIterator = function windowIterator() {
+ let winEnum = gWindowWatcher.getWindowEnumerator();
+ while (winEnum.hasMoreElements())
+ yield winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
+};
+
+/**
+ * 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 each (let window in windowIterator()) {
+ if (isBrowser(window))
+ yield window;
+ }
+}
+exports.browserWindowIterator = browserWindowIterator;
+
+var WindowTracker = exports.WindowTracker = function WindowTracker(delegate) {
+ this.delegate = delegate;
+ this._loadingWindows = [];
+ for (let window in windowIterator())
+ this._regWindow(window);
+ gWindowWatcher.registerNotification(this);
+ require("./unload").ensure(this);
+};
+
+WindowTracker.prototype = {
+ _regLoadingWindow: function _regLoadingWindow(window) {
+ this._loadingWindows.push(window);
+ window.addEventListener("load", this, true);
+ },
+
+ _unregLoadingWindow: function _unregLoadingWindow(window) {
+ var index = this._loadingWindows.indexOf(window);
+
+ if (index != -1) {
+ this._loadingWindows.splice(index, 1);
+ window.removeEventListener("load", this, true);
+ }
+ },
+
+ _regWindow: function _regWindow(window) {
+ 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() {
+ gWindowWatcher.unregisterNotification(this);
+ for (let window in windowIterator())
+ this._unregWindow(window);
+ },
+
+ handleEvent: function handleEvent(event) {
+ if (event.type == "load" && event.target) {
+ var window = event.target.defaultView;
+ if (window)
+ this._regWindow(window);
+ }
+ },
+
+ observe: function observe(subject, topic, data) {
+ var window = subject.QueryInterface(Ci.nsIDOMWindow);
+ if (topic == "domwindowopened")
+ this._regWindow(window);
+ else
+ this._unregWindow(window);
+ }
+};
+
+errors.catchAndLogProps(WindowTracker.prototype, ["handleEvent", "observe"]);
+
+const WindowTrackerTrait = Trait.compose({
+ _onTrack: Trait.required,
+ _onUntrack: Trait.required,
+ constructor: function WindowTrackerTrait() {
+ new WindowTracker({
+ onTrack: this._onTrack.bind(this),
+ onUntrack: this._onUntrack.bind(this)
+ });
+ }
+});
+exports.WindowTrackerTrait = WindowTrackerTrait;
+
+var gDocsToClose = [];
+
+function onDocUnload(event) {
+ var index = gDocsToClose.indexOf(event.target);
+ if (index == -1)
+ throw new Error("internal error: unloading document not found");
+ var document = gDocsToClose.splice(index, 1)[0];
+ // Just in case, let's remove the event listener too.
+ document.defaultView.removeEventListener("unload", onDocUnload, false);
+}
+
+onDocUnload = require("./errors").catchAndLog(onDocUnload);
+
+exports.closeOnUnload = function closeOnUnload(window) {
+ window.addEventListener("unload", onDocUnload, false);
+ gDocsToClose.push(window.document);
+};
+
+exports.__defineGetter__("activeWindow", function() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow(null);
+});
+exports.__defineSetter__("activeWindow", function(window) {
+ try {
+ window.focus();
+ }
+ catch (e) { }
+});
+
+exports.__defineGetter__("activeBrowserWindow", function() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser");
+});
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+exports.getInnerId = function getInnerId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+};
+
+/**
+ * Returns the ID of the window's outer window.
+ */
+exports.getOuterId = function getOuterId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+};
+
+function isBrowser(window) {
+ return window.document.documentElement.getAttribute("windowtype") ===
+ "navigator:browser";
+};
+exports.isBrowser = isBrowser;
+
+exports.hiddenWindow = appShellService.hiddenDOMWindow;
+
+function createHiddenXULFrame() {
+ return function promise(deliver) {
+ let window = appShellService.hiddenDOMWindow;
+ let document = window.document;
+ let isXMLDoc = (document.contentType == "application/xhtml+xml" ||
+ document.contentType == "application/vnd.mozilla.xul+xml")
+
+ if (isXMLDoc) {
+ deliver(window)
+ }
+ else {
+ let frame = document.createElement('iframe');
+ // This is ugly but we need window for XUL document in order to create
+ // browser elements.
+ frame.setAttribute('src', 'chrome://browser/content/hiddenWindow.xul');
+ frame.addEventListener('DOMContentLoaded', function onLoad(event) {
+ frame.removeEventListener('DOMContentLoaded', onLoad, false);
+ deliver(frame.contentWindow);
+ }, false);
+ document.documentElement.appendChild(frame);
+ }
+ }
+};
+exports.createHiddenXULFrame = createHiddenXULFrame;
+
+exports.createRemoteBrowser = function createRemoteBrowser(remote) {
+ return function promise(deliver) {
+ createHiddenXULFrame()(function(hiddenWindow) {
+ let document = hiddenWindow.document;
+ let browser = document.createElementNS(XUL, "browser");
+ // Remote="true" enable everything here:
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347
+ if (remote !== false)
+ browser.setAttribute("remote","true");
+ // Type="content" is mandatory to enable stuff here:
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1776
+ browser.setAttribute("type","content");
+ // 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)
+ browser.setAttribute("style","-moz-binding: none;");
+ // Flex it in order to be visible (optional, for debug purpose)
+ browser.setAttribute("flex", "1");
+ document.documentElement.appendChild(browser);
+
+ // Return browser
+ deliver(browser);
+ });
+ };
+};
+
+require("./unload").when(
+ function() {
+ gDocsToClose.slice().forEach(
+ function(doc) { doc.defaultView.close(); });
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/windows/dom.js b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/dom.js
new file mode 100644
index 0000000..d74e314
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/dom.js
@@ -0,0 +1,60 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+'use strict';
+
+const { Trait } = require('../traits');
+
+const WindowDom = Trait.compose({
+ _window: Trait.required,
+ get title() {
+ let window = this._window;
+ return window && window.document ? window.document.title : null
+ },
+ close: function close() {
+ let window = this._window;
+ if (window) window.close();
+ return this._public;
+ },
+ activate: function activate() {
+ let window = this._window;
+ if (window) window.focus();
+ return this._public;
+ }
+});
+exports.WindowDom = WindowDom;
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/windows/loader.js b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/loader.js
new file mode 100644
index 0000000..6b43fb6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/loader.js
@@ -0,0 +1,152 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Cc, Ci } = require('chrome'),
+ { setTimeout } = require("../timer"),
+ { Trait } = require('../traits'),
+
+ WM = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator),
+
+ URI_BROWSER = 'chrome://browser/content/browser.xul',
+ NAME = '_blank',
+ FEATURES = 'chrome,all,dialog=no',
+ PARAMS = [ URI_BROWSER, NAME, FEATURES ],
+ ON_LOAD = 'load',
+ ON_UNLOAD = 'unload',
+ STATE_LOADED = 'complete',
+ BROWSER = 'navigator:browser';
+
+/**
+ * Trait provides private `_window` property and requires `_onLoad` property
+ * that will be called when `_window` is loaded. If `_window` property value
+ * is changed with already loaded window `_onLoad` still will be called.
+ */
+const WindowLoader = Trait.compose({
+ /**
+ * Internal listener that is called when window is loaded.
+ * Please keep in mind that this trait will not handle exceptions that may
+ * be thrown by this method so method itself should take care of
+ * handling them.
+ * @param {nsIWindow} window
+ */
+ _onLoad: Trait.required,
+ _tabOptions: Trait.required,
+ /**
+ * Internal listener that is called when `_window`'s DOM 'unload' event
+ * is dispatched. Please note that this trait will not handle exceptions that
+ * may be thrown by this method so method itself should take care of
+ * handling them.
+ */
+ _onUnload: Trait.required,
+ _load: function _load() {
+ if (this.__window) return;
+ let params = PARAMS.slice()
+ params.push(this._tabOptions.map(function(options) options.url).join("|"))
+ let browser = WM.getMostRecentWindow(BROWSER);
+ this._window = browser.openDialog.apply(browser, params);
+ },
+ /**
+ * Private window who's load event is being tracked. Once window is loaded
+ * `_onLoad` is called.
+ * @type {nsIWindow}
+ */
+ get _window() this.__window,
+ set _window(window) {
+ let _window = this.__window;
+ if (!window) window = null;
+ if (window !== _window) {
+ if (_window) {
+ _window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
+ _window.removeEventListener(ON_LOAD, this.__loadListener, false);
+ }
+ if (window) {
+ window.addEventListener(
+ ON_UNLOAD,
+ this.__unloadListener ||
+ (this.__unloadListener = this._unloadListener.bind(this))
+ ,
+ false
+ );
+ this.__window = window;
+ // If window is not loaded yet setting up a listener.
+ if (STATE_LOADED != window.document.readyState) {
+ window.addEventListener(
+ ON_LOAD,
+ this.__loadListener ||
+ (this.__loadListener = this._loadListener.bind(this))
+ ,
+ false
+ );
+ }
+ else { // If window is loaded calling listener next turn of event loop.
+ this._onLoad(window)
+ }
+ }
+ }
+ },
+ __window: null,
+ /**
+ * Internal method used for listening 'load' event on the `_window`.
+ * Method takes care of removing itself from 'load' event listeners once
+ * event is being handled.
+ */
+ _loadListener: function _loadListener(event) {
+ let window = this._window;
+ if (!event.target || event.target.defaultView != window) return;
+ window.removeEventListener(ON_LOAD, this.__loadListener, false);
+ this._onLoad(window);
+ },
+ __loadListener: null,
+ /**
+ * Internal method used for listening 'unload' event on the `_window`.
+ * Method takes care of removing itself from 'unload' event listeners once
+ * event is being handled.
+ */
+ _unloadListener: function _unloadListener(event) {
+ let window = this._window;
+ if (!event.target
+ || event.target.defaultView != window
+ || STATE_LOADED != window.document.readyState
+ ) return;
+ window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
+ this._onUnload(window);
+ },
+ __unloadListener: null
+});
+exports.WindowLoader = WindowLoader;
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/windows/observer.js b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/observer.js
new file mode 100644
index 0000000..d7b01e6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/observer.js
@@ -0,0 +1,86 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { EventEmitterTrait: EventEmitter } = require("../events");
+const { WindowTracker, windowIterator } = require("../window-utils");
+const { DOMEventAssembler } = require("../events/assembler");
+const { Trait } = require("../light-traits");
+
+// Event emitter objects used to register listeners and emit events on them
+// when they occur.
+const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
+ /**
+ * Method is implemented by `EventEmitter` and is used just for emitting
+ * events on registered listeners.
+ */
+ _emit: Trait.required,
+ /**
+ * Events that are supported and emitted by the module.
+ */
+ supportedEventsTypes: [ "activate", "deactivate" ],
+ /**
+ * 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: function handleEvent(event) {
+ this._emit(event.type, event.target, event);
+ }
+});
+
+// Using `WindowTracker` to track window events.
+new WindowTracker({
+ onTrack: function onTrack(chromeWindow) {
+ observer._emit("open", chromeWindow);
+ observer.observe(chromeWindow);
+ },
+ onUntrack: function onUntrack(chromeWindow) {
+ observer._emit("close", chromeWindow);
+ observer.ignore(chromeWindow);
+ }
+});
+
+// Making observer aware of already opened windows.
+for each (let window in windowIterator())
+ observer.observe(window);
+
+exports.observer = observer;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/windows/tabs.js b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/tabs.js
new file mode 100644
index 0000000..551b1ce
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/windows/tabs.js
@@ -0,0 +1,207 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { Trait } = require("../traits");
+const { List } = require("../list");
+const { Tab, Options } = require("../tabs/tab");
+const { EventEmitter } = require("../events");
+const { EVENTS } = require("../tabs/events");
+const { getOwnerWindow, getActiveTab, getTabs,
+ openTab, activateTab } = require("../tabs/utils");
+const { observer: tabsObserver } = require("../tabs/observer");
+
+const TAB_BROWSER = "tabbrowser";
+
+/**
+ * This is a trait that is used in composition of window wrapper. Trait tracks
+ * tab related events of the wrapped window in order to keep track of open
+ * tabs and maintain their wrappers. Every new tab gets wrapped and jetpack
+ * type event is emitted.
+ */
+const WindowTabTracker = Trait.compose({
+ /**
+ * Chrome window whose tabs are tracked.
+ */
+ _window: Trait.required,
+ /**
+ * Function used to emit events.
+ */
+ _emit: EventEmitter.required,
+ _tabOptions: Trait.required,
+ /**
+ * Function to add event listeners.
+ */
+ on: EventEmitter.required,
+ removeListener: EventEmitter.required,
+ /**
+ * Initializes tab tracker for a browser window.
+ */
+ _initWindowTabTracker: function _initWindowTabTracker() {
+ // Ugly hack that we have to remove at some point (see Bug 658059). At this
+ // point it is necessary to invoke lazy `tabs` getter on the windows object
+ // which creates a `TabList` instance.
+ this.tabs;
+ // Binding all methods used as event listeners to the instance.
+ this._onTabReady = this._emitEvent.bind(this, "ready");
+ this._onTabOpen = this._onTabEvent.bind(this, "open");
+ this._onTabClose = this._onTabEvent.bind(this, "close");
+ this._onTabActivate = this._onTabEvent.bind(this, "activate");
+ this._onTabDeactivate = this._onTabEvent.bind(this, "deactivate");
+
+ for each (let tab in getTabs(this._window)) {
+ // We emulate "open" events for all open tabs since gecko does not emits
+ // them on the tabs that new windows are open with. Also this is
+ // necessary to synchronize tabs lists with an actual state.
+ this._onTabOpen(tab);
+ }
+ // We also emulate "activate" event so that it's picked up by a tab list.
+ this._onTabActivate(getActiveTab(this._window));
+
+ // Setting up event listeners
+ tabsObserver.on("open", this._onTabOpen);
+ tabsObserver.on("close", this._onTabClose);
+ tabsObserver.on("activate", this._onTabActivate);
+ tabsObserver.on("deactivate", this._onTabDeactivate);
+ },
+ _destroyWindowTabTracker: function _destroyWindowTabTracker() {
+ // We emulate close events on all tabs, since gecko does not emits such
+ // events by itself.
+ for each (let tab in this.tabs)
+ this._emitEvent("close", tab);
+
+ this._tabs._clear();
+
+ tabsObserver.removeListener("open", this._onTabOpen);
+ tabsObserver.removeListener("close", this._onTabClose);
+ tabsObserver.removeListener("activate", this._onTabActivate);
+ tabsObserver.removeListener("deactivate", this._onTabDeactivate);
+ },
+ _onTabEvent: function _onTabEvent(type, tab) {
+ if (this._window === getOwnerWindow(tab)) {
+ let options = this._tabOptions.shift() || {};
+ options.tab = tab;
+ options.window = this._public;
+ // creating tab wrapper and adding listener to "ready" events.
+ let wrappedTab = Tab(options);
+
+ // Setting up an event listener for ready events.
+ if (type === "open")
+ wrappedTab.on("ready", this._onTabReady);
+
+ this._emitEvent(type, wrappedTab);
+ }
+ },
+ _emitEvent: function _emitEvent(type, tab) {
+ // Notifies combined tab list that tab was added / removed.
+ tabs._emit(type, tab);
+ // Notifies contained tab list that window was added / removed.
+ this._tabs._emit(type, tab);
+ }
+});
+exports.WindowTabTracker = WindowTabTracker;
+
+/**
+ * This trait is used to create live representation of open tab lists. Each
+ * window wrapper's tab list is represented by an object created from this
+ * trait. It is also used to represent list of all the open windows. Trait is
+ * composed out of `EventEmitter` in order to emit 'TabOpen', 'TabClose' events.
+ * **Please note** that objects created by this trait can't be exposed outside
+ * instead you should expose it's `_public` property, see comments in
+ * constructor for details.
+ */
+const TabList = List.resolve({ constructor: "_init" }).compose(
+ // This is ugly, but necessary. Will be removed by #596248
+ EventEmitter.resolve({ toString: null }),
+ Trait.compose({
+ on: Trait.required,
+ _emit: Trait.required,
+ constructor: function TabList(options) {
+ this._window = options.window;
+ // Add new items to the list
+ this.on(EVENTS.open.name, this._add.bind(this));
+ // Remove closed items from the list
+ this.on(EVENTS.close.name, this._remove.bind(this));
+
+ // Set value whenever new tab becomes active.
+ this.on("activate", function onTabActivate(tab) {
+ this._activeTab = tab;
+ }.bind(this));
+ // Initialize list.
+ this._init();
+ // This list is not going to emit any events, object holding this list
+ // will do it instead, to make that possible we return a private API.
+ return this;
+ },
+ get activeTab() this._activeTab,
+ _activeTab: null,
+
+ open: function open(options) {
+ options = Options(options);
+ this._window._tabOptions.push(options);
+ let tab = openTab(this._window._window, options.url);
+ if (!options.inBackground)
+ activateTab(tab);
+ }
+ // This is ugly, but necessary. Will be removed by #596248
+ }).resolve({ toString: null })
+);
+
+/**
+ * Combined list of all open tabs on all the windows.
+ * type {TabList}
+ */
+var tabs = TabList({ window: null });
+exports.tabs = tabs._public;
+
+/**
+ * Trait is a part of composition that represents window wrapper. This trait is
+ * composed out of `WindowTabTracker` that allows it to keep track of open tabs
+ * on the window being wrapped.
+ */
+const WindowTabs = Trait.compose(
+ WindowTabTracker,
+ Trait.compose({
+ _window: Trait.required,
+ /**
+ * List of tabs
+ */
+ get tabs() (this._tabs || (this._tabs = TabList({ window: this })))._public,
+ _tabs: null,
+ })
+);
+exports.WindowTabs = WindowTabs;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/xhr.js b/tools/addon-sdk-1.4/packages/api-utils/lib/xhr.js
new file mode 100644
index 0000000..10b83db
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/xhr.js
@@ -0,0 +1,181 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+
+// ## Implementation Notes ##
+//
+// Making `XMLHttpRequest` objects available to Jetpack code involves a
+// few key principles universal to all low-level module implementations:
+//
+// * **Unloadability**. A Jetpack-based extension using this module can be
+// asked to unload itself at any time, e.g. because the user decides to
+// uninstall or disable the extension. This means we need to keep track of
+// all in-progress reqests and abort them on unload.
+//
+// * **Developer-Ergonomic Tracebacks**. Whenever an exception is raised
+// by a Jetpack-based extension, we want it to be logged in a
+// place that is specific to that extension--so that a developer
+// can distinguish it from an error on a web page or in another
+// extension, for instance. We also want it to be logged with a
+// full stack traceback, which the Mozilla platform doesn't usually
+// do.
+//
+// Because of this, we don't actually want to give the Mozilla
+// platform's "real" XHR implementation to clients, but instead provide
+// a simple wrapper that trivially delegates to the implementation in
+// all cases except where callbacks are involved: whenever Mozilla
+// platform code calls into the extension, such as during the XHR's
+// `onreadystatechange` callback, we want to wrap the client's callback
+// in a try-catch clause that traps any exceptions raised by the
+// callback and logs them via console.exception() instead of allowing
+// them to propagate back into Mozilla platform code.
+
+// This is a private list of all active requests, so we know what to
+// abort if we're asked to unload.
+var requests = [];
+
+// Events on XHRs that we should listen for, so we know when to remove
+// a request from our private list.
+const TERMINATE_EVENTS = ["load", "error", "abort"];
+
+// Read-only properties of XMLHttpRequest objects that we want to
+// directly delegate to.
+const READ_ONLY_PROPS = ["readyState", "responseText", "responseXML",
+ "status", "statusText"];
+
+// Methods of XMLHttpRequest that we want to directly delegate to.
+const DELEGATED_METHODS = ["abort", "getAllResponseHeaders",
+ "getResponseHeader", "overrideMimeType",
+ "send", "sendAsBinary", "setRequestHeader",
+ "open"];
+
+var getRequestCount = exports.getRequestCount = function getRequestCount() {
+ return requests.length;
+};
+
+var XMLHttpRequest = exports.XMLHttpRequest = function XMLHttpRequest() {
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ // For the sake of simplicity, don't tie this request to any UI.
+ req.mozBackgroundRequest = true;
+
+ memory.track(req, "XMLHttpRequest");
+
+ this._req = req;
+ this._orsc = null;
+
+ requests.push(this);
+
+ var self = this;
+
+ this._boundCleanup = function _boundCleanup() {
+ self._cleanup();
+ };
+
+ TERMINATE_EVENTS.forEach(
+ function(name) {
+ self._req.addEventListener(name, self._boundCleanup, false);
+ });
+};
+
+XMLHttpRequest.prototype = {
+ _cleanup: function _cleanup() {
+ this.onreadystatechange = null;
+ var index = requests.indexOf(this);
+ if (index != -1) {
+ var self = this;
+ TERMINATE_EVENTS.forEach(
+ function(name) {
+ self._req.removeEventListener(name, self._boundCleanup, false);
+ });
+ requests.splice(index, 1);
+ }
+ },
+ _unload: function _unload() {
+ this._req.abort();
+ this._cleanup();
+ },
+ addEventListener: function addEventListener() {
+ throw new Error("not implemented");
+ },
+ removeEventListener: function removeEventListener() {
+ throw new Error("not implemented");
+ },
+ set upload(newValue) {
+ throw new Error("not implemented");
+ },
+ get onreadystatechange() {
+ return this._orsc;
+ },
+ set onreadystatechange(cb) {
+ this._orsc = cb;
+ if (cb) {
+ var self = this;
+ this._req.onreadystatechange = function() {
+ try {
+ self._orsc.apply(self, arguments);
+ } catch (e) {
+ console.exception(e);
+ }
+ };
+ } else
+ this._req.onreadystatechange = null;
+ }
+};
+
+READ_ONLY_PROPS.forEach(
+ function(name) {
+ XMLHttpRequest.prototype.__defineGetter__(
+ name,
+ function() {
+ return this._req[name];
+ });
+ });
+
+DELEGATED_METHODS.forEach(
+ function(name) {
+ XMLHttpRequest.prototype[name] = function() {
+ return this._req[name].apply(this._req, arguments);
+ };
+ });
+
+require("./unload").when(
+ function() {
+ requests.slice().forEach(function(request) { request._unload(); });
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/xpcom.js b/tools/addon-sdk-1.4/packages/api-utils/lib/xpcom.js
new file mode 100644
index 0000000..10f1d6b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/xpcom.js
@@ -0,0 +1,152 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci,Cm,Cr,Cu} = require("chrome");
+
+var jsm = {};
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", jsm);
+var utils = exports.utils = jsm.XPCOMUtils;
+
+Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+var factories = [];
+
+function Factory(options) {
+ memory.track(this);
+
+ this.wrappedJSObject = this;
+ this.create = options.create;
+ this.uuid = options.uuid;
+ this.name = options.name;
+ this.contractID = options.contractID;
+
+ Cm.registerFactory(this.uuid,
+ this.name,
+ this.contractID,
+ this);
+
+ var self = this;
+
+ factories.push(this);
+}
+
+Factory.prototype = {
+ createInstance: function(outer, iid) {
+ try {
+ if (outer)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return (new this.create()).QueryInterface(iid);
+ } catch (e) {
+ console.exception(e);
+ if (e instanceof Ci.nsIException)
+ throw e;
+ else
+ throw Cr.NS_ERROR_FAILURE;
+ }
+ },
+ unregister: function() {
+ var index = factories.indexOf(this);
+ if (index == -1)
+ throw new Error("factory already unregistered");
+
+ var self = this;
+
+ factories.splice(index, 1);
+ Cm.unregisterFactory(this.uuid, this);
+ },
+ QueryInterface: utils.generateQI([Ci.nsIFactory])
+};
+
+var makeUuid = exports.makeUuid = function makeUuid() {
+ var uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+ var uuid = uuidGenerator.generateUUID();
+ return uuid;
+};
+
+var autoRegister = exports.autoRegister = 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 appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+ var runtime = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime);
+
+ var osDirName = runtime.OS + "_" + runtime.XPCOMABI;
+ var platformVersion = appInfo.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);
+};
+
+var register = exports.register = function register(options) {
+ options = {__proto__: options};
+ if (!options.uuid)
+ options.uuid = makeUuid();
+ return new Factory(options);
+};
+
+var getClass = exports.getClass = function getClass(contractID, iid) {
+ if (!iid)
+ iid = Ci.nsISupports;
+ return Cm.getClassObjectByContractID(contractID, iid);
+};
+
+require("./unload").when(
+ function() {
+ var copy = factories.slice();
+ copy.reverse();
+ copy.forEach(function(factory) { factory.unregister(); });
+ });
diff --git a/tools/addon-sdk-1.4/packages/api-utils/lib/xul-app.js b/tools/addon-sdk-1.4/packages/api-utils/lib/xul-app.js
new file mode 100644
index 0000000..c118682
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/lib/xul-app.js
@@ -0,0 +1,95 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc, Ci} = require("chrome");
+
+var appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+
+var ID = exports.ID = appInfo.ID;
+var name = exports.name = appInfo.name;
+var version = exports.version = appInfo.version;
+var platformVersion = exports.platformVersion = appInfo.platformVersion;
+
+// The following mapping of application names to GUIDs was taken from:
+//
+// https://addons.mozilla.org/en-US/firefox/pages/appversions
+//
+// Using the GUID instead of the app's name is preferable because sometimes
+// re-branded versions of a product have different names: for instance,
+// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
+// GUID.
+// This mapping is duplicated in `app-extensions/bootstrap.js`. They should keep
+// in sync, so if you change one, change the other too!
+
+var ids = exports.ids = {
+ Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
+ Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
+ SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
+ Fennec: "{a23983c0-fd0e-11dc-95ff-0800200c9a66}",
+ Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
+};
+
+var is = exports.is = function is(name) {
+ if (!(name in ids))
+ throw new Error("Unkown Mozilla Application: " + name);
+ return ID == ids[name];
+};
+
+var isOneOf = exports.isOneOf = function isOneOf(names) {
+ for (var i = 0; i < names.length; i++)
+ if (is(names[i]))
+ return true;
+ return false;
+};
+
+/**
+ * Use this to check whether the given version (e.g. xulApp.platformVersion)
+ * is in the given range. Versions must be in version comparator-compatible
+ * format. See MDC for details:
+ * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
+ */
+var versionInRange = exports.versionInRange =
+function versionInRange(version, lowInclusive, highExclusive) {
+ var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
+ .getService(Ci.nsIVersionComparator);
+ return (vc.compare(version, lowInclusive) >= 0) &&
+ (vc.compare(version, highExclusive) < 0);
+}
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/package.json b/tools/addon-sdk-1.4/packages/api-utils/package.json
new file mode 100644
index 0000000..d734856
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "api-utils",
+ "description": "Foundational infrastructure and utilities.",
+ "keywords": ["javascript", "engine", "platform", "xulrunner",
+ "jetpack-low-level"],
+ "author": "Atul Varma (http://toolness.com/) <atul@mozilla.com>",
+ "contributors": [
+ "Myk Melez (http://melez.com/) <myk@mozilla.org>",
+ "Daniel Aquino <mr.danielaquino@gmail.com>"
+ ],
+ "license": "MPL 1.1/GPL 2.0/LGPL 2.1",
+ "dependencies": ["addon-kit"],
+ "loader": "lib/cuddlefish.js"
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/commonjs-test-adapter/asserts.js b/tools/addon-sdk-1.4/packages/api-utils/tests/commonjs-test-adapter/asserts.js
new file mode 100644
index 0000000..83abd23
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/commonjs-test-adapter/asserts.js
@@ -0,0 +1,50 @@
+"use strict";
+
+const AssertBase = require("test/assert").Assert;
+
+/**
+ * Generates custom assertion constructors that may be bundled with a test
+ * suite.
+ * @params {String}
+ * names of assertion function to be added to the generated Assert.
+ */
+function Assert() {
+ let assertDescriptor = {};
+ Array.forEach(arguments, function(name) {
+ assertDescriptor[name] = { value: function(message) {
+ this.pass(message);
+ }}
+ });
+
+ return function Assert() {
+ return Object.create(AssertBase.apply(null, arguments), assertDescriptor);
+ };
+}
+
+exports["test suite"] = {
+ Assert: Assert("foo"),
+ "test that custom assertor is passed to test function": function(assert) {
+ assert.ok("foo" in assert, "custom assertion function `foo` is defined");
+ assert.foo("custom assertion function `foo` is called");
+ },
+ "test sub suite": {
+ "test that `Assert` is inherited by sub suits": function(assert) {
+ assert.ok("foo" in assert, "assertion function `foo` is not defined");
+ },
+ "test sub sub suite": {
+ Assert: Assert("bar"),
+ "test that custom assertor is passed to test function": function(assert) {
+ assert.ok("bar" in assert,
+ "custom assertion function `bar` is defined");
+ assert.bar("custom assertion function `bar` is called");
+ },
+ "test that `Assert` is not inherited by sub sub suits": function(assert) {
+ assert.ok(!("foo" in assert),
+ "assertion function `foo` is not defined");
+ }
+ }
+ }
+};
+
+if (module == require.main)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/fixtures/es5.js b/tools/addon-sdk-1.4/packages/api-utils/tests/fixtures/es5.js
new file mode 100644
index 0000000..6e5b1ea
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/fixtures/es5.js
@@ -0,0 +1,4 @@
+"use strict";
+exports.frozen = Object.freeze({});
+exports.sealed = Object.seal({});
+exports.inextensible = Object.preventExtensions({});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/helpers.js b/tools/addon-sdk-1.4/packages/api-utils/tests/helpers.js
new file mode 100644
index 0000000..bbfe911
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/helpers.js
@@ -0,0 +1,19 @@
+"use strict";
+
+const { Loader } = require("@loader");
+
+exports.Loader = function(module, globals) {
+ var options = JSON.parse(JSON.stringify(require("@packaging")));
+ options.globals = globals;
+ let loader = Loader.new(options);
+ return Object.create(loader, {
+ require: { value: Loader.require.bind(loader, module.path) },
+ sandbox: { value: function sandbox(id) {
+ let path = options.manifest[module.path].requirements[id].path;
+ return loader.sandboxes[path].sandbox;
+ }},
+ unload: { value: function unload(reason, callback) {
+ loader.unload(reason, callback);
+ }}
+ })
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/loader/fixture.js b/tools/addon-sdk-1.4/packages/api-utils/tests/loader/fixture.js
new file mode 100644
index 0000000..d097dea
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/loader/fixture.js
@@ -0,0 +1,2 @@
+exports.foo = foo;
+console.log('testing', 1, [2, 3, 4]);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/add.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/add.js
new file mode 100644
index 0000000..5825e08
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/add.js
@@ -0,0 +1,5 @@
+define('modules/add', function () {
+ return function (a, b) {
+ return a + b;
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/async1.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/async1.js
new file mode 100644
index 0000000..cb51500
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/async1.js
@@ -0,0 +1,10 @@
+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/tools/addon-sdk-1.4/packages/api-utils/tests/modules/async2.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/async2.js
new file mode 100644
index 0000000..c0281e5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/async2.js
@@ -0,0 +1,4 @@
+define(['./traditional2', 'exports'], function (traditional2, exports) {
+ exports.name = 'async2';
+ exports.traditional2Name = traditional2.name;
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badExportAndReturn.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badExportAndReturn.js
new file mode 100644
index 0000000..0844be1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badExportAndReturn.js
@@ -0,0 +1,6 @@
+// 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/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badFirst.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badFirst.js
new file mode 100644
index 0000000..c3e4c36
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badFirst.js
@@ -0,0 +1,5 @@
+define(['./badSecond'], function (badSecond) {
+ return {
+ name: 'badFirst'
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badSecond.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badSecond.js
new file mode 100644
index 0000000..213c7b8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/badSecond.js
@@ -0,0 +1,4 @@
+var first = require('./badFirst');
+
+exports.name = 'badSecond';
+exports.badFirstName = first.name;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/blue.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/blue.js
new file mode 100644
index 0000000..af3a193
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/blue.js
@@ -0,0 +1,5 @@
+define(function () {
+ return {
+ name: 'blue'
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/castor.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/castor.js
new file mode 100644
index 0000000..c2d40b7
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/castor.js
@@ -0,0 +1,6 @@
+define(['exports', './pollux'], function(exports, pollux) {
+ exports.name = 'castor';
+ exports.getPolluxName = function () {
+ return pollux.name;
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/cheetah.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/cheetah.js
new file mode 100644
index 0000000..ad24e3a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/cheetah.js
@@ -0,0 +1,5 @@
+define(function () {
+ return function () {
+ return 'cheetah';
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/color.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/color.js
new file mode 100644
index 0000000..e1fe374
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/color.js
@@ -0,0 +1,3 @@
+define({
+ type: 'color'
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupe.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupe.js
new file mode 100644
index 0000000..f5ce8c9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupe.js
@@ -0,0 +1,11 @@
+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/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupeNested.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupeNested.js
new file mode 100644
index 0000000..85ecb8d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupeNested.js
@@ -0,0 +1,11 @@
+
+define(function () {
+ // This is wrong and should not be allowed.
+ define('dupeNested2', {
+ name: 'dupeNested2'
+ });
+
+ return {
+ name: 'dupeNested'
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupeSetExports.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupeSetExports.js
new file mode 100644
index 0000000..8ad3417
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/dupeSetExports.js
@@ -0,0 +1,4 @@
+define({name: "dupeSetExports"});
+
+// so this should cause a failure
+module.setExports("no no no");
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/exportsEquals.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/exportsEquals.js
new file mode 100644
index 0000000..a9bbdd8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/exportsEquals.js
@@ -0,0 +1 @@
+module.exports = 4;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/green.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/green.js
new file mode 100644
index 0000000..8bca33c
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/green.js
@@ -0,0 +1,6 @@
+define('modules/green', ['./color'], function (color) {
+ return {
+ name: 'green',
+ parentType: color.type
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/lion.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/lion.js
new file mode 100644
index 0000000..f3962c1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/lion.js
@@ -0,0 +1,3 @@
+define(function(require) {
+ return 'lion';
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/orange.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/orange.js
new file mode 100644
index 0000000..d983a35
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/orange.js
@@ -0,0 +1,6 @@
+define(['./color'], function (color) {
+ return {
+ name: 'orange',
+ parentType: color.type
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/pollux.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/pollux.js
new file mode 100644
index 0000000..e49370b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/pollux.js
@@ -0,0 +1,6 @@
+define(['exports', './castor'], function(exports, castor) {
+ exports.name = 'pollux';
+ exports.getCastorName = function () {
+ return castor.name;
+ };
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/red.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/red.js
new file mode 100644
index 0000000..eb58660
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/red.js
@@ -0,0 +1,12 @@
+define(function (require) {
+ // comment fake-outs for require finding.
+ // require('bad1');
+ return {
+ name: 'red',
+ parentType: require('./color').type
+ };
+
+ /*
+ require('bad2');
+ */
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/setExports.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/setExports.js
new file mode 100644
index 0000000..290a3cb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/setExports.js
@@ -0,0 +1 @@
+module.setExports(5);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/subtract.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/subtract.js
new file mode 100644
index 0000000..2743132
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/subtract.js
@@ -0,0 +1,5 @@
+define(function () {
+ return function (a, b) {
+ return a - b;
+ }
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/tiger.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/tiger.js
new file mode 100644
index 0000000..9a98b76
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/tiger.js
@@ -0,0 +1,4 @@
+define(function (require, exports) {
+ exports.name = 'tiger';
+ exports.type = require('modules/types/cat').type;
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/traditional1.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/traditional1.js
new file mode 100644
index 0000000..d2e720d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/traditional1.js
@@ -0,0 +1,8 @@
+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/tools/addon-sdk-1.4/packages/api-utils/tests/modules/traditional2.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/traditional2.js
new file mode 100644
index 0000000..8363404
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/traditional2.js
@@ -0,0 +1,2 @@
+exports.name = 'traditional2';
+exports.traditional1Name = require('./traditional1').name;
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/modules/types/cat.js b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/types/cat.js
new file mode 100644
index 0000000..24a1c59
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/modules/types/cat.js
@@ -0,0 +1 @@
+exports.type = 'cat';
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-api-utils.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-api-utils.js
new file mode 100644
index 0000000..fc072bb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-api-utils.js
@@ -0,0 +1,274 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const apiUtils = require("api-utils/api-utils");
+
+exports.testPublicConstructor = function (test) {
+ function PrivateCtor() {}
+ PrivateCtor.prototype = {};
+
+ let PublicCtor = apiUtils.publicConstructor(PrivateCtor);
+ test.assert(
+ PrivateCtor.prototype.isPrototypeOf(PublicCtor.prototype),
+ "PrivateCtor.prototype should be prototype of PublicCtor.prototype"
+ );
+
+ function testObj(useNew) {
+ let obj = useNew ? new PublicCtor() : PublicCtor();
+ test.assert(obj instanceof PublicCtor,
+ "Object should be instance of PublicCtor");
+ test.assert(obj instanceof PrivateCtor,
+ "Object should be instance of PrivateCtor");
+ test.assert(PublicCtor.prototype.isPrototypeOf(obj),
+ "PublicCtor's prototype should be prototype of object");
+ test.assertEqual(obj.constructor, PublicCtor,
+ "Object constructor should be PublicCtor");
+ }
+ testObj(true);
+ testObj(false);
+};
+
+exports.testValidateOptionsEmpty = function (test) {
+ let val = apiUtils.validateOptions(null, {});
+ assertObjsEqual(test, val, {});
+
+ val = apiUtils.validateOptions(null, { foo: {} });
+ assertObjsEqual(test, val, {});
+
+ val = apiUtils.validateOptions({}, {});
+ assertObjsEqual(test, val, {});
+
+ val = apiUtils.validateOptions({}, { foo: {} });
+ assertObjsEqual(test, val, {});
+};
+
+exports.testValidateOptionsNonempty = function (test) {
+ let val = apiUtils.validateOptions({ foo: 123 }, {});
+ assertObjsEqual(test, val, {});
+
+ val = apiUtils.validateOptions({ foo: 123, bar: 456 },
+ { foo: {}, bar: {}, baz: {} });
+ assertObjsEqual(test, val, { foo: 123, bar: 456 });
+};
+
+exports.testValidateOptionsMap = function (test) {
+ let val = apiUtils.validateOptions({ foo: 3, bar: 2 }, {
+ foo: { map: function (v) v * v },
+ bar: { map: function (v) undefined }
+ });
+ assertObjsEqual(test, val, { foo: 9, bar: undefined });
+};
+
+exports.testValidateOptionsMapException = function (test) {
+ let val = apiUtils.validateOptions({ foo: 3 }, {
+ foo: { map: function () { throw new Error(); }}
+ });
+ assertObjsEqual(test, val, { foo: 3 });
+};
+
+exports.testValidateOptionsOk = function (test) {
+ let val = apiUtils.validateOptions({ foo: 3, bar: 2, baz: 1 }, {
+ foo: { ok: function (v) v },
+ bar: { ok: function (v) v }
+ });
+ assertObjsEqual(test, val, { foo: 3, bar: 2 });
+
+ test.assertRaises(
+ function () apiUtils.validateOptions({ foo: 2, bar: 2 }, {
+ bar: { ok: function (v) v > 2 }
+ }),
+ 'The option "bar" is invalid.',
+ "ok should raise exception on invalid option"
+ );
+
+ test.assertRaises(
+ function () apiUtils.validateOptions(null, { foo: { ok: function (v) v }}),
+ 'The option "foo" is invalid.',
+ "ok should raise exception on invalid option"
+ );
+};
+
+exports.testValidateOptionsIs = function (test) {
+ 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);
+ assertObjsEqual(test, val, opts);
+
+ test.assertRaises(
+ function () 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.testValidateOptionsMapIsOk = function (test) {
+ let [map, is, ok] = [false, false, false];
+ let val = apiUtils.validateOptions({ foo: 1337 }, {
+ foo: {
+ map: function (v) v.toString(),
+ is: ["string"],
+ ok: function (v) v.length > 0
+ }
+ });
+ assertObjsEqual(test, val, { foo: "1337" });
+
+ let requirements = {
+ foo: {
+ is: ["object"],
+ ok: function () test.fail("is should have caused us to throw by now")
+ }
+ };
+ test.assertRaises(
+ function () 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 (test) {
+ test.assertRaises(
+ function () apiUtils.validateOptions(null, {
+ foo: { ok: function (v) v, msg: "foo!" }
+ }),
+ "foo!",
+ "ok should raise exception with customized message"
+ );
+};
+
+exports.testValidateMapWithMissingKey = function (test) {
+ let val = apiUtils.validateOptions({ }, {
+ foo: {
+ map: function (v) v || "bar"
+ }
+ });
+ assertObjsEqual(test, val, { foo: "bar" });
+
+ val = apiUtils.validateOptions({ }, {
+ foo: {
+ map: function (v) { throw "bar" }
+ }
+ });
+ assertObjsEqual(test, val, { });
+};
+
+exports.testAddIterator = function testAddIterator(test) {
+ 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 each (let keyVal in keysVals)
+ yield keyVal;
+ }
+ );
+
+ let keysItr = [];
+ for (let key in obj)
+ keysItr.push(key);
+ test.assertEqual(keysItr.length, keys.length,
+ "the keys iterator returns the correct number of items");
+ for (let i = 0; i < keys.length; i++)
+ test.assertEqual(keysItr[i], keys[i], "the key is correct");
+
+ let valsItr = [];
+ for each (let val in obj)
+ valsItr.push(val);
+ test.assertEqual(valsItr.length, vals.length,
+ "the vals iterator returns the correct number of items");
+ for (let i = 0; i < vals.length; i++)
+ test.assertEqual(valsItr[i], vals[i], "the val is correct");
+
+ let keysValsItr = [];
+ for (let keyVal in Iterator(obj))
+ keysValsItr.push(keyVal);
+ test.assertEqual(keysValsItr.length, keysVals.length, "the keys/vals " +
+ "iterator returns the correct number of items");
+ for (let i = 0; i < keysVals.length; i++) {
+ test.assertEqual(keysValsItr[i][0], keysVals[i][0], "the key is correct");
+ test.assertEqual(keysValsItr[i][1], keysVals[i][1], "the val is correct");
+ }
+
+ let keysOnlyItr = [];
+ for (let key in Iterator(obj, true /* keysonly */))
+ keysOnlyItr.push(key);
+ test.assertEqual(keysOnlyItr.length, keysVals.length, "the keys only " +
+ "iterator returns the correct number of items");
+ for (let i = 0; i < keysVals.length; i++)
+ test.assertEqual(keysOnlyItr[i], keysVals[i][0], "the key is correct");
+};
+
+function assertObjsEqual(test, obj1, obj2) {
+ var items = 0;
+ for (let [key, val] in Iterator(obj1)) {
+ items++;
+ test.assert(key in obj2, "obj1 key should be present in obj2");
+ test.assertEqual(obj2[key], val, "obj1 value should match obj2 value");
+ }
+ for (let [key, val] in Iterator(obj2)) {
+ items++;
+ test.assert(key in obj1, "obj2 key should be present in obj1");
+ test.assertEqual(obj1[key], val, "obj2 value should match obj1 value");
+ }
+ if (!items)
+ test.assertEqual(JSON.stringify(obj1), JSON.stringify(obj2),
+ "obj1 should have same JSON representation as obj2");
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-app-strings.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-app-strings.js
new file mode 100644
index 0000000..3c41929
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-app-strings.js
@@ -0,0 +1,58 @@
+const { Cc,Ci } = require("chrome");
+
+let StringBundle = require("app-strings").StringBundle;
+exports.testStringBundle = function(test) {
+ let url = "chrome://global/locale/security/caps.properties";
+
+ let strings = StringBundle(url);
+
+ test.assertEqual(strings.url, url,
+ "'url' property contains correct URL of string bundle");
+
+ let appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"].
+ getService(Ci.nsILocaleService).
+ getApplicationLocale();
+
+ let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(url, appLocale);
+
+ let (name = "Yes") {
+ test.assertEqual(strings.get(name), stringBundle.GetStringFromName(name),
+ "getting a string returns the string");
+ }
+
+ let (name = "ExtensionCapability", args = ["foo"]) {
+ test.assertEqual(strings.get(name, args),
+ stringBundle.formatStringFromName(name, args, args.length),
+ "getting a formatted string returns the formatted string");
+ }
+
+ test.assertRaises(function () strings.get("nonexistentString"),
+ "String 'nonexistentString' could not be retrieved from " +
+ "the bundle due to an unknown error (it doesn't exist?).",
+ "retrieving a nonexistent string throws an exception");
+
+ let a = [], b = [];
+ let enumerator = stringBundle.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ a.push([elem.key, elem.value]);
+ }
+ for (let keyVal in Iterator(strings))
+ b.push(keyVal);
+
+ // Sort the arrays, because we don't assume enumeration has a set order.
+ // Sort compares [key, val] as string "key,val", which sorts the way we want
+ // it to, so there is no need to provide a custom compare function.
+ a.sort();
+ b.sort();
+
+ test.assertEqual(a.length, b.length,
+ "the iterator returns the correct number of items");
+ for (let i = 0; i < a.length; i++) {
+ test.assertEqual(a[i][0], b[i][0], "the iterated string's name is correct");
+ test.assertEqual(a[i][1], b[i][1],
+ "the iterated string's value is correct");
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-array.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-array.js
new file mode 100644
index 0000000..88aed24
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-array.js
@@ -0,0 +1,36 @@
+var array = require("array");
+
+exports.testHas = function(test) {
+ var testAry = [1, 2, 3];
+ test.assertEqual(array.has([1, 2, 3], 1), true);
+ test.assertEqual(testAry.length, 3);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(testAry[1], 2);
+ test.assertEqual(testAry[2], 3);
+ test.assertEqual(array.has(testAry, 2), true);
+ test.assertEqual(array.has(testAry, 3), true);
+ test.assertEqual(array.has(testAry, 4), false);
+ test.assertEqual(array.has(testAry, "1"), false);
+};
+
+exports.testAdd = function(test) {
+ var testAry = [1];
+ test.assertEqual(array.add(testAry, 1), false);
+ test.assertEqual(testAry.length, 1);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(array.add(testAry, 2), true);
+ test.assertEqual(testAry.length, 2);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(testAry[1], 2);
+};
+
+exports.testRemove = function(test) {
+ var testAry = [1, 2];
+ test.assertEqual(array.remove(testAry, 3), false);
+ test.assertEqual(testAry.length, 2);
+ test.assertEqual(testAry[0], 1);
+ test.assertEqual(testAry[1], 2);
+ test.assertEqual(array.remove(testAry, 2), true);
+ test.assertEqual(testAry.length, 1);
+ test.assertEqual(testAry[0], 1);
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-byte-streams.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-byte-streams.js
new file mode 100644
index 0000000..4eae428
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-byte-streams.js
@@ -0,0 +1,203 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const byteStreams = require("byte-streams");
+const file = require("file");
+const url = require("url");
+const { Loader } = require("./helpers");
+
+const STREAM_CLOSED_ERROR = "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 (test) {
+ let fname = dataFileFilename();
+
+ // Write a small string less than the stream's buffer size...
+ let str = "exports.testWriteRead data!";
+ let stream = open(test, fname, true);
+ test.assert(!stream.closed, "stream.closed after open should be false");
+ stream.write(str);
+ stream.close();
+ test.assert(stream.closed, "Stream should be closed after stream.close");
+ test.assertRaises(function () stream.write("This shouldn't be written!"),
+ STREAM_CLOSED_ERROR,
+ "stream.write after close should raise error");
+
+ // ... and read it.
+ stream = open(test, fname);
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ test.assertEqual(stream.read(), "",
+ "stream.read at EOS should return empty string");
+ stream.close();
+ test.assert(stream.closed, "Stream should be closed after stream.close");
+ test.assertRaises(function () 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 (test) {
+ 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(test, fname, true);
+ stream.write(str);
+ stream.close();
+ stream = open(test, fname);
+ test.assertEqual(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 (test) {
+ 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(test, 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(test, fname);
+ let readStr = "";
+ bufLen = BUFFER_BYTE_LEN;
+ let readLen = bufLen + 1;
+ do {
+ var frag = stream.read(readLen);
+ readStr += frag;
+ } while (frag);
+ stream.close();
+ test.assertEqual(readStr, str,
+ "stream.write and read in chunks should work as expected");
+ file.remove(fname);
+};
+
+exports.testReadLengths = function (test) {
+ let fname = dataFileFilename();
+ let str = "exports.testReadLengths data!";
+ let stream = open(test, fname, true);
+ stream.write(str);
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(str.length * 1000), str,
+ "stream.read with big byte length should return string " +
+ "written");
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(0), "",
+ "string.read with zero byte length should return empty " +
+ "string");
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(-1), "",
+ "string.read with negative byte length should return " +
+ "empty string");
+ stream.close();
+
+ file.remove(fname);
+};
+
+exports.testTruncate = function (test) {
+ let fname = dataFileFilename();
+ let str = "exports.testReadLengths data!";
+ let stream = open(test, fname, true);
+ stream.write(str);
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ stream.close();
+
+ stream = open(test, fname, true);
+ stream.close();
+
+ stream = open(test, fname);
+ test.assertEqual(stream.read(), "",
+ "stream.read after truncate should be empty");
+ stream.close();
+
+ file.remove(fname);
+};
+
+exports.testUnload = function (test) {
+ let loader = Loader(module);
+ let file = loader.require("file");
+
+ let filename = url.toFilename(module.uri);
+ let stream = file.open(filename, "b");
+
+ loader.unload();
+ test.assert(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() {
+ let dir = file.dirname(url.toFilename(module.uri));
+ return file.join(dir, "test-byte-streams-data");
+}
+
+// Opens and returns the given file and ensures it's of the correct class.
+function open(test, filename, forWriting) {
+ let stream = file.open(filename, forWriting ? "wb" : "b");
+ let klass = forWriting ? "ByteWriter" : "ByteReader";
+ test.assert(stream instanceof byteStreams[klass],
+ "Opened stream should be a " + klass);
+ return stream;
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-collection.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-collection.js
new file mode 100644
index 0000000..9db638e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-collection.js
@@ -0,0 +1,160 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const collection = require("collection");
+
+exports.testAddRemove = function (test) {
+ let coll = new collection.Collection();
+ compare(test, coll, []);
+ addRemove(test, coll, [], false);
+};
+
+exports.testAddRemoveBackingArray = function (test) {
+ let items = ["foo"];
+ let coll = new collection.Collection(items);
+ compare(test, coll, items);
+ addRemove(test, coll, items, true);
+
+ items = ["foo", "bar"];
+ coll = new collection.Collection(items);
+ compare(test, coll, items);
+ addRemove(test, coll, items, true);
+};
+
+exports.testProperty = function (test) {
+ let obj = makeObjWithCollProp();
+ compare(test, obj.coll, []);
+ addRemove(test, obj.coll, [], false);
+
+ // Test single-value set.
+ let items = ["foo"];
+ obj.coll = items[0];
+ compare(test, obj.coll, items);
+ addRemove(test, obj.coll, items, false);
+
+ // Test array set.
+ items = ["foo", "bar"];
+ obj.coll = items;
+ compare(test, obj.coll, items);
+ addRemove(test, obj.coll, items, false);
+};
+
+exports.testPropertyBackingArray = function (test) {
+ let items = ["foo"];
+ let obj = makeObjWithCollProp(items);
+ compare(test, obj.coll, items);
+ addRemove(test, obj.coll, items, true);
+
+ items = ["foo", "bar"];
+ obj = makeObjWithCollProp(items);
+ compare(test, obj.coll, items);
+ addRemove(test, 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(test, 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(test, coll, items);
+ coll.add(i);
+ if (!isBacking)
+ items.push(i);
+ }
+ compare(test, 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(test, 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(test, coll, items);
+ }
+ test.assertEqual(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(test, coll, items);
+
+ // Test add and remove([val1, val2]).
+ let newItems = [0, 1];
+ coll.add(newItems);
+ compare(test, coll, isBacking ? items : items.concat(newItems));
+ coll.remove(newItems);
+ compare(test, coll, items);
+ test.assertEqual(coll.length, numInitialItems,
+ "All inserted items should be removed");
+}
+
+// Asserts that the items in coll are the items of array.
+function compare(test, coll, array) {
+ test.assertEqual(coll.length, array.length,
+ "Collection length should be correct");
+ let numItems = 0;
+ for (let item in coll) {
+ test.assertEqual(item, array[numItems], "Items should be equal");
+ numItems++;
+ }
+ test.assertEqual(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;
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-commonjs-test-adapter.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-commonjs-test-adapter.js
new file mode 100644
index 0000000..71fc4a0
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-commonjs-test-adapter.js
@@ -0,0 +1,7 @@
+"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/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-loader.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-loader.js
new file mode 100644
index 0000000..0e91ee5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-loader.js
@@ -0,0 +1,223 @@
+"use strict";
+const { Loader } = require('content/loader');
+const self = require("self");
+
+exports['test:contentURL'] = function(test) {
+ let loader = Loader(),
+ value, emitted = 0, changes = 0;
+
+ test.assertRaises(
+ function() loader.contentURL = undefined,
+ 'The `contentURL` option must be a valid URL.',
+ 'Must throw an exception if `contentURL` is not URL.'
+ );
+ test.assertRaises(
+ function() loader.contentURL = null,
+ 'The `contentURL` option must be a valid URL.',
+ 'Must throw an exception if `contentURL` is not URL.'
+ );
+ test.assertRaises(
+ function() loader.contentURL = 4,
+ 'The `contentURL` option must be a valid URL.',
+ 'Must throw an exception if `contentURL` is not URL.'
+ );
+ test.assertRaises(
+ function() loader.contentURL = { toString: function() 'Oops' },
+ 'The `contentURL` option must be a valid URL.',
+ 'Must throw an exception if `contentURL` is not URL.'
+ );
+
+ function listener(e) {
+ emitted ++;
+ test.assert(
+ 'contentURL' in e,
+ 'emitted event must contain "content" property'
+ );
+ test.assert(
+ value,
+ '' + e.contentURL,
+ 'content property of an event must match value'
+ );
+ }
+ loader.on('propertyChange', listener);
+
+ test.assertEqual(
+ null,
+ loader.contentURL,
+ 'default value is `null`'
+ );
+ loader.contentURL = value = 'data:text/html,<html><body>Hi</body><html>';
+ test.assertEqual(
+ value,
+ '' + loader.contentURL,
+ 'data uri is ok'
+ );
+ test.assertEqual(
+ ++changes,
+ emitted,
+ 'had to emit `propertyChange`'
+ );
+ loader.contentURL = value;
+ test.assertEqual(
+ changes,
+ emitted,
+ 'must not emit `propertyChange` if same value is set'
+ );
+
+ loader.contentURL = value = 'http://google.com/';
+ test.assertEqual(
+ value,
+ '' + loader.contentURL,
+ 'value must be set'
+ );
+ test.assertEqual(
+ ++ changes,
+ emitted,
+ 'had to emit `propertyChange`'
+ );
+ loader.contentURL = value;
+ test.assertEqual(
+ changes,
+ emitted,
+ 'must not emit `propertyChange` if same value is set'
+ );
+
+ loader.removeListener('propertyChange', listener);
+ loader.contentURL = value = 'about:blank';
+ test.assertEqual(
+ value,
+ '' + loader.contentURL,
+ 'contentURL must be an actual value'
+ );
+ test.assertEqual(
+ changes,
+ emitted,
+ 'listener had to be romeved'
+ );
+};
+
+exports['test:contentScriptWhen'] = function(test) {
+ let loader = Loader();
+ test.assertEqual(
+ 'end',
+ loader.contentScriptWhen,
+ '`contentScriptWhen` defaults to "end"'
+ );
+ loader.contentScriptWhen = "end";
+ test.assertEqual(
+ "end",
+ loader.contentScriptWhen
+ );
+ try {
+ loader.contentScriptWhen = 'boom';
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The `contentScriptWhen` option must be either "start", "ready" or "end".',
+ e.message
+ );
+ }
+ loader.contentScriptWhen = null;
+ test.assertEqual(
+ 'end',
+ loader.contentScriptWhen,
+ '`contentScriptWhen` defaults to "end"'
+ );
+ loader.contentScriptWhen = "ready";
+ test.assertEqual(
+ "ready",
+ loader.contentScriptWhen
+ );
+ loader.contentScriptWhen = "start";
+ test.assertEqual(
+ 'start',
+ loader.contentScriptWhen
+ );
+};
+
+exports['test:contentScript'] = function(test) {
+ let loader = Loader(), value;
+ test.assertEqual(
+ null,
+ loader.contentScript,
+ '`contentScript` defaults to `null`'
+ );
+ loader.contentScript = value = 'let test = {};';
+ test.assertEqual(
+ value,
+ loader.contentScript
+ );
+ try {
+ loader.contentScript = { 1: value }
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The script option must be a string or an array of strings.',
+ e.message
+ );
+ }
+ try {
+ loader.contentScript = ['oue', 2]
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The script option must be a string or an array of strings.',
+ e.message
+ );
+ }
+ loader.contentScript = undefined;
+ test.assertEqual(
+ null,
+ loader.contentScript
+ );
+ loader.contentScript = value = ["1;", "2;"];
+ test.assertEqual(
+ value,
+ loader.contentScript
+ );
+};
+
+exports['test:contentScriptFile'] = function(test) {
+ let loader = Loader(), value, uri = self.data.url("test-content-loader.js");
+ test.assertEqual(
+ null,
+ loader.contentScriptFile,
+ '`contentScriptFile` defaults to `null`'
+ );
+ loader.contentScriptFile = value = uri;
+ test.assertEqual(
+ value,
+ loader.contentScriptFile
+ );
+ try {
+ loader.contentScriptFile = { 1: uri }
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The `contentScriptFile` option must be a local file URL or an array of'
+ + 'URLs.',
+ e.message
+ );
+ }
+ try {
+ loader.contentScriptFile = ['oue', uri]
+ test.fail('must throw when wrong value is set');
+ } catch(e) {
+ test.assertEqual(
+ 'The `contentScriptFile` option must be a local file URL or an array of'
+ + 'URLs.',
+ e.message
+ );
+ }
+ loader.contentScriptFile = undefined;
+ test.assertEqual(
+ null,
+ loader.contentScriptFile
+ );
+ loader.contentScriptFile = value = [uri];
+ test.assertEqual(
+ value,
+ loader.contentScriptFile
+ );
+};
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-proxy.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-proxy.js
new file mode 100644
index 0000000..1689c81
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-proxy.js
@@ -0,0 +1,754 @@
+const hiddenFrames = require("hidden-frame");
+const xulApp = require("xul-app");
+
+const { Loader } = require('./helpers');
+
+/*
+ * 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 (test) {
+ test.waitUntilDone();
+
+ let url = 'data:text/html,' + encodeURI(html);
+
+ let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
+ onReady: function () {
+
+ function onDOMReady() {
+ hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady,
+ false);
+
+ let xrayWindow = hiddenFrame.element.contentWindow;
+ let rawWindow = xrayWindow.wrappedJSObject;
+
+ let done = false;
+ let helper = {
+ xrayWindow: xrayWindow,
+ rawWindow: rawWindow,
+ createWorker: function (contentScript) {
+ return createWorker(test, xrayWindow, contentScript, helper.done);
+ },
+ done: function () {
+ if (done)
+ return;
+ done = true;
+ hiddenFrames.remove(hiddenFrame);
+ test.done();
+ }
+ }
+ callback(helper, test);
+ }
+
+ hiddenFrame.element.addEventListener("DOMContentLoaded", onDOMReady, false);
+ hiddenFrame.element.setAttribute("src", url);
+
+ }
+ }));
+ };
+}
+
+function createWorker(test, xrayWindow, contentScript, done) {
+ // We have to use Sandboxed loader in order to get access to the private
+ // unlock key `PRIVATE_KEY`. This key should not be used anywhere else.
+ // See `PRIVATE_KEY` definition in worker.js
+ let loader = Loader(module);
+ let Worker = loader.require("api-utils/content/worker").Worker;
+ let key = loader.sandbox("api-utils/content/worker").PRIVATE_KEY;
+ let worker = Worker({
+ exposeUnlockKey : key,
+ 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) {
+ test.assert(data.assertion, data.msg);
+ });
+
+ return worker;
+}
+
+/* Examples for the `createProxyTest` uses */
+
+let html = "<script>var documentGlobal = true</script>";
+exports.testCreateProxyTest = createProxyTest(html, function (helper, test) {
+ // You can get access to regular `test` object in second argument of
+ // `createProxyTest` method:
+ test.assert(helper.rawWindow.documentGlobal,
+ "You have access to a raw window reference via `helper.rawWindow`");
+ test.assert(!("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.testCreateProxyTestWithWorker = 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.testCreateProxyTestWithEvents = createProxyTest("", function (helper, test) {
+
+ let worker = helper.createWorker(
+ "new " + function WorkerScope() {
+ self.port.emit("foo");
+ }
+ );
+
+ worker.port.on("foo", function () {
+ test.pass("You can use events");
+ // And terminate your test with helper.done:
+ helper.done();
+ });
+
+});
+
+// Verify that the attribute `exposeUnlockKey`, that allow this test
+// to identify proxies, works correctly.
+// See `PRIVATE_KEY` definition in worker.js
+exports.testKeyAccess = createProxyTest("", function(helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ assert("UNWRAP_ACCESS_KEY" in window, "have access to `UNWRAP_ACCESS_KEY`");
+ done();
+ }
+ );
+
+});
+
+
+// Bug 714778: There was some issue around `toString` functions
+// that ended up being shared between content scripts
+exports.testSharedToStringProxies = createProxyTest("", function(helper) {
+
+ let worker = helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ assert(document.location.toString() == "data:text/html,",
+ "document.location.toString()");
+ self.postMessage("next");
+ }
+ );
+ worker.on("message", function () {
+ helper.createWorker(
+ 'new ' + function ContentScriptScope2() {
+ assert(document.location.toString() == "data:text/html,",
+ "document.location.toString()");
+ done();
+ }
+ );
+ });
+});
+
+
+// Ensure that postMessage is working correctly across documents with an iframe
+let html = '<iframe id="iframe" name="test" src="data:text/html," />';
+exports.testPostMessage = createProxyTest(html, function (helper, test) {
+ let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;
+ // Listen without proxies, to check that it will work in regular case
+ // simulate listening from a web document.
+ ifWindow.addEventListener("message", function listener(event) {
+ //if (event.source.wrappedJSObject == helper.rawWindow) return;
+ ifWindow.removeEventListener("message", listener, false);
+ // As we are in system principal, event is an XrayWrapper
+ test.assertEqual(event.source, ifWindow,
+ "event.source is the iframe window");
+ test.assertEqual(event.origin, "null", "origin is null");
+
+ test.assertEqual(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
+ "message data is correct");
+
+ helper.done();
+ }, false);
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ assert(postMessage === postMessage,
+ "verify that we doesn't generate multiple functions for the same method");
+
+ var json = JSON.stringify({foo : "bar\n \"escaped\"."});
+
+ document.getElementById("iframe").contentWindow.postMessage(json, "*");
+ }
+ );
+});
+
+let html = '<input id="input2" type="checkbox" />';
+exports.testObjectListener = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Test objects being given as event listener
+ let input = document.getElementById("input2");
+ let myClickListener = {
+ called: false,
+ handleEvent: function(event) {
+ assert(this === myClickListener, "`this` is the original object");
+ assert(!this.called, "called only once");
+ this.called = true;
+ assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
+ assert(event.target, input, "event.target is the wrapped window");
+ done();
+ }
+ };
+
+ window.addEventListener("click", myClickListener, true);
+ input.click();
+ window.removeEventListener("click", myClickListener, true);
+ }
+ );
+
+});
+
+exports.testObjectListener2 = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Verify object as DOM event listener
+ let myMessageListener = {
+ called: false,
+ handleEvent: function(event) {
+ window.removeEventListener("message", myMessageListener, true);
+
+ assert(this == myMessageListener, "`this` is the original object");
+ assert(!this.called, "called only once");
+ this.called = true;
+ assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
+ assert(event.target == document.defaultView, "event.target is the wrapped window");
+ assert(event.source == document.defaultView, "event.source is the wrapped window");
+ assert(event.origin == "null", "origin is null");
+ assert(event.data == "ok", "message data is correct");
+ done();
+ }
+ };
+
+ window.addEventListener("message", myMessageListener, true);
+ document.defaultView.postMessage("ok", '*');
+ }
+ );
+
+});
+
+let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
+ '<input id="input2" type="checkbox" />';
+exports.testStringOverload = createProxyTest(html, function (helper, test) {
+ // Proxy - toString error
+ let originalString = "string";
+ let p = Proxy.create({
+ get: function(receiver, name) {
+ if (name == "binded")
+ return originalString.toString.bind(originalString);
+ return originalString[name];
+ }
+ });
+ test.assertRaises(function () {
+ p.toString();
+ },
+ /String.prototype.toString called on incompatible Proxy/,
+ "toString can't be called with this being the proxy");
+ test.assertEqual(p.binded(), "string", "but it works if we bind this to the original string");
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // RightJS is hacking around String.prototype, and do similar thing:
+ // Pass `this` from a String prototype method.
+ // It is funny because typeof this == "object"!
+ // So that when we pass `this` to a native method,
+ // our proxy code can fail on another even more crazy thing.
+ // See following test to see what fails around proxies.
+ String.prototype.update = function () {
+ assert(typeof this == "object", "in update, `this` is an object");
+ assert(this.toString() == "input", "in update, `this.toString works");
+ return document.querySelectorAll(this);
+ };
+ assert("input".update().length == 3, "String.prototype overload works");
+ done();
+ }
+ );
+});
+
+exports.testMozMatchedSelector = createProxyTest("", function (helper) {
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Check mozMatchesSelector XrayWrappers bug:
+ // mozMatchesSelector returns bad results when we are not calling it from the node itself
+ // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
+ assert(document.createElement( "div" ).mozMatchesSelector("div"),
+ "mozMatchesSelector works while being called from the node");
+ assert(document.documentElement.mozMatchesSelector.call(
+ document.createElement( "div" ),
+ "div"
+ ),
+ "mozMatchesSelector works while being called from a " +
+ "function reference to " +
+ "document.documentElement.mozMatchesSelector.call");
+ done();
+ }
+ );
+});
+
+exports.testEventsOverload = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // If we add a "____proxy" attribute on XrayWrappers in order to store
+ // the related proxy to create an unique proxy for each wrapper;
+ // we end up setting this attribute to prototype objects :x
+ // And so, instances created with such prototype will be considered
+ // as equal to the prototype ...
+ // // Internal method that return the proxy for a given XrayWrapper
+ // function proxify(obj) {
+ // if (obj._proxy) return obj._proxy;
+ // return obj._proxy = Proxy.create(...);
+ // }
+ //
+ // // Get a proxy of an XrayWrapper prototype object
+ // let proto = proxify(xpcProto);
+ //
+ // // Use this proxy as a prototype
+ // function Constr() {}
+ // Constr.proto = proto;
+ //
+ // // Try to create an instance using this prototype
+ // let xpcInstance = new Constr();
+ // let wrapper = proxify(xpcInstance)
+ //
+ // xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto,
+ // xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :(
+ //
+ let proto = window.document.createEvent('HTMLEvents').__proto__;
+ window.Event.prototype = proto;
+ let event = document.createEvent('HTMLEvents');
+ assert(event !== proto, "Event should not be equal to its prototype");
+ event.initEvent('dataavailable', true, true);
+ assert(event.type === 'dataavailable', "Events are working fine");
+ done();
+ }
+ );
+
+});
+
+exports.testNestedAttributes = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // XrayWrappers has a bug when you set an attribute on it,
+ // in some cases, it creates an unnecessary wrapper that introduces
+ // a different object that refers to the same original object
+ // Check that our wrappers don't reproduce this bug
+ // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
+ let o = {sandboxObject:true};
+ window.nested = o;
+ o.foo = true;
+ assert(o === window.nested, "Nested attribute to sandbox object should not be proxified");
+ window.nested = document;
+ assert(window.nested === document, "Nested attribute to proxy should not be double proxified");
+ done();
+ }
+ );
+
+});
+
+exports.testFormNodeName = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ let body = document.body;
+ // Check form[nodeName]
+ let form = document.createElement("form");
+ let input = document.createElement("input");
+ input.setAttribute("name", "test");
+ form.appendChild(input);
+ body.appendChild(form);
+ assert(form.test == input, "form[nodeName] is valid");
+ body.removeChild(form);
+ done();
+ }
+ );
+
+});
+
+exports.testLocalStorage = createProxyTest("", function (helper, test) {
+
+ let worker = helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Check localStorage:
+ assert(window.localStorage, "has access to localStorage");
+ window.localStorage.name = 1;
+ assert(window.localStorage.name == 1, "localStorage appears to work");
+
+ self.port.on("step2", function () {
+ window.localStorage.clear();
+ assert(window.localStorage.name == undefined, "localStorage really, really works");
+ done();
+ });
+ self.port.emit("step1");
+ }
+ );
+
+ worker.port.on("step1", function () {
+ test.assertEqual(helper.rawWindow.localStorage.name, 1, "localStorage really works");
+ worker.port.emit("step2");
+ });
+
+});
+
+exports.testAutoUnwrapCustomAttributes = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ let body = document.body;
+ // Setting a custom object to a proxy attribute is not wrapped when we get it afterward
+ let object = {custom: true, enumerable: false};
+ body.customAttribute = object;
+ assert(body.customAttribute.valueOf() === body.customAttribute.valueOf(UNWRAP_ACCESS_KEY), "custom JS attributes are not wrapped");
+ assert(object === body.customAttribute, "custom JS attributes are not wrapped");
+ done();
+ }
+ );
+
+});
+
+exports.testObjectTag = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // <object>, <embed> and other tags return typeof 'function'
+ let flash = document.createElement("object");
+ assert(typeof flash == "function", "<object> is typeof 'function'");
+ assert(flash.toString().match(/\[object HTMLObjectElement.*\]/), "<object> is HTMLObjectElement");
+ assert("setAttribute" in flash, "<object> has a setAttribute method");
+ done();
+ }
+ );
+
+});
+
+exports.testHighlightToStringBehavior = createProxyTest("", function (helper, test) {
+ // We do not have any workaround this particular use of toString
+ // applied on <object> elements. So disable this test until we found one!
+ //test.assertEqual(helper.rawWindow.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement");
+ function f() {};
+ test.assertMatches(Object.prototype.toString.call(f), /\[object Function.*\]/, "functions are functions 1");
+ // This is how jquery call toString:
+ test.assertMatches(helper.rawWindow.Object.prototype.toString.call(""), /\[object String.*\]/, "strings are strings");
+ test.assertMatches(helper.rawWindow.Object.prototype.toString.call({}), /\[object Object.*\]/, "objects are objects");
+
+ // Make sure to pass a function from the same compartments
+ // or toString will return [object Object] on FF8+
+ let f2 = helper.rawWindow.eval("(function () {})");
+ test.assertMatches(helper.rawWindow.Object.prototype.toString.call(f2), /\[object Function.*\]/, "functions are functions 2");
+
+ helper.done();
+});
+
+exports.testDocumentTagName = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ let body = document.body;
+ // Check document[tagName]
+ let div = document.createElement("div");
+ div.setAttribute("name", "test");
+ body.appendChild(div);
+ assert(!document.test, "document[divName] is undefined");
+ body.removeChild(div);
+
+ let form = document.createElement("form");
+ form.setAttribute("name", "test");
+ body.appendChild(form);
+ assert(document.test == form, "document[formName] is valid");
+ body.removeChild(form);
+
+ let img = document.createElement("img");
+ img.setAttribute("name", "test");
+ body.appendChild(img);
+ assert(document.test == img, "document[imgName] is valid");
+ body.removeChild(img);
+ done();
+ }
+ );
+
+});
+
+let html = '<iframe id="iframe" name="test" src="data:text/html," />';
+exports.testWindowFrames = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'let glob = this; new ' + function ContentScriptScope() {
+ // Check window[frameName] and window.frames[i]
+ let iframe = document.getElementById("iframe");
+ //assert(window.frames.length == 1, "The iframe is reported in window.frames check1");
+ //assert(window.frames[0] == iframe.contentWindow, "The iframe is reported in window.frames check2");
+ //console.log(window.test+ "-"+iframe.contentWindow);
+ //console.log(window);
+ assert(window.test == iframe.contentWindow, "window[frameName] is valid");
+ done();
+ }
+ );
+
+});
+
+exports.testCollections = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Highlight XPCNativeWrapper bug with HTMLCollection
+ // tds[0] is only defined on first access :o
+ let body = document.body;
+ let div = document.createElement("div");
+ body.appendChild(div);
+ div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+ let tds = div.getElementsByTagName("td");
+ assert(tds[0] == tds[0], "We can get array element multiple times");
+ body.removeChild(div);
+ done();
+ }
+ );
+
+});
+
+let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
+ '<input id="input2" type="checkbox" />';
+exports.testCollections2 = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Verify that NodeList/HTMLCollection are working fine
+ let body = document.body;
+ let inputs = body.getElementsByTagName("input");
+ assert(body.childNodes.length == 3, "body.childNodes length is correct");
+ assert(inputs.length == 3, "inputs.length is correct");
+ assert(body.childNodes[0] == inputs[0], "body.childNodes[0] is correct");
+ assert(body.childNodes[1] == inputs[1], "body.childNodes[1] is correct");
+ assert(body.childNodes[2] == inputs[2], "body.childNodes[2] is correct");
+ let count = 0;
+ for(let i in body.childNodes) {
+ count++;
+ }
+ assert(count == 3, "body.childNodes is iterable");
+ done();
+ }
+ );
+
+});
+
+exports.testValueOf = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Check internal use of valueOf()
+ assert(window.valueOf().toString().match(/\[object Window.*\]/), "proxy.valueOf() returns the wrapped version");
+ assert(window.valueOf({}).toString().match(/\[object Window.*\]/), "proxy.valueOf({}) returns the wrapped version");
+ assert(window.valueOf(UNWRAP_ACCESS_KEY).toString().match(/\[object XrayWrapper \[object Window.*\].*\]/), "proxy.valueOf(UNWRAP_ACCESS_KEY) returns the unwrapped version");
+ done();
+ }
+ );
+
+});
+
+exports.testXMLHttpRequest = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // XMLHttpRequest doesn't support XMLHttpRequest.apply,
+ // that may break our proxy code
+ assert(window.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object");
+ done();
+ }
+ );
+
+});
+
+exports.testXPathResult = createProxyTest("", function (helper, test) {
+
+ // Check XPathResult bug with constants being undefined on
+ // XPCNativeWrapper
+ let value = helper.rawWindow.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE;
+ let xpcXPathResult = helper.xrayWindow.XPathResult;
+ test.assertEqual(xpcXPathResult.wrappedJSObject.
+ UNORDERED_NODE_SNAPSHOT_TYPE,
+ value,
+ "XPathResult's constants are valid on unwrapped node");
+
+ if (xulApp.versionInRange(xulApp.platformVersion, "10.0a1", "*")) {
+ test.assertEqual(xpcXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, 6,
+ "XPathResult's constants are defined on " +
+ "XPCNativeWrapper (platform bug #)");
+ }
+ else {
+ test.assertEqual(xpcXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
+ undefined,
+ "XPathResult's constants are undefined on " +
+ "XPCNativeWrapper (platform bug #665279)");
+ }
+
+ let worker = helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ self.port.on("value", function (value) {
+ // Check that our work around is working:
+ assert(window.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === value,
+ "XPathResult works correctly on Proxies");
+ done();
+ });
+ }
+ );
+ worker.port.emit("value", value);
+
+});
+
+exports.testPrototypeInheritance = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Verify that inherited prototype function like initEvent
+ // are handled correctly. (e2.type will return an error if it's not the case)
+ let event1 = document.createEvent( 'MouseEvents' );
+ event1.initEvent( "click", true, true );
+ let event2 = document.createEvent( 'MouseEvents' );
+ event2.initEvent( "click", true, true );
+ assert(event2.type == "click", "We are able to create an event");
+ done();
+ }
+ );
+
+});
+
+exports.testFunctions = createProxyTest("", function (helper) {
+
+ helper.rawWindow.callFunction = function (f) f();
+ helper.rawWindow.isEqual = function (a, b) a == b;
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Check basic usage of functions
+ let closure2 = function () {return "ok";};
+ assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
+
+ // Ensure that functions are cached when being wrapped to native code
+ let closure = function () {};
+ assert(window.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native");
+ done();
+ }
+ );
+
+});
+
+let html = '<input id="input2" type="checkbox" />';
+exports.testListeners = createProxyTest(html, function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ // Verify listeners:
+ let input = document.getElementById("input2");
+ assert(input, "proxy.getElementById works");
+
+ function onclick() {};
+ input.onclick = onclick;
+ assert(input.onclick === onclick, "on* attributes are equal to original function set");
+
+ let addEventListenerCalled = false;
+ let expandoCalled = false;
+ input.addEventListener("click", function onclick(event) {
+ input.removeEventListener("click", onclick, true);
+
+ assert(!addEventListenerCalled, "closure given to addEventListener is called once");
+ if (addEventListenerCalled)
+ return;
+ addEventListenerCalled = true;
+
+ assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
+ assert("__isWrappedProxy" in event.target, "event object is a proxy");
+
+ let input2 = document.getElementById("input2");
+
+ input.onclick = function (event) {
+ input.onclick = null;
+ assert(!expandoCalled, "closure set to expando is called once");
+ if (expandoCalled) return;
+ expandoCalled = true;
+
+ assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
+ assert("__isWrappedProxy" in event.target, "event object is a proxy");
+
+ setTimeout(function () {
+ input.click();
+ done();
+ }, 0);
+
+ }
+
+ setTimeout(function () {
+ input.click();
+ }, 0);
+
+ }, true);
+
+ input.click();
+ }
+ );
+
+});
+
+exports.testMozRequestAnimationFrame = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ window.mozRequestAnimationFrame(function callback() {
+ assert(callback == this, "callback is equal to `this`");
+ done();
+ });
+ }
+ );
+
+});
+
+exports.testGlobalScope = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'let toplevelScope = true;' +
+ 'assert(window.toplevelScope, "variables in toplevel scope are set to `window` object");' +
+ 'assert(this.toplevelScope, "variables in toplevel scope are set to `this` object");' +
+ 'done();'
+ );
+
+});
+
+// Bug 671016: Typed arrays should not be proxified
+exports.testTypedArrays = createProxyTest("", function (helper) {
+
+ helper.createWorker(
+ 'new ' + function ContentScriptScope() {
+ let canvas = document.createElement("canvas");
+ let context = canvas.getContext("2d");
+ let imageData = context.getImageData(0,0, 1, 1);
+ let unwrappedData = imageData.valueOf(UNWRAP_ACCESS_KEY).data;
+ let data = imageData.data;
+ assert(unwrappedData === data, "Typed array isn't proxified")
+ done();
+ }
+ );
+
+});
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-symbiont.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-symbiont.js
new file mode 100644
index 0000000..3bd2890
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-symbiont.js
@@ -0,0 +1,186 @@
+"use strict";
+
+const { Cc, Ci } = require('chrome');
+const { Symbiont } = require('content/symbiont');
+const self = require("self");
+
+function makeWindow() {
+ let content =
+ '<?xml version="1.0"?>' +
+ '<window ' +
+ 'xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">' +
+ '<iframe id="content" type="content"/>' +
+ '</window>';
+ var url = "data:application/vnd.mozilla.xul+xml," +
+ encodeURIComponent(content);
+ var features = ["chrome", "width=10", "height=10"];
+
+ return Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher).
+ openWindow(null, url, null, features.join(","), null);
+}
+
+exports['test:constructing symbiont && validating API'] = function(test) {
+ let window = makeWindow();
+ window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, false);
+ let frame = window.document.getElementById("content");
+ // TODO: support arrays ??
+ let contentScripts = ["1;", "2;"];
+ let contentSymbiont = Symbiont({
+ frame: frame,
+ contentScriptFile: self.data.url("test-content-symbiont.js"),
+ contentScript: contentScripts,
+ contentScriptWhen: "start"
+ });
+
+ test.assertEqual(
+ self.data.url("test-content-symbiont.js"),
+ contentSymbiont.contentScriptFile,
+ "There is one contentScriptFile, as specified in options."
+ );
+ test.assertEqual(
+ contentScripts.length,
+ contentSymbiont.contentScript.length,
+ "There are two contentScripts, as specified in options."
+ );
+ test.assertEqual(
+ contentScripts[0],
+ contentSymbiont.contentScript[0],
+ "There are two contentScripts, as specified in options."
+ );
+ test.assertEqual(
+ contentScripts[1],
+ contentSymbiont.contentScript[1],
+ "There are two contentScripts, as specified in options."
+ )
+ test.assertEqual(
+ contentSymbiont.contentScriptWhen,
+ "start",
+ "contentScriptWhen is as specified in options."
+ );
+
+ test.done();
+ window.close();
+ frame.setAttribute("src", "data:text/html,<html><body></body></html>");
+ }, false);
+ test.waitUntilDone();
+};
+
+exports["test:communication with worker global scope"] = function(test) {
+ let window = makeWindow();
+ let contentSymbiont;
+
+ function onMessage1(message) {
+ test.assertEqual(message, 1, "Program gets message via onMessage.");
+ contentSymbiont.removeListener('message', onMessage1);
+ contentSymbiont.on('message', onMessage2);
+ contentSymbiont.postMessage(2);
+ };
+
+ function onMessage2(message) {
+ if (5 == message) {
+ test.done();
+ } else {
+ test.assertEqual(message, 3, "Program gets message via onMessage2.");
+ contentSymbiont.postMessage(4)
+ }
+ }
+
+ window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, false);
+ let frame = window.document.getElementById("content");
+ contentSymbiont = Symbiont({
+ frame: frame,
+ contentScript: 'new ' + function() {
+ self.postMessage(1);
+ self.on("message", function onMessage(message) {
+ if (message === 2)
+ self.postMessage(3);
+ if (message === 4)
+ self.postMessage(5);
+ });
+ } + '()',
+ contentScriptWhen: 'ready',
+ onMessage: onMessage1
+ });
+
+ frame.setAttribute("src", "data:text/html,<html><body></body></html>");
+ }, false);
+ test.waitUntilDone();
+};
+
+exports['test:pageWorker'] = function(test) {
+ test.waitUntilDone();
+ let worker = Symbiont({
+ contentURL: 'about:buildconfig',
+ contentScript: 'new ' + function WorkerScope() {
+ self.on('message', function(data) {
+ if (data.valid)
+ self.postMessage('bye!');
+ })
+ self.postMessage(window.location.toString());
+ },
+ onMessage: function(msg) {
+ if (msg == 'bye!') {
+ test.done()
+ } else {
+ test.assertEqual(
+ worker.contentURL + '',
+ msg
+ );
+ worker.postMessage({ valid: true });
+ }
+ }
+ });
+};
+
+exports["test:document element present on 'start'"] = function(test) {
+ test.waitUntilDone();
+ let xulApp = require("xul-app");
+ let worker = Symbiont({
+ contentURL: "about:buildconfig",
+ contentScript: "self.postMessage(!!document.documentElement)",
+ contentScriptWhen: "start",
+ onMessage: function(message) {
+ if (xulApp.versionInRange(xulApp.platformVersion, "2.0b6", "*"))
+ test.assert(message, "document element present on 'start'");
+ else
+ test.pass("document element not necessarily present on 'start'");
+ test.done();
+ }
+ });
+};
+
+exports["test:direct communication with trusted document"] = function(test) {
+ test.waitUntilDone();
+
+ let worker = Symbiont({
+ contentURL: require("self").data.url("test-trusted-document.html")
+ });
+
+ worker.port.on('document-to-addon', function (arg) {
+ test.assertEqual(arg, "ok", "Received an event from the document");
+ worker.destroy();
+ test.done();
+ });
+ worker.port.emit('addon-to-document', 'ok');
+};
+
+exports["test:`addon` is not available when a content script is set"] = function(test) {
+ test.waitUntilDone();
+
+ let worker = Symbiont({
+ contentURL: require("self").data.url("test-trusted-document.html"),
+ contentScript: "new " + function ContentScriptScope() {
+ self.port.emit("cs-to-addon", "addon" in unsafeWindow);
+ }
+ });
+
+ worker.port.on('cs-to-addon', function (hasAddon) {
+ test.assertEqual(hasAddon, false,
+ "`addon` is not available");
+ worker.destroy();
+ test.done();
+ });
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-worker.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-worker.js
new file mode 100644
index 0000000..ced0b60
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-content-worker.js
@@ -0,0 +1,374 @@
+"use stirct";
+
+const { Cc, Ci } = require('chrome');
+const timer = require('timer');
+
+function makeWindow(contentURL) {
+ let content =
+ '<?xml version="1.0"?>' +
+ '<window ' +
+ 'xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">' +
+ '<iframe id="content" type="content" src="' +
+ encodeURIComponent(contentURL) + '"/>' +
+ '<script>var documentValue=true;</script>' +
+ '</window>';
+ var url = "data:application/vnd.mozilla.xul+xml," +
+ encodeURIComponent(content);
+ var features = ["chrome", "width=10", "height=10"];
+
+ return Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher).
+ openWindow(null, url, null, features.join(","), null);
+}
+
+const { Worker } = require('content/worker');
+exports['test:sample'] = function(test) {
+ let window = makeWindow();
+ test.waitUntilDone();
+
+ // As window has just being created, its document is still loading,
+ // and we have about:blank document before the expected one
+ test.assertEqual(window.document.location.href, "about:blank",
+ "window starts by loading about:blank");
+
+ // We need to wait for the load/unload of temporary about:blank
+ // or our worker is going to be automatically destroyed
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ test.assertNotEqual(window.document.location.href, "about:blank",
+ "window is now on the right document");
+
+ let worker = Worker({
+ window: window,
+ contentScript: 'new ' + function WorkerScope() {
+ // window is accessible
+ let myLocation = window.location.toString();
+ self.on('message', function(data) {
+ if (data == 'hi!')
+ self.postMessage('bye!');
+ });
+ },
+ contentScriptWhen: 'ready',
+ onMessage: function(msg) {
+ test.assertEqual('bye!', msg);
+ test.assertEqual(worker.url, window.document.location.href,
+ "worker.url still works");
+ test.done();
+ }
+ });
+
+ test.assertEqual(worker.url, window.document.location.href,
+ "worker.url works");
+ worker.postMessage('hi!');
+
+ }, true);
+
+}
+
+exports['test:emit'] = function(test) {
+ let window = makeWindow();
+ test.waitUntilDone();
+
+ let worker = Worker({
+ window: window,
+ contentScript: 'new ' + function WorkerScope() {
+ // Validate self.on and self.emit
+ self.port.on('addon-to-content', function (data) {
+ self.port.emit('content-to-addon', data);
+ });
+
+ // Check for global pollution
+ //if (typeof on != "undefined")
+ // self.postMessage("`on` is in globals");
+ if (typeof once != "undefined")
+ self.postMessage("`once` is in globals");
+ if (typeof emit != "undefined")
+ self.postMessage("`emit` is in globals");
+
+ },
+ onMessage: function(msg) {
+ test.fail("Got an unexpected message : "+msg);
+ }
+ });
+
+ // Validate worker.port
+ worker.port.on('content-to-addon', function (data) {
+ test.assertEqual(data, "event data");
+ test.done();
+ });
+ worker.port.emit('addon-to-content', 'event data');
+
+}
+
+exports['test:emit hack message'] = function(test) {
+ let window = makeWindow();
+ test.waitUntilDone();
+
+ let worker = Worker({
+ window: window,
+ contentScript: 'new ' + function WorkerScope() {
+ // Validate self.port
+ self.port.on('message', function (data) {
+ self.port.emit('message', data);
+ });
+ // We should not receive message on self, but only on self.port
+ self.on('message', function (data) {
+ self.postMessage('message', data);
+ });
+ },
+ onError: function(e) {
+ test.fail("Got exception: "+e);
+ }
+ });
+
+ worker.port.on('message', function (data) {
+ test.assertEqual(data, "event data");
+ test.done();
+ });
+ worker.on('message', function (data) {
+ test.fail("Got an unexpected message : "+msg);
+ });
+ worker.port.emit('message', 'event data');
+
+}
+
+exports['test:n-arguments emit'] = function(test) {
+ let window = makeWindow();
+ test.waitUntilDone();
+
+ let worker = Worker({
+ window: window,
+ contentScript: 'new ' + function WorkerScope() {
+ // Validate self.on and self.emit
+ self.port.on('addon-to-content', function (a1, a2, a3) {
+ self.port.emit('content-to-addon', a1, a2, a3);
+ });
+ }
+ });
+
+ // Validate worker.port
+ worker.port.on('content-to-addon', function (arg1, arg2, arg3) {
+ test.assertEqual(arg1, "first argument");
+ test.assertEqual(arg2, "second");
+ test.assertEqual(arg3, "third");
+ test.done();
+ });
+ worker.port.emit('addon-to-content', 'first argument', 'second', 'third');
+}
+
+exports['test:post-json-values-only'] = function(test) {
+ let window = makeWindow("data:text/html,");
+ test.waitUntilDone();
+
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ let worker = Worker({
+ window: window.document.getElementById("content").contentWindow,
+ contentScript: 'new ' + function WorkerScope() {
+ self.on('message', function (message) {
+ self.postMessage([ message.fun === undefined,
+ typeof message.w,
+ message.w && "port" in message.w,
+ message.w.url,
+ Array.isArray(message.array),
+ JSON.stringify(message.array)]);
+ });
+ }
+ });
+
+ // Validate worker.onMessage
+ let array = [1, 2, 3];
+ worker.on('message', function (message) {
+ test.assert(message[0], "function becomes undefined");
+ test.assertEqual(message[1], "object", "object stays object");
+ test.assert(message[2], "object's attributes are enumerable");
+ test.assertEqual(message[3], "about:blank", "jsonable attributes are accessible");
+ // See bug 714891, Arrays may be broken over compartements:
+ test.assert(message[4], "Array keeps being an array");
+ test.assertEqual(message[5], JSON.stringify(array),
+ "Array is correctly serialized");
+ test.done();
+ });
+ worker.postMessage({ fun: function () {}, w: worker, array: array });
+
+ }, true);
+
+};
+
+exports['test:emit-json-values-only'] = function(test) {
+ let window = makeWindow("data:text/html,");
+ test.waitUntilDone();
+
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ let win = window.document.getElementById("content").contentWindow;
+ let worker = Worker({
+ window: win,
+ contentScript: 'new ' + function WorkerScope() {
+ // Validate self.on and self.emit
+ self.port.on('addon-to-content', function (fun, w, obj, array) {
+ self.port.emit('content-to-addon', [
+ fun === null,
+ typeof w,
+ "port" in w,
+ w.url,
+ "fun" in obj,
+ Object.keys(obj.dom).length,
+ Array.isArray(array),
+ JSON.stringify(array)
+ ]);
+ });
+ }
+ });
+
+ // Validate worker.port
+ let array = [1, 2, 3];
+ worker.port.on('content-to-addon', function (result) {
+ test.assert(result[0], "functions become null");
+ test.assertEqual(result[1], "object", "objects stay objects");
+ test.assert(result[2], "object's attributes are enumerable");
+ test.assertEqual(result[3], "about:blank", "json attribute is accessible");
+ test.assert(!result[4], "function as object attribute is removed");
+ test.assertEqual(result[5], 0, "DOM nodes are converted into empty object");
+ // See bug 714891, Arrays may be broken over compartements:
+ test.assert(result[6], "Array keeps being an array");
+ test.assertEqual(result[7], JSON.stringify(array),
+ "Array is correctly serialized");
+ test.done();
+ });
+ let obj = {
+ fun: function () {},
+ dom: win.document.documentElement
+ };
+ worker.port.emit('addon-to-content', function () {}, worker, obj, array);
+
+ }, true);
+
+}
+
+exports['test:content is wrapped'] = function(test) {
+ let contentURL = 'data:text/html,<script>var documentValue=true;</script>';
+ let window = makeWindow(contentURL);
+ test.waitUntilDone();
+
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ let worker = Worker({
+ window: window.document.getElementById("content").contentWindow,
+ contentScript: 'new ' + function WorkerScope() {
+ self.postMessage(!window.documentValue);
+ },
+ contentScriptWhen: 'ready',
+ onMessage: function(msg) {
+ test.assert(msg,
+ "content script has a wrapped access to content document");
+ test.done();
+ }
+ });
+
+ }, true);
+
+}
+
+exports['test:chrome is unwrapped'] = function(test) {
+ let window = makeWindow();
+ test.waitUntilDone();
+
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ let worker = Worker({
+ window: window,
+ contentScript: 'new ' + function WorkerScope() {
+ self.postMessage(window.documentValue);
+ },
+ contentScriptWhen: 'ready',
+ onMessage: function(msg) {
+ test.assert(msg,
+ "content script has an unwrapped access to chrome document");
+ test.done();
+ }
+ });
+
+ }, true);
+
+}
+
+exports['test:setTimeout can\'t be cancelled by content'] = function(test) {
+ let contentURL = 'data:text/html,<script>var documentValue=true;</script>';
+ let window = makeWindow(contentURL);
+ test.waitUntilDone();
+
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ let worker = Worker({
+ window: window.document.getElementById("content").contentWindow,
+ contentScript: 'new ' + function WorkerScope() {
+ let id = setTimeout(function () {
+ self.postMessage("timeout");
+ }, 100);
+ unsafeWindow.eval("clearTimeout("+id+");");
+ },
+ contentScriptWhen: 'ready',
+ onMessage: function(msg) {
+ test.assert(msg,
+ "content didn't managed to cancel our setTimeout");
+ test.done();
+ }
+ });
+
+ }, true);
+
+}
+
+exports['test:setTimeout are unregistered on content unload'] = function(test) {
+ let contentURL = 'data:text/html,foo';
+ let window = makeWindow(contentURL);
+ test.waitUntilDone();
+
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ let iframe = window.document.getElementById("content");
+ let originalDocument = iframe.contentDocument;
+ let worker = Worker({
+ window: iframe.contentWindow,
+ contentScript: 'new ' + function WorkerScope() {
+ document.title = "ok";
+ let i = 0;
+ setInterval(function () {
+ document.title = i++;
+ }, 10);
+ },
+ contentScriptWhen: 'ready'
+ });
+
+ // Change location so that content script is destroyed,
+ // and all setTimeout/setInterval should be unregistered.
+ // Wait some cycles in order to execute some intervals.
+ timer.setTimeout(function () {
+ // Bug 689621: Wait for the new document load so that we are sure that
+ // previous document cancelled its intervals
+ iframe.addEventListener("load", function onload() {
+ iframe.removeEventListener("load", onload, true);
+ let titleAfterLoad = originalDocument.title;
+ // Wait additional cycles to verify that intervals are really cancelled
+ timer.setTimeout(function () {
+ test.assertEqual(iframe.contentDocument.title, "final",
+ "New document has not been modified");
+ test.assertEqual(originalDocument.title, titleAfterLoad,
+ "Nor previous one");
+ test.done();
+ }, 100);
+ }, true);
+ iframe.setAttribute("src", "data:text/html,<title>final</title>");
+ }, 100);
+
+ }, true);
+
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-cortex.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-cortex.js
new file mode 100644
index 0000000..11ed397
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-cortex.js
@@ -0,0 +1,118 @@
+// vim:set ts=2 sw=2 sts=2
+
+"use strict";
+
+var Cortex = require("cortex").Cortex;
+
+exports["test property changes propagate"] = function (assert) {
+ var source = {
+ _foo: "secret",
+ get foo() {
+ return this._foo;
+ },
+ set foo(value) {
+ this._foo = value;
+ },
+ get getOnly() {
+ return this._foo;
+ },
+ set setOnly(value) {
+ this._setOnly = value;
+ },
+ bar: "public",
+ method: function method(a, b) {
+ return this._foo + a + b
+ }
+ };
+ var fixture = Cortex(source);
+
+ assert.ok(!('_foo' in fixture),
+ "properties that start with `_` are omitted");
+ assert.equal(fixture.foo, "secret", "get accessor alias works");
+ fixture.foo = "new secret";
+ assert.equal(fixture.foo, "new secret", "set accessor alias works");
+ assert.equal(source.foo, "new secret", "accessor delegates to the source");
+ assert.equal(fixture.bar, "public", "data property alias works");
+ fixture.bar = "bar";
+ assert.equal(source.bar, "bar", "data property change propagates");
+ source.bar = "foo"
+ assert.equal(fixture.bar, "foo", "data property change probagets back");
+ assert.equal(fixture.method("a", "b"), "new secretab",
+ "public methods are callable");
+ assert.equal(fixture.method.call({ _foo: "test" }, " a,", "b"),
+ "new secret a,b",
+ "`this` pseudo-variable can not be passed through call.");
+ assert.equal(fixture.method.apply({ _foo: "test" }, [" a,", "b"]),
+ "new secret a,b",
+ "`this` pseudo-variable can not be passed through apply.");
+ assert.equal(fixture.getOnly, source._foo,
+ "getter returned property of wrapped object");
+ fixture.setOnly = 'bar'
+ assert.equal(source._setOnly, 'bar', "setter modified wrapped object")
+};
+
+
+exports["test immunity of inheritance"] = function(assert) {
+ function Type() {}
+ Type.prototype = {
+ constructor: Type,
+ _bar: 2,
+ bar: 3,
+ get_Foo: function getFoo() {
+ return this._foo;
+ }
+ }
+ var source = Object.create(Type.prototype, {
+ _foo: { value: 'secret' },
+ getBar: { value: function get_Bar() {
+ return this.bar
+ }},
+ get_Bar: { value: function getBar() {
+ return this._bar
+ }}
+ });
+
+ var fixture = Cortex(source);
+
+ assert.ok(Cortex({}, null, Type.prototype) instanceof Type,
+ "if custom prototype is providede cortex will inherit from it");
+ assert.ok(fixture instanceof Type,
+ "if no prototype is given cortex inherits from object's prototype");
+
+ source.bar += 1;
+ assert.notEqual(fixture.bar, source.bar,
+ "chages of properties don't propagate to non-aliases");
+ assert.equal(fixture.getBar(), source.bar,
+ "methods accessing public properties are bound to the source");
+
+ fixture._bar += 1;
+ assert.notEqual(fixture._bar, source._bar,
+ "changes of non aliased properties don't propagate");
+ assert.equal(fixture.get_Bar(), source._bar,
+ "methods accessing privates are bound to the source");
+ assert.notEqual(fixture.get_Foo(), source._foo,
+ "prototoype methods are not bound to the source");
+}
+
+exports["test customized public properties"] = function(assert) {
+ var source = {
+ _a: 'a',
+ b: 'b',
+ get: function get(name) {
+ return this[name];
+ }
+ };
+
+ var fixture = Cortex(source, ['_a', 'get']);
+ fixture._a += "#change";
+
+
+ assert.ok(!("b" in fixture), "non-public own property is not defined");
+ assert.equal(fixture.get("b"), source.b,
+ "public methods preserve access to the private properties");
+ assert.equal(fixture._a, source._a,
+ "custom public property changes propagate");
+}
+
+//if (require.main == module)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-dom.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-dom.js
new file mode 100644
index 0000000..57b1afe
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-dom.js
@@ -0,0 +1,84 @@
+"use strict";
+
+const events = require("dom/events");
+const { activeBrowserWindow: { document } } = require("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/tools/addon-sdk-1.4/packages/api-utils/tests/test-e10s-porting.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-e10s-porting.js
new file mode 100644
index 0000000..3dc4d32
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-e10s-porting.js
@@ -0,0 +1,73 @@
+// This file just runs all test suites we've white-listed as being
+// compatible with E10s. Once we're done with the porting effort,
+// we'll just enable cfx's '--e10s' option by default and remove
+// this file.
+
+// This is just to serve as an indicator not to run these tests in
+// the addon process.
+require("chrome");
+
+const E10S_COMPATIBLE_TEST_SUITES = [
+ 'test-api-utils.js',
+ 'test-traits-core.js',
+ 'test-traits.js',
+ 'test-list.js',
+ 'test-self.js'
+];
+
+exports.runE10SCompatibleTestSuites = function(test) {
+ var xulApp = require("xul-app");
+ if (xulApp.is("Firefox") &&
+ xulApp.versionInRange(xulApp.version, "4.0b7", "4.0b8pre")) {
+ test.pass("Due to bug 609066, Firefox 4.0b7 will never pass this test, " +
+ "so we'll skip it.");
+ return;
+ }
+
+ // If the "jetpack/service" XPCOM component is not present, then the host
+ // application does not support e10s, so we can't run any e10s-compatible
+ // test suites under e10s mode.
+ if (!require("chrome").Cc["@mozilla.org/jetpack/service;1"]) {
+ test.pass("This application does not support e10s.");
+ return;
+ }
+
+ if (packaging.enableE10s) {
+ // Don't worry about running these E10S-compatible test
+ // suites, cfx will find them by default because its
+ // '--e10s' option is enabled.
+ test.pass("'cfx --e10s' detected, skipping this test.");
+ return;
+ }
+
+ var {TestFinder} = require("unit-test-finder");
+ var {TestRunner} = require("unit-test");
+ var url = require("url");
+
+ var thisDir = url.toFilename(url.URL('./', __url__));
+ var finder = new TestFinder({
+ dirs: [thisDir],
+ filter: function(name) {
+ return E10S_COMPATIBLE_TEST_SUITES.indexOf(name) != -1;
+ },
+ testInProcess: false,
+ testOutOfProcess: true
+ });
+ var runner = new TestRunner();
+ finder.findTests(function(tests) {
+ test.assert(tests.length >= 1, "must find at least one test");
+ runner.startMany({
+ tests: tests,
+ onDone: function(runner) {
+ test.assertEqual(runner.failed, 0,
+ "No tests in addon process should have failed");
+ test.assert(runner.passed > 0,
+ "Some tests in addon process must have been run");
+ test.failed += runner.failed;
+ test.passed += runner.passed;
+ test.done();
+ }
+ });
+ });
+ test.waitUntilDone();
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-environment.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-environment.js
new file mode 100644
index 0000000..24077a6
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-environment.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const { env } = require('api-utils/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 varibale is set');
+ delete env.BLA4;
+ assert.equal(env.BLA4, undefined, 'BLA4 env variable is unset');
+ assert.equal('BLA4' in env, false, 'BLA4 env variable no longer exists' );
+};
+
+require('test').run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-errors.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-errors.js
new file mode 100644
index 0000000..6db9c87
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-errors.js
@@ -0,0 +1,66 @@
+var errors = require("errors");
+
+exports.testCatchAndLog = function(test) {
+ var caught = [];
+ function dummyLog(e) { caught.push(e); }
+
+ var wrapped = errors.catchAndLog(function(x) {
+ throw Error("blah" + x + this);
+ },
+ "boop",
+ dummyLog);
+ test.assertEqual(wrapped.call("hi", 1), "boop",
+ "exceptions should be trapped, def. resp. returned");
+ test.assertEqual(caught.length, 1,
+ "logging function should be called");
+ test.assertEqual(caught[0].message, "blah1hi",
+ "args and this should be passed to wrapped func");
+};
+
+exports.testCatchAndLogProps = function(test) {
+ var caught = [];
+ function dummyLog(e) { caught.push(e); }
+
+ var thing = {
+ foo: function(x) { throw Error("nowai" + x); },
+ bar: function() { throw Error("blah"); },
+ baz: function() { throw Error("fnarg"); }
+ };
+
+ errors.catchAndLogProps(thing, "foo", "ugh", dummyLog);
+
+ test.assertEqual(thing.foo(1), "ugh",
+ "props should be wrapped");
+ test.assertEqual(caught.length, 1,
+ "logging function should be called");
+ test.assertEqual(caught[0].message, "nowai1",
+ "args should be passed to wrapped func");
+ test.assertRaises(function() { thing.bar(); },
+ "blah",
+ "non-wrapped props should be wrapped");
+
+ errors.catchAndLogProps(thing, ["bar", "baz"], "err", dummyLog);
+ test.assert((thing.bar() == thing.baz()) &&
+ (thing.bar() == "err"),
+ "multiple props should be wrapped if array passed in");
+};
+
+exports.testCatchAndReturn = function(test) {
+ var wrapped = errors.catchAndReturn(function(x) {
+ if (x == 1)
+ return "one";
+ if (x == 2)
+ throw new Error("two");
+ return this + x;
+ });
+
+ test.assertEqual(wrapped(1).returnValue, "one",
+ "arg should be passed; return value should be returned");
+ test.assert(wrapped(2).exception, "exception should be returned");
+ test.assertEqual(wrapped(2).exception.message, "two", "message is correct");
+ test.assert(wrapped(2).exception.fileName.indexOf("test-errors.js") != -1,
+ "filename is present");
+ test.assert(wrapped(2).exception.stack, "stack is available");
+ test.assertEqual(wrapped.call("hi", 3).returnValue, "hi3",
+ "`this` should be set correctly");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-events.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-events.js
new file mode 100644
index 0000000..8bd846b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-events.js
@@ -0,0 +1,182 @@
+'use strict';
+
+// Exposing private methods as public in order to test
+const EventEmitter = require('events').EventEmitter.compose({
+ listeners: function(type) this._listeners(type),
+ emit: function() this._emit.apply(this, arguments),
+ emitOnObject: function() this._emitOnObject.apply(this, arguments),
+ removeAllListeners: function(type) this._removeAllListeners(type)
+});
+
+exports['test:add listeners'] = function(test) {
+ let e = new EventEmitter();
+
+ let events_new_listener_emited = [];
+ let times_hello_emited = 0;
+
+ e.on("newListener", function (event, listener) {
+ events_new_listener_emited.push(event)
+ })
+
+ e.on("hello", function (a, b) {
+ times_hello_emited += 1
+ test.assertEqual("a", a)
+ test.assertEqual("b", b)
+ test.assertEqual(this, e, '`this` pseudo-variable is bound to instance');
+ })
+
+ e.emit("hello", "a", "b")
+};
+
+exports['test:remove listeners'] = function(test) {
+ let count = 0;
+
+ function listener1 () {
+ count++;
+ }
+ function listener2 () {
+ count++;
+ }
+ function listener3 () {
+ count++;
+ }
+
+ let e1 = new EventEmitter();
+ e1.on("hello", listener1);
+ test.assertEqual(1, e1.listeners('hello').length);
+ e1.removeListener("hello", listener1);
+ test.assertEqual(0, e1.listeners('hello').length);
+
+ let e2 = new EventEmitter();
+ e2.on("hello", listener1);
+ test.assertEqual(1, e2.listeners('hello').length);
+ e2.removeListener("hello", listener2);
+ test.assertEqual(1, e2.listeners('hello').length);
+ test.assertEqual(listener1, e2.listeners('hello')[0]);
+
+ let e3 = new EventEmitter();
+ e3.on("hello", listener1);
+ test.assertEqual(1, e3.listeners('hello').length);
+ e3.on("hello", listener2);
+ test.assertEqual(2, e3.listeners('hello').length);
+ e3.removeListener("hello", listener1);
+ test.assertEqual(1, e3.listeners('hello').length);
+ test.assertEqual(listener2, e3.listeners('hello')[0]);
+};
+
+exports['test: modify in emit'] = function(test) {
+ let callbacks_called = [ ];
+ let e = new EventEmitter();
+
+ function callback1() {
+ callbacks_called.push("callback1");
+ e.on("foo", callback2);
+ e.on("foo", callback3);
+ e.removeListener("foo", callback1);
+ }
+ function callback2() {
+ callbacks_called.push("callback2");
+ e.removeListener("foo", callback2);
+ }
+ function callback3() {
+ callbacks_called.push("callback3");
+ e.removeListener("foo", callback3);
+ }
+
+ e.on("foo", callback1);
+ test.assertEqual(1, e.listeners("foo").length);
+
+ e.emit("foo");
+ test.assertEqual(2, e.listeners("foo").length);
+ test.assertEqual(1, callbacks_called.length);
+ test.assertEqual('callback1', callbacks_called[0]);
+
+ e.emit("foo");
+ test.assertEqual(0, e.listeners("foo").length);
+ test.assertEqual(3, callbacks_called.length);
+ test.assertEqual('callback1', callbacks_called[0]);
+ test.assertEqual('callback2', callbacks_called[1]);
+ test.assertEqual('callback3', callbacks_called[2]);
+
+ e.emit("foo");
+ test.assertEqual(0, e.listeners("foo").length);
+ test.assertEqual(3, callbacks_called.length);
+ test.assertEqual('callback1', callbacks_called[0]);
+ test.assertEqual('callback2', callbacks_called[1]);
+ test.assertEqual('callback3', callbacks_called[2]);
+
+ e.on("foo", callback1);
+ e.on("foo", callback2);
+ test.assertEqual(2, e.listeners("foo").length);
+ e.removeAllListeners("foo");
+ test.assertEqual(0, e.listeners("foo").length);
+
+ // Verify that removing callbacks while in emit allows emits to propagate to
+ // all listeners
+ callbacks_called = [ ];
+
+ e.on("foo", callback2);
+ e.on("foo", callback3);
+ test.assertEqual(2, e.listeners("foo").length);
+ e.emit("foo");
+ test.assertEqual(2, callbacks_called.length);
+ test.assertEqual('callback2', callbacks_called[0]);
+ test.assertEqual('callback3', callbacks_called[1]);
+ test.assertEqual(0, e.listeners("foo").length);
+};
+
+exports['test:adding same listener'] = function(test) {
+ function foo() {}
+ let e = new EventEmitter();
+ e.on("foo", foo);
+ e.on("foo", foo);
+ test.assertEqual(
+ 1,
+ e.listeners("foo").length,
+ "listener reregistration is ignored"
+ );
+}
+
+exports['test:errors are reported if listener throws'] = function(test) {
+ let e = new EventEmitter(),
+ reported = false;
+ e.on('error', function(e) reported = true)
+ e.on('boom', function() { throw new Error('Boom!') });
+ e.emit('boom', 3);
+ test.assert(reported, 'error should be reported through event');
+};
+
+exports['test:emitOnObject'] = function(test) {
+ let e = new EventEmitter();
+
+ e.on("foo", function() {
+ test.assertEqual(this, e, "`this` should be emitter");
+ });
+ e.emitOnObject(e, "foo");
+
+ e.on("bar", function() {
+ test.assertEqual(this, obj, "`this` should be other object");
+ });
+ let obj = {};
+ e.emitOnObject(obj, "bar");
+};
+
+exports['test:once'] = function(test) {
+ let e = new EventEmitter();
+ let called = false;
+
+ e.once('foo', function(value) {
+ test.assert(!called, "listener called only once");
+ test.assertEqual(value, "bar", "correct argument was passed");
+ });
+
+ e.emit('foo', 'bar');
+ e.emit('foo', 'baz');
+};
+
+exports["test romeving once"] = function(test) {
+ let e = require("events").EventEmitterTrait.create();
+ e.once("foo", function() { test.pass("listener was called"); });
+ e.once("error", function() { test.fail("error event was emitted"); });
+ e._emit("foo", "bug-656684");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-file.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-file.js
new file mode 100644
index 0000000..5b1314a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-file.js
@@ -0,0 +1,276 @@
+var file = require("file");
+var url = require("url");
+var byteStreams = require("byte-streams");
+var textStreams = require("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: .+$/,
+};
+
+var myurl = module.uri;
+var mydir = myurl.slice(0, -("test-file.js".length));
+var otherdir = mydir + "modules/";
+
+exports.testDirName = function(test) {
+ var aDir = url.toFilename(otherdir);
+ test.assertEqual(file.dirname(aDir),
+ aDir.slice(0, aDir.lastIndexOf("modules")-1),
+ "file.dirname() of dir should return parent dir");
+
+ aDir = url.toFilename(myurl);
+ test.assertEqual(file.dirname(aDir),
+ aDir.slice(0, aDir.lastIndexOf("test-file")-1),
+ "file.dirname() of file should return its dir");
+
+ while (aDir)
+ aDir = file.dirname(aDir);
+ test.assertEqual(aDir, "",
+ "dirname should return empty string when dir has no parent");
+};
+
+exports.testBasename = function(test) {
+ // 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.
+ var topPath = url.toFilename(myurl);
+ var parentPath = file.dirname(topPath);
+ while (parentPath) {
+ topPath = parentPath;
+ parentPath = file.dirname(topPath);
+ }
+
+ var path = topPath;
+ test.assertEqual(file.basename(path), "",
+ "basename should work on paths with no components");
+
+ path = file.join(path, "foo");
+ test.assertEqual(file.basename(path), "foo",
+ "basename should work on paths with a single component");
+
+ path = file.join(path, "bar");
+ test.assertEqual(file.basename(path), "bar",
+ "basename should work on paths with multiple components");
+};
+
+exports.testList = function(test) {
+ var list = file.list(url.toFilename(otherdir));
+ var found = [true for each (name in list)
+ if (name == "add.js")];
+ if (found.length > 1)
+ test.fail("a dir can't contain two files of the same name!");
+ test.assertEqual(found[0], true, "file.list() should work");
+
+ test.assertRaises(
+ function() { file.list(url.toFilename(module.uri)); },
+ ERRORS.NOT_A_DIRECTORY,
+ "file.list() on non-dir should raise error"
+ );
+
+ test.assertRaises(
+ function() { file.list(url.toFilename(mydir + "foo/")); },
+ ERRORS.FILE_NOT_FOUND,
+ "file.list() on nonexistent dir should raise error"
+ );
+};
+
+exports.testRead = function(test) {
+ var filename = url.toFilename(module.uri);
+ var contents = file.read(filename);
+ test.assertMatches(contents, /file\.read\(\) should work/,
+ "file.read() should work");
+
+ test.assertRaises(
+ function() { file.read(filename + "blah"); },
+ ERRORS.FILE_NOT_FOUND,
+ "file.read() on nonexistent file should raise error"
+ );
+
+ test.assertRaises(
+ function() { file.read(url.toFilename(otherdir)); },
+ ERRORS.NOT_A_FILE,
+ "file.read() on dir should raise error"
+ );
+};
+
+exports.testJoin = function(test) {
+ var filename = url.toFilename(myurl);
+ var baseDir = file.dirname(filename);
+
+ test.assertEqual(file.join(baseDir, "test-file.js"),
+ filename,
+ "file.join() should work");
+};
+
+exports.testOpenNonexistentForRead = function (test) {
+ var filename = dataFileFilename(test);
+ test.assertRaises(function () file.open(filename),
+ ERRORS.FILE_NOT_FOUND,
+ "file.open() on nonexistent file should raise error");
+ test.assertRaises(function () file.open(filename, "r"),
+ ERRORS.FILE_NOT_FOUND,
+ "file.open('r') on nonexistent file should raise error");
+ test.assertRaises(function () file.open(filename, "zzz"),
+ ERRORS.FILE_NOT_FOUND,
+ "file.open('zzz') on nonexistent file should raise error");
+};
+
+exports.testOpenNonexistentForWrite = function (test) {
+ var filename = dataFileFilename(test);
+
+ var stream = file.open(filename, "w");
+ stream.close();
+
+ test.assert(file.exists(filename),
+ "file.exists() should return true after file.open('w')");
+ file.remove(filename);
+ test.assert(!file.exists(filename),
+ "file.exists() should return false after file.remove()");
+
+ stream = file.open(filename, "rw");
+ stream.close();
+
+ test.assert(file.exists(filename),
+ "file.exists() should return true after file.open('rw')");
+ file.remove(filename);
+ test.assert(!file.exists(filename),
+ "file.exists() should return false after file.remove()");
+};
+
+exports.testOpenDirectory = function (test) {
+ var dir = file.dirname(url.toFilename(module.uri));
+ test.assertRaises(function () file.open(dir),
+ ERRORS.NOT_A_FILE,
+ "file.open() on directory should raise error");
+ test.assertRaises(function () file.open(dir, "w"),
+ ERRORS.NOT_A_FILE,
+ "file.open('w') on directory should raise error");
+};
+
+exports.testOpenTypes = function (test) {
+ var filename = dataFileFilename(test);
+
+ // Do the opens first to create the data file.
+ var stream = file.open(filename, "w");
+ test.assert(stream instanceof textStreams.TextWriter,
+ "open(w) should return a TextWriter");
+ stream.close();
+
+ stream = file.open(filename, "wb");
+ test.assert(stream instanceof byteStreams.ByteWriter,
+ "open(wb) should return a ByteWriter");
+ stream.close();
+
+ stream = file.open(filename);
+ test.assert(stream instanceof textStreams.TextReader,
+ "open() should return a TextReader");
+ stream.close();
+
+ stream = file.open(filename, "r");
+ test.assert(stream instanceof textStreams.TextReader,
+ "open(r) should return a TextReader");
+ stream.close();
+
+ stream = file.open(filename, "b");
+ test.assert(stream instanceof byteStreams.ByteReader,
+ "open(b) should return a ByteReader");
+ stream.close();
+
+ stream = file.open(filename, "rb");
+ test.assert(stream instanceof byteStreams.ByteReader,
+ "open(rb) should return a ByteReader");
+ stream.close();
+
+ file.remove(filename);
+};
+
+exports.testMkpathRmdir = function (test) {
+ var basePath = file.dirname(url.toFilename(module.uri));
+ var dirs = [];
+ for (var i = 0; i < 3; i++)
+ dirs.push("test-file-dir");
+ var paths = [];
+ for (var i = 0; i < dirs.length; i++) {
+ var args = [basePath].concat(dirs.slice(0, i + 1));
+ paths.unshift(file.join.apply(null, args));
+ }
+ for (i = 0; i < paths.length; i++) {
+ test.assert(!file.exists(paths[i]),
+ "Sanity check: path should not exist: " + paths[i]);
+ }
+ file.mkpath(paths[0]);
+ test.assert(file.exists(paths[0]), "mkpath should create path: " + paths[0]);
+ for (i = 0; i < paths.length; i++) {
+ file.rmdir(paths[i]);
+ test.assert(!file.exists(paths[i]),
+ "rmdir should remove path: " + paths[i]);
+ }
+};
+
+exports.testMkpathTwice = function (test) {
+ var dir = file.dirname(url.toFilename(module.uri));
+ var path = file.join(dir, "test-file-dir");
+ test.assert(!file.exists(path),
+ "Sanity check: path should not exist: " + path);
+ file.mkpath(path);
+ test.assert(file.exists(path), "mkpath should create path: " + path);
+ file.mkpath(path);
+ test.assert(file.exists(path),
+ "After second mkpath, path should still exist: " + path);
+ file.rmdir(path);
+ test.assert(!file.exists(path), "rmdir should remove path: " + path);
+};
+
+exports.testMkpathExistingNondirectory = function (test) {
+ var fname = dataFileFilename(test);
+ file.open(fname, "w").close();
+ test.assert(file.exists(fname), "File should exist");
+ test.assertRaises(function () file.mkpath(fname),
+ /^The path already exists and is not a directory: .+$/,
+ "mkpath on file should raise error");
+ file.remove(fname);
+};
+
+exports.testRmdirNondirectory = function (test) {
+ var fname = dataFileFilename(test);
+ file.open(fname, "w").close();
+ test.assert(file.exists(fname), "File should exist");
+ test.assertRaises(function () file.rmdir(fname),
+ ERRORS.NOT_A_DIRECTORY,
+ "rmdir on file should raise error");
+ file.remove(fname);
+ test.assert(!file.exists(fname), "File should not exist");
+ test.assertRaises(function () file.rmdir(fname),
+ ERRORS.FILE_NOT_FOUND,
+ "rmdir on non-existing file should raise error");
+};
+
+exports.testRmdirNonempty = function (test) {
+ var dir = file.dirname(url.toFilename(module.uri));
+ var path = file.join(dir, "test-file-dir");
+ test.assert(!file.exists(path),
+ "Sanity check: path should not exist: " + path);
+ file.mkpath(path);
+ var filePath = file.join(path, "file");
+ file.open(filePath, "w").close();
+ test.assert(file.exists(filePath),
+ "Sanity check: path should exist: " + filePath);
+ test.assertRaises(function () file.rmdir(path),
+ /^The directory is not empty: .+$/,
+ "rmdir on non-empty directory should raise error");
+ file.remove(filePath);
+ file.rmdir(path);
+ test.assert(!file.exists(path), "Path should not exist");
+};
+
+// Returns the name of a file that should be used to test writing and reading.
+function dataFileFilename(test) {
+ var dir = file.dirname(url.toFilename(module.uri));
+ var fname = file.join(dir, "test-file-data");
+ test.assert(!file.exists(fname),
+ "Sanity check: the file that this test assumes does not " +
+ "exist should really not exist!");
+ return fname;
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-function-utils.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-function-utils.js
new file mode 100644
index 0000000..292380a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-function-utils.js
@@ -0,0 +1,23 @@
+const { invoke, Enqueued } = require('utils/function');
+
+exports['test forwardApply'] = function(test) {
+ function sum(b, c) this.a + b + c
+ test.assertEqual(invoke(sum, [2, 3], { a: 1 }), 6,
+ 'passed arguments and pseoude-variable are used');
+ test.assertEqual(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7,
+ 'bounded `this` pseoudo variable is used')
+}
+
+exports['test enqueued function'] = function(test) {
+ test.waitUntilDone();
+ let nextTurn = false;
+ function sum(b, c) {
+ test.assert(nextTurn, 'enqueued is called in next turn of event loop');
+ test.assertEqual(this.a + b + c, 6,
+ 'passed arguments an pseoude-variable are used');
+ test.done();
+ }
+ let fixture = { a: 1, method: Enqueued(sum) }
+ fixture.method(2, 3);
+ nextTurn = true;
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-globals.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-globals.js
new file mode 100644
index 0000000..97cb774
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-globals.js
@@ -0,0 +1,21 @@
+Object.defineProperty(this, "global", { value: this });
+
+exports.testGlobals = function(test) {
+ // the only globals in module scope should be:
+ // module, exports, require, dump, console
+ test.assertObject(module, "have 'module', good");
+ test.assertObject(exports, "have 'exports', good");
+ test.assertFunction(require, "have 'require', good");
+ test.assertFunction(dump, "have 'dump', good");
+ test.assertObject(console, "have 'console', good");
+
+ // in particular, these old globals should no longer be present
+ test.assert(!('packaging' in global), "no 'packaging', good");
+ // this will be fixed by bug 620559
+ test.expectFail(function() {
+ test.assert(!('memory' in global), "no 'memory', good");
+ });
+
+ test.assertMatches(module.uri, /test-globals\.js$/,
+ 'should contain filename');
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-hidden-frame.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-hidden-frame.js
new file mode 100644
index 0000000..c92046b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-hidden-frame.js
@@ -0,0 +1,47 @@
+let tests = {}, hiddenFrames, HiddenFrame;
+
+tests.testFrame = function(test) {
+ let url = "data:text/html,<!DOCTYPE%20html>";
+ test.waitUntilDone();
+ let hiddenFrame = hiddenFrames.add(HiddenFrame({
+ onReady: function () {
+ test.assertEqual(this.element.contentWindow.location, "about:blank",
+ "HiddenFrame loads about:blank by default.");
+
+ function onDOMReady() {
+ hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady,
+ false);
+ test.assertEqual(hiddenFrame.element.contentWindow.location, url,
+ "HiddenFrame loads the specified content.");
+ test.done();
+ }
+ this.element.addEventListener("DOMContentLoaded", onDOMReady, false);
+ this.element.setAttribute("src", url);
+ }
+ }));
+};
+
+let hiddenFrameSupported = true;
+
+try {
+ hiddenFrames = require("hidden-frame");
+ HiddenFrame = hiddenFrames.HiddenFrame;
+}
+catch(ex if ex.message == [
+ "The hidden-frame module currently supports only Firefox and Thunderbird. ",
+ "In the future, we would like it to support other applications, however. ",
+ "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
+ "information."
+ ].join("")) {
+ hiddenFrameSupported = false;
+}
+
+if (hiddenFrameSupported) {
+ for (let test in tests)
+ exports[test] = tests[test];
+}
+else {
+ exports.testHiddenFrameNotSupported = function(test) {
+ test.pass("The hidden-frame module is not supported on this app.");
+ }
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-httpd.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-httpd.js
new file mode 100644
index 0000000..42734c1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-httpd.js
@@ -0,0 +1,28 @@
+exports.testBasicHTTPServer = function(test) {
+ var port = 8080;
+ var data = require("self").data;
+ var testFilePath = require("url").toFilename(data.url("test-httpd.txt"));
+ var basePath = require("file").dirname(testFilePath);
+ var {startServerAsync} = require("httpd");
+
+ var srv = startServerAsync(port, basePath);
+
+ test.waitUntilDone();
+
+ // Request this very file.
+ var Request = require('request').Request;
+ Request({
+ url: "http://localhost:" + port + "/test-httpd.txt",
+ onComplete: function (response) {
+ test.assertEqual(response.text, "This is the HTTPD test file.\n");
+ done();
+ }
+ }).get();
+
+
+ function done() {
+ srv.stop(function() {
+ test.done();
+ });
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-keyboard-observer.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-keyboard-observer.js
new file mode 100644
index 0000000..27d4276
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-keyboard-observer.js
@@ -0,0 +1,32 @@
+"use strict";
+
+const { keyPress } = require("api-utils/dom/events/keys");
+const { Loader } = require("./helpers");
+
+exports["test unload keyboard observer"] = function(assert, done) {
+ let loader = Loader(module);
+ let element = loader.require("api-utils/window-utils").
+ activeBrowserWindow.document.documentElement;
+ let observer = loader.require("api-utils/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.
+ require("timer").setTimeout(function () {
+ assert.equal(called, 1, "observer was called before unload only.");
+ done();
+ }, 0);
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-keyboard-utils.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-keyboard-utils.js
new file mode 100644
index 0000000..137e6b1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-keyboard-utils.js
@@ -0,0 +1,58 @@
+"use strict";
+
+const utils = require("keyboard/utils");
+const runtime = require("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("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-light-traits.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-light-traits.js
new file mode 100644
index 0000000..4ad97b5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-light-traits.js
@@ -0,0 +1,7 @@
+"use strict";
+
+exports["test traits from objects"] = require("./traits/object-tests");
+exports["test traits from descriptors"] = require("./traits/descriptor-tests");
+exports["test inheritance"] = require("./traits/inheritance-tests");
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-list.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-list.js
new file mode 100644
index 0000000..29abe1a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-list.js
@@ -0,0 +1,203 @@
+"use strict";
+
+function assertList(test, array, list) {
+ for (let i = 0, ii = array.length; i < ii; i < ii, i++) {
+ test.assertEqual(
+ array.length,
+ list.length,
+ 'list must contain same amount of elements as array'
+ );
+ test.assertEqual(
+ 'List(' + array + ')',
+ list + '',
+ 'toString must output array like result'
+ );
+ test.assert(
+ i in list,
+ 'must contain element with index: ' + i
+ );
+ test.assertEqual(
+ array[i],
+ list[i],
+ 'element with index: ' + i + ' should match'
+ );
+ }
+}
+
+const { List } = require('api-utils/list');
+
+exports['test:test for'] = function(test) {
+ let fixture = List(3, 2, 1);
+
+ test.assertEqual(3, fixture.length, 'length is 3');
+ let i = 0;
+ for (let key in fixture) {
+ test.assertEqual(i++, key, 'key should match');
+ }
+};
+
+exports['test:test for each'] = function(test) {
+ let fixture = new List(3, 2, 1);
+
+ test.assertEqual(3, fixture.length, 'length is 3');
+ let i = 3;
+ for each (let value in fixture) {
+ test.assertEqual(i--, value, 'value should match');
+ }
+};
+
+exports['test: for each using Iterator'] = function(test) {
+ let fixture = new List(3, 2, 1);
+
+ test.assertEqual(3, fixture.length, 'length is 3');
+ let v = 3, k = 0;
+ for each (let [key, value] in Iterator(fixture)) {
+ test.assertEqual(k++, key, 'key should match');
+ test.assertEqual(v--, value, 'value should match');
+ }
+};
+
+exports['test:test toString'] = function(test) {
+ let fixture = List(3, 2, 1);
+
+ test.assertEqual(
+ 'List(3,2,1)',
+ fixture + '',
+ 'toString must output array like result'
+ )
+};
+
+exports['test:test constructor with apply'] = function(test) {
+ let array = ['a', 'b', 'c'];
+ let fixture = List.apply(null, array);
+
+ test.assertEqual(
+ 3,
+ fixture.length,
+ 'should have applied arguments'
+ );
+};
+
+exports['test:direct element access'] = function(test) {
+ let array = [1, 'foo', 2, 'bar', {}, 'bar', function a() {}, test, 1];
+ let fixture = List.apply(null, array);
+ array.splice(5, 1);
+ array.splice(7, 1);
+
+ test.assertEqual(
+ array.length,
+ fixture.length,
+ 'list should omit duplicate elements'
+ );
+
+ test.assertEqual(
+ 'List(' + array + ')',
+ fixture.toString(),
+ 'elements should not be rearranged'
+ );
+
+ for (let key in array) {
+ test.assert(key in fixture,'should contain key for index:' + key);
+ test.assertEqual(
+ array[key],
+ fixture[key],
+ 'values should match for: ' + key
+ );
+ }
+};
+
+exports['test:removing adding elements'] = function(test) {
+ let array = [1, 'foo', 2, 'bar', {}, 'bar', function a() {}, test, 1];
+ let fixture = List.compose({
+ add: function() this._add.apply(this, arguments),
+ remove: function() this._remove.apply(this, arguments),
+ clear: function() this._clear()
+ }).apply(null, array);
+ array.splice(5, 1);
+ array.splice(7, 1);
+
+ assertList(test, array, fixture);
+
+ array.splice(array.indexOf(2), 1);
+ fixture.remove(2);
+ assertList(test, array, fixture);
+
+ array.splice(array.indexOf('foo'), 1);
+ fixture.remove('foo');
+ array.splice(array.indexOf(1), 1);
+ fixture.remove(1);
+ array.push('foo');
+ fixture.add('foo');
+ assertList(test, array, fixture);
+
+ array.splice(0);
+ fixture.clear(0);
+ assertList(test, array, fixture);
+
+ array.push(1, 'foo', 2, 'bar', 3);
+ fixture.add(1);
+ fixture.add('foo');
+ fixture.add(2);
+ fixture.add('bar');
+ fixture.add(3);
+
+ assertList(test, array, fixture);
+};
+
+exports['test: remove does not leave invalid numerical properties'] = function(test) {
+ let fixture = List.compose({
+ remove: function() this._remove.apply(this, arguments),
+ }).apply(null, [1, 2, 3]);
+
+ fixture.remove(1);
+ test.assertEqual(fixture[fixture.length], undefined);
+}
+
+exports['test:add list item from Iterator'] = function(test) {
+ let array = [1, 2, 3, 4], sum = 0, added = false;
+
+ let fixture = List.compose({
+ add: function() this._add.apply(this, arguments),
+ }).apply(null, array);
+
+ for each (let item in fixture) {
+ sum += item;
+
+ if (!added) {
+ fixture.add(5);
+ added = true;
+ }
+ }
+
+ test.assertEqual(sum, 1 + 2 + 3 + 4);
+};
+
+exports['test:remove list item from Iterator'] = function(test) {
+ let array = [1, 2, 3, 4], sum = 0;
+
+ let fixture = List.compose({
+ remove: function() this._remove.apply(this, arguments),
+ }).apply(null, array);
+
+ for each (let item in fixture) {
+ sum += item;
+ fixture.remove(item);
+ }
+
+ test.assertEqual(sum, 1 + 2 + 3 + 4);
+};
+
+exports['test:clear list from Iterator'] = function(test) {
+ let array = [1, 2, 3, 4], sum = 0;
+
+ let fixture = List.compose({
+ clear: function() this._clear()
+ }).apply(null, array);
+
+ for each (let item in fixture) {
+ sum += item;
+ fixture.clear();
+ }
+
+ test.assertEqual(sum, 1 + 2 + 3 + 4);
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-loader.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-loader.js
new file mode 100644
index 0000000..796d1cb
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-loader.js
@@ -0,0 +1,31 @@
+const { Loader } = require("./helpers");
+
+exports.testLoader = function(test) {
+ var prints = [];
+ function print(message) {
+ prints.push(message);
+ }
+
+ var loader = Loader(module, { dump: print, foo: 1 });
+
+ var fixture = loader.require('./loader/fixture');
+
+ test.assertEqual(fixture.foo, 1, "custom globals must work.");
+
+ test.assertEqual(prints[0], "info: testing 1 2,3,4\n",
+ "global console must work.");
+
+ var unloadsCalled = '';
+
+ loader.require("unload").when(function() {
+ unloadsCalled += 'a';
+ });
+ loader.require("unload.js").when(function() {
+ unloadsCalled += 'b';
+ });
+
+ loader.unload();
+
+ test.assertEqual(unloadsCalled, 'ba',
+ "loader.unload() must call cb's in LIFO order.");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-match-pattern.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-match-pattern.js
new file mode 100644
index 0000000..0352ba9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-match-pattern.js
@@ -0,0 +1,161 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const { MatchPattern } = require("match-pattern");
+
+exports.testMatchPatternTestTrue = function(test) {
+ function ok(pattern, url) {
+ let mp = new MatchPattern(pattern);
+ test.assert(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(/https:.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
+};
+
+exports.testMatchPatternTestFalse = function(test) {
+ function ok(pattern, url) {
+ let mp = new MatchPattern(pattern);
+ test.assert(!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(/https:.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
+};
+
+exports.testMatchPatternErrors = function(test) {
+ test.assertRaises(
+ function() new MatchPattern("*.google.com/*"),
+ /There can be at most one/,
+ "MatchPattern throws when supplied multiple '*'"
+ );
+
+ test.assertRaises(
+ function() 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"
+ );
+
+ test.assertRaises(
+ function() new MatchPattern("http://google*.com"),
+ /expected to be the first or the last/,
+ "MatchPattern throws when a '*' is in the middle of the wildcard"
+ );
+
+ test.assertRaises(
+ function() 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)."
+ );
+
+ test.assertRaises(
+ function() new MatchPattern(/ /i),
+ /^A RegExp match pattern cannot be set to `ignoreCase` \(i\.e\. \/\/i\)\.$/,
+ "MatchPattern throws on a RegExp set to `ignoreCase` (i.e. //i)."
+ );
+
+ test.assertRaises(
+ function() 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(test) {
+ test.assertEqual(
+ new MatchPattern("http://google.com/test").exactURL,
+ "http://google.com/test"
+ );
+
+ test.assertEqual(
+ new MatchPattern("http://google.com/test/*").urlPrefix,
+ "http://google.com/test/"
+ );
+
+ test.assertEqual(
+ new MatchPattern("*.example.com").domain,
+ "example.com"
+ );
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-memory.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-memory.js
new file mode 100644
index 0000000..7e172bf
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-memory.js
@@ -0,0 +1,15 @@
+var memory = require("memory");
+
+exports.testMemory = function(test) {
+ test.pass("Skipping this test until Gecko memory debugging issues " +
+ "are resolved (see bug 592774).");
+ return;
+
+ var obj = {};
+ memory.track(obj, "testMemory.testObj");
+ var objs = memory.getObjects("testMemory.testObj");
+ test.assertEqual(objs[0].weakref.get(), obj);
+ obj = null;
+ memory.gc();
+ test.assertEqual(objs[0].weakref.get(), null);
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-modules.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-modules.js
new file mode 100644
index 0000000..99297bc
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-modules.js
@@ -0,0 +1,144 @@
+exports.testDefine = function(test) {
+ let tiger = require('./modules/tiger');
+ test.assertEqual(tiger.name, 'tiger', 'name proprety was exported properly');
+ test.assertEqual(tiger.type, 'cat', 'property form other module exported');
+};
+
+exports.testDefineInoresNonFactory = function(test) {
+ let mod = require('./modules/async2');
+ test.assertEqual(mod.name, 'async2', 'name proprety was exported properly');
+ test.assertNotEqual(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(test) {
+ var add = require('modules/add');
+ test.assertEqual(add(1, 1), 2, 'Named define() exporting a function');
+};
+
+// define() that exports function as a value, but is anonymous
+exports.testAnonDefExport = function (test) {
+ var subtract = require('modules/subtract');
+ test.assertEqual(subtract(4, 2), 2,
+ 'Anonymous define() exporting a function');
+}
+
+// using require([], function () {}) to load modules.
+exports.testSimpleRequire = function (test) {
+ require(['modules/blue', 'modules/orange'], function (blue, orange) {
+ test.assertEqual(blue.name, 'blue', 'Simple require for blue');
+ test.assertEqual(orange.name, 'orange', 'Simple require for orange');
+ test.assertEqual(orange.parentType, 'color',
+ 'Simple require dependency check for orange');
+ });
+}
+
+// using nested require([]) calls.
+exports.testSimpleRequireNested = function (test) {
+ require(['modules/blue', 'modules/orange', 'modules/green'],
+ function (blue, orange, green) {
+
+ require(['modules/orange', 'modules/red'], function (orange, red) {
+ test.assertEqual(red.name, 'red', 'Simple require for red');
+ test.assertEqual(red.parentType, 'color',
+ 'Simple require dependency check for red');
+ test.assertEqual(blue.name, 'blue', 'Simple require for blue');
+ test.assertEqual(orange.name, 'orange', 'Simple require for orange');
+ test.assertEqual(orange.parentType, 'color',
+ 'Simple require dependency check for orange');
+ test.assertEqual(green.name, 'green', 'Simple require for green');
+ test.assertEqual(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 (test) {
+ var t = require('modules/traditional1');
+ test.assertEqual(t.name, 'traditional1', 'Testing name');
+ test.assertEqual(t.traditional2Name, 'traditional2',
+ 'Testing dependent name');
+ test.assertEqual(t.traditional1Name, 'traditional1', 'Testing circular name');
+ test.assertEqual(t.async2Name, 'async2', 'Testing async2 name');
+ test.assertEqual(t.async2Traditional2Name, 'traditional2',
+ 'Testing nested traditional2 name');
+}
+
+// Testing define()(function(require) {}) with some that use exports,
+// some that use return.
+exports.testAnonExportsReturn = function (test) {
+ var lion = require('modules/lion');
+ require(['modules/tiger', 'modules/cheetah'], function (tiger, cheetah) {
+ test.assertEqual('lion', lion, 'Check lion name');
+ test.assertEqual('tiger', tiger.name, 'Check tiger name');
+ test.assertEqual('cat', tiger.type, 'Check tiger type');
+ test.assertEqual('cheetah', cheetah(), 'Check cheetah name');
+ });
+}
+
+// circular dependency
+exports.testCircular = function (test) {
+ var pollux = require('modules/pollux'),
+ castor = require('modules/castor');
+
+ test.assertEqual(pollux.name, 'pollux', 'Pollux\'s name');
+ test.assertEqual(pollux.getCastorName(),
+ 'castor', 'Castor\'s name from Pollux.');
+ test.assertEqual(castor.name, 'castor', 'Castor\'s name');
+ test.assertEqual(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 (test) {
+ var passed = false;
+ try {
+ var bad = require('modules/badExportAndReturn');
+ } catch(e) {
+ passed = /cannot use exports and also return/.test(e.toString());
+ }
+ test.assertEqual(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 (test) {
+ var passed = false;
+ try {
+ var bad = require('modules/badFirst');
+ } catch(e) {
+ passed = /after another module has referenced its exported value/
+ .test(e.toString());
+ }
+ test.assertEqual(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 (test) {
+ var passed = false;
+ try {
+ var dupe = require('modules/dupe');
+ } catch(e) {
+ passed = /Only one call to define/.test(e.toString());
+ }
+ test.assertEqual(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 (test) {
+ var passed = false;
+ try {
+ var dupe = require('modules/dupeNested');
+ } catch(e) {
+ passed = /Only one call to define/.test(e.toString());
+ }
+ test.assertEqual(passed, true, 'Only allow one define call per module');
+}
+*/
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-namespace.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-namespace.js
new file mode 100644
index 0000000..60ba23d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-namespace.js
@@ -0,0 +1,70 @@
+"use strict";
+
+let { Namespace } = require("api-utils/namespace");
+
+exports["test namsepace basics"] = function(assert) {
+ var privates = Namespace();
+ 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 _ = new Namespace();
+ 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 _ = new Namespace({ hello: 'hello world' });
+
+ var f1 = { hello: 1 };
+ var f2 = { foo: 'foo' };
+
+ 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 = 2;
+
+ 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 = new Namespace();
+ var n2 = new Namespace();
+ 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");
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-observer-service.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-observer-service.js
new file mode 100644
index 0000000..68cda74
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-observer-service.js
@@ -0,0 +1,73 @@
+var observers = require("api-utils/observer-service");
+var {Cc,Ci} = require("chrome");
+const { Loader } = require("./helpers");
+
+exports.testUnloadAndErrorLogging = function(test) {
+ var prints = [];
+ var loader = Loader(module, { dump: function print(message) {
+ prints.push(message);
+ }});
+ var sbobsvc = loader.require("api-utils/observer-service");
+
+ var timesCalled = 0;
+ var cb = function(subject, data) {
+ timesCalled++;
+ };
+ var badCb = function(subject, data) {
+ throw new Error("foo");
+ };
+ sbobsvc.add("blarg", cb);
+ observers.notify("blarg", "yo yo");
+ test.assertEqual(timesCalled, 1);
+ sbobsvc.add("narg", badCb);
+ observers.notify("narg", "yo yo");
+ var lines = prints[0].split("\n");
+ test.assertEqual(lines[0], "error: An exception occurred.");
+ test.assertEqual(lines[1], "Traceback (most recent call last):");
+ test.assertEqual(lines.slice(-2)[0], "Error: foo");
+
+ loader.unload();
+ observers.notify("blarg", "yo yo");
+ test.assertEqual(timesCalled, 1);
+};
+
+exports.testObserverService = function(test) {
+ var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+ var service = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ var uri = ios.newURI("http://www.foo.com", null, null);
+ var timesCalled = 0;
+ var lastSubject = null;
+ var lastData = null;
+
+ var cb = function(subject, data) {
+ timesCalled++;
+ lastSubject = subject;
+ lastData = data;
+ };
+
+ observers.add("blarg", cb);
+ service.notifyObservers(uri, "blarg", "some data");
+ test.assertEqual(timesCalled, 1,
+ "observer-service.add() should call callback");
+ test.assertEqual(lastSubject, uri,
+ "observer-service.add() should pass subject");
+ test.assertEqual(lastData, "some data",
+ "observer-service.add() should pass data");
+
+ function customSubject() {}
+ function customData() {}
+ observers.notify("blarg", customSubject, customData);
+ test.assertEqual(timesCalled, 2,
+ "observer-service.notify() should work");
+ test.assertEqual(lastSubject, customSubject,
+ "observer-service.notify() should pass+wrap subject");
+ test.assertEqual(lastData, customData,
+ "observer-service.notify() should pass data");
+
+ observers.remove("blarg", cb);
+ service.notifyObservers(null, "blarg", "some data");
+ test.assertEqual(timesCalled, 2,
+ "observer-service.remove() should work");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-passwords-utils.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-passwords-utils.js
new file mode 100644
index 0000000..651296f
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-passwords-utils.js
@@ -0,0 +1,138 @@
+"use strict";
+
+const { store, search, remove } = require("passwords/utils");
+
+exports["test store requires `password` field"] = function(assert) {
+ assert.throws(function() {
+ store({ username: "foo", realm: "bar" });
+ }, "`password` is required");
+};
+
+exports["test store requires `username` field"] = function(assert) {
+ assert.throws(function() {
+ store({ password: "foo", realm: "bar" });
+ }, "`username` is required");
+};
+
+exports["test store requires `realm` field"] = function(assert) {
+ assert.throws(function() {
+ store({ username: "foo", password: "bar" });
+ }, "`password` is required");
+};
+
+exports["test can't store same login twice"] = function(assert) {
+ let options = { username: "user", password: "pass", realm: "realm" };
+ store(options);
+ assert.throws(function() {
+ store(options);
+ }, "can't store same pass twice");
+ remove(options);
+};
+
+exports["test remove throws if no login found"] = function(assert) {
+ assert.throws(function() {
+ remove({ username: "foo", password: "bar", realm: "baz" });
+ }, "can't remove unstored credentials");
+};
+
+exports["test addon associated credentials"] = function(assert) {
+ let options = { username: "foo", password: "bar", realm: "baz" };
+ store(options);
+
+ assert.ok(search().length, "credential was stored");
+ assert.ok(search(options).length, "stored credential found");
+ assert.ok(search({ username: options.username }).length, "found by username");
+ assert.ok(search({ password: options.password }).length, "found by password");
+ assert.ok(search({ realm: options.realm }).length, "found by realm");
+
+ let credential = search(options)[0];
+ assert.equal(credential.url.indexOf("addon:"), 0,
+ "`addon:` uri is used for add-on associated credentials");
+ assert.equal(credential.username, options.username, "username matches");
+ assert.equal(credential.password, options.password, "password matches");
+ assert.equal(credential.realm, options.realm, "realm matches");
+ assert.equal(credential.formSubmitURL, null,
+ "`formSubmitURL` is `null` for add-on associated credentials");
+ assert.equal(credential.usernameField, "", "usernameField is empty");
+ assert.equal(credential.passwordField, "", "passwordField is empty");
+
+ remove(search(options)[0]);
+ assert.ok(!search(options).length, "remove worked");
+};
+
+exports["test web page associated credentials"] = function(assert) {
+ let options = {
+ url: "http://www.example.com",
+ formSubmitURL: "http://login.example.com",
+ username: "user",
+ password: "pass",
+ usernameField: "user-f",
+ passwordField: "pass-f"
+ };
+ store({
+ url: "http://www.example.com/login",
+ formSubmitURL: "http://login.example.com/foo/authenticate.cgi",
+ username: options.username,
+ password: options.password,
+ usernameField: options.usernameField,
+ passwordField: options.passwordField
+ });
+
+ assert.ok(search().length, "credential was stored");
+ assert.ok(search(options).length, "stored credential found");
+ assert.ok(search({ username: options.username }).length, "found by username");
+ assert.ok(search({ password: options.password }).length, "found by password");
+ assert.ok(search({ formSubmitURL: options.formSubmitURL }).length,
+ "found by formSubmitURL");
+ assert.ok(search({ usernameField: options.usernameField }).length,
+ "found by usernameField");
+ assert.ok(search({ passwordField: options.passwordField }).length,
+ "found by passwordField");
+
+ let credential = search(options)[0];
+ assert.equal(credential.url, options.url, "url matches");
+ assert.equal(credential.username, options.username, "username matches");
+ assert.equal(credential.password, options.password, "password matches");
+ assert.equal(credential.realm, null, "realm is ");
+ assert.equal(credential.formSubmitURL, options.formSubmitURL,
+ "`formSubmitURL` matches");
+ assert.equal(credential.usernameField, options.usernameField,
+ "usernameField matches");
+ assert.equal(credential.passwordField, options.passwordField,
+ "passwordField matches");
+
+ remove(search(options)[0]);
+ assert.ok(!search(options).length, "remove worked");
+};
+
+exports["test site authentication credentials"] = function(assert) {
+ let options = {
+ url: "http://test.authentication.com",
+ username: "u",
+ password: "p",
+ realm: "r"
+ };
+
+ store(options);
+ assert.ok(search().length, "credential was stored");
+ assert.ok(search(options).length, "stored credential found");
+ assert.ok(search({ username: options.username }).length, "found by username");
+ assert.ok(search({ password: options.password }).length, "found by password");
+ assert.ok(search({ realm: options.realm }).length, "found by realm");
+ assert.ok(search({ url: options.url }).length, "found by url");
+
+ let credential = search(options)[0];
+ assert.equal(credential.url, options.url, "url matches");
+ assert.equal(credential.username, options.username, "username matches");
+ assert.equal(credential.password, options.password, "password matches");
+ assert.equal(credential.realm, options.realm, "realm matches");
+ assert.equal(credential.formSubmitURL, null,
+ "`formSubmitURL` is `null` for site authentication credentials");
+ assert.equal(credential.usernameField, "", "usernameField is empty");
+ assert.equal(credential.passwordField, "", "passwordField is empty");
+
+ remove(search(options)[0]);
+ assert.ok(!search(options).length, "remove worked");
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-plain-text-console.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-plain-text-console.js
new file mode 100644
index 0000000..a0c9dda
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-plain-text-console.js
@@ -0,0 +1,64 @@
+exports.testPlainTextConsole = function(test) {
+ var prints = [];
+ function print(message) {
+ prints.push(message);
+ }
+ function lastPrint() {
+ var last = prints.slice(-1)[0];
+ prints = [];
+ return last;
+ }
+
+ var Console = require("plain-text-console").PlainTextConsole;
+ var con = new Console(print);
+
+ test.pass("PlainTextConsole instantiates");
+
+ con.log('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "info: testing 1 2,3,4\n",
+ "PlainTextConsole.log() must work.");
+
+ con.info('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "info: testing 1 2,3,4\n",
+ "PlainTextConsole.info() must work.");
+
+ con.warn('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "warning: testing 1 2,3,4\n",
+ "PlainTextConsole.warn() must work.");
+
+ con.error('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "error: testing 1 2,3,4\n",
+ "PlainTextConsole.error() must work.");
+
+ con.debug('testing', 1, [2, 3, 4]);
+ test.assertEqual(lastPrint(), "debug: testing 1 2,3,4\n",
+ "PlainTextConsole.debug() must work.");
+
+ con.log('testing', undefined);
+ test.assertEqual(lastPrint(), "info: testing undefined\n",
+ "PlainTextConsole.log() must stringify undefined.");
+
+ con.log('testing', null);
+ test.assertEqual(lastPrint(), "info: testing null\n",
+ "PlainTextConsole.log() must stringify null.");
+
+ con.log("testing", { toString: function() "obj.toString()" });
+ test.assertEqual(lastPrint(), "info: testing obj.toString()\n",
+ "PlainTextConsole.log() must stringify custom toString.");
+
+ con.log("testing", { toString: function() { throw "fail!"; } });
+ test.assertEqual(lastPrint(), "info: testing <toString() error>\n",
+ "PlainTextConsole.log() must stringify custom bad toString.");
+
+ con.exception(new Error("blah"));
+ var tbLines = prints[0].split("\n");
+ test.assertEqual(tbLines[0], "error: An exception occurred.");
+ test.assertEqual(tbLines[1], "Traceback (most recent call last):");
+ test.assertEqual(tbLines.slice(-2)[0], "Error: blah");
+
+ prints = [];
+ con.trace();
+ tbLines = prints[0].split("\n");
+ test.assertEqual(tbLines[0], "info: Traceback (most recent call last):");
+ test.assertEqual(tbLines.slice(-2)[0].trim(), "con.trace();");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-preferences-service.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-preferences-service.js
new file mode 100644
index 0000000..dd0de38
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-preferences-service.js
@@ -0,0 +1,87 @@
+var prefs = require("preferences-service");
+var {Cc,Ci} = require("chrome");
+
+exports.testReset = function(test) {
+ prefs.reset("test_reset_pref");
+ test.assertEqual(prefs.has("test_reset_pref"), false);
+ test.assertEqual(prefs.isSet("test_reset_pref"), false);
+ prefs.set("test_reset_pref", 5);
+ test.assertEqual(prefs.has("test_reset_pref"), true);
+ test.assertEqual(prefs.isSet("test_reset_pref"), true);
+};
+
+exports.testGetAndSet = function(test) {
+ let svc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(null);
+ svc.setCharPref("test_get_string_pref", "a normal string");
+ test.assertEqual(prefs.get("test_get_string_pref"), "a normal string",
+ "preferences-service should read from " +
+ "application-wide preferences service");
+
+ prefs.set("test_set_get_pref.integer", 1);
+ test.assertEqual(prefs.get("test_set_get_pref.integer"), 1,
+ "set/get integer preference should work");
+
+ prefs.set("test_set_get_number_pref", 42);
+ test.assertRaises(
+ function() { prefs.set("test_set_get_number_pref", 3.14159); },
+ "cannot store non-integer number: 3.14159",
+ "setting a float preference should raise an error"
+ );
+ test.assertEqual(prefs.get("test_set_get_number_pref"), 42,
+ "bad-type write attempt should not overwrite");
+
+ // 0x80000000 (no), 0x7fffffff (yes), -0x80000000 (yes), -0x80000001 (no)
+ test.assertRaises(
+ function() { prefs.set("test_set_get_number_pref", Math.pow(2, 31)); },
+ ("you cannot set the test_set_get_number_pref pref to the number " +
+ "2147483648, as number pref values must be in the signed 32-bit " +
+ "integer range -(2^31) to 2^31-1. To store numbers outside that " +
+ "range, store them as strings."),
+ "setting an int pref outside the range -(2^31) to 2^31-1 shouldn't work"
+ );
+ test.assertEqual(prefs.get("test_set_get_number_pref"), 42,
+ "out-of-range write attempt should not overwrite 1");
+ prefs.set("test_set_get_number_pref", Math.pow(2, 31)-1);
+ test.assertEqual(prefs.get("test_set_get_number_pref"), 0x7fffffff,
+ "in-range write attempt should work 1");
+ prefs.set("test_set_get_number_pref", -Math.pow(2, 31));
+ test.assertEqual(prefs.get("test_set_get_number_pref"), -0x80000000,
+ "in-range write attempt should work 2");
+ test.assertRaises(
+ function() { prefs.set("test_set_get_number_pref", -0x80000001); },
+ ("you cannot set the test_set_get_number_pref pref to the number " +
+ "-2147483649, as number pref values must be in the signed 32-bit " +
+ "integer range -(2^31) to 2^31-1. To store numbers outside that " +
+ "range, store them as strings."),
+ "setting an int pref outside the range -(2^31) to 2^31-1 shouldn't work"
+ );
+ test.assertEqual(prefs.get("test_set_get_number_pref"), -0x80000000,
+ "out-of-range write attempt should not overwrite 2");
+
+
+ prefs.set("test_set_get_pref.string", "foo");
+ test.assertEqual(prefs.get("test_set_get_pref.string"), "foo",
+ "set/get string preference should work");
+
+ prefs.set("test_set_get_pref.boolean", true);
+ test.assertEqual(prefs.get("test_set_get_pref.boolean"), true,
+ "set/get boolean preference should work");
+
+ prefs.set("test_set_get_unicode_pref", String.fromCharCode(960));
+ test.assertEqual(prefs.get("test_set_get_unicode_pref"),
+ String.fromCharCode(960),
+ "set/get unicode preference should work");
+
+ var unsupportedValues = [null, [], undefined];
+ unsupportedValues.forEach(
+ function(value) {
+ test.assertRaises(
+ function() { prefs.set("test_set_pref", value); },
+ ("can't set pref test_set_pref to value '" + value + "'; " +
+ "it isn't a string, integer, or boolean"),
+ "Setting a pref to " + uneval(value) + " should raise error"
+ );
+ });
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-registry.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-registry.js
new file mode 100644
index 0000000..8a251f7
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-registry.js
@@ -0,0 +1,76 @@
+'use strict';
+
+exports['test:add'] = function(test) {
+ function Class() {}
+ let fixture = require('utils/registry').Registry(Class);
+ let isAddEmitted = false;
+ fixture.on('add', function(item) {
+ test.assert(
+ item instanceof Class,
+ 'if object added is not an instance should construct instance from it'
+ );
+ test.assert(
+ fixture.has(item),
+ 'callback is called after instance is added'
+ );
+ test.assert(
+ !isAddEmitted,
+ 'callback should be called for the same item only once'
+ );
+ isAddEmitted = true;
+ });
+
+ let object = fixture.add({});
+ fixture.add(object);
+};
+
+exports['test:remove'] = function(test) {
+ function Class() {}
+ let fixture = require('utils/registry').Registry(Class);
+ fixture.on('remove', function(item) {
+ test.assert(
+ item instanceof Class,
+ 'if object removed can be only instance of Class'
+ );
+ test.assert(
+ fixture.has(item),
+ 'callback is called before instance is removed'
+ );
+ test.assert(
+ !isRemoveEmitted,
+ 'callback should be called for the same item only once'
+ );
+ isRemoveEmitted = true;
+ });
+
+ fixture.remove({});
+ let object = fixture.add({});
+ fixture.remove(object);
+ fixture.remove(object);
+};
+
+exports['test:items'] = function(test) {
+ function Class() {}
+ let fixture = require('utils/registry').Registry(Class),
+ actual,
+ times = 0;
+
+ function testItem(item) {
+ times ++;
+ test.assertEqual(
+ actual,
+ item,
+ 'item should match actual item being added/removed'
+ );
+ }
+
+ actual = fixture.add({});
+
+ fixture.on('add', testItem);
+ fixture.on('remove', testItem);
+
+ fixture.remove(actual);
+ fixture.remove(fixture.add(actual = new Class()));
+ test.assertEqual(3, times, 'should notify listeners on each call');
+}
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-require.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-require.js
new file mode 100644
index 0000000..986a53c
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-require.js
@@ -0,0 +1,25 @@
+const traceback = require("traceback");
+
+exports.test_no_args = function(test) {
+ var passed = false;
+ try {
+ var oops = require(); // leave this on line 6!
+ } catch(e) {
+ let msg = e.toString();
+ test.assertEqual(msg.indexOf("Error: you must provide a module name when calling require() from "), 0);
+ test.assertNotEqual(msg.indexOf("test-require"), -1, msg);
+ // we'd also like to assert that the right filename and linenumber is in
+ // the stack trace, but this currently doesn't work (see bugs 679591 and
+ // 551604)
+ if (0) {
+ let tb = traceback.fromException(e);
+ let lastFrame = tb[tb.length-1];
+ test.assertNotEqual(lastFrame.filename.indexOf("test-require.js"), -1,
+ lastFrame.filename);
+ test.assertEqual(lastFrame.lineNo, 6);
+ test.assertEqual(lastFrame.funcName, "??");
+ }
+ passed = true;
+ }
+ test.assert(passed, 'require() with no args should raise helpful error');
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-self.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-self.js
new file mode 100644
index 0000000..3cc6df5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-self.js
@@ -0,0 +1,29 @@
+exports.testSelf = function(test) {
+ var self = require("self");
+ // We can't assert anything about the ID inside the unit test right now,
+ // because the ID we get depends upon how the test was invoked. The idea
+ // is that it is supposed to come from the main top-level package's
+ // package.json file, from the "id" key.
+ test.assertEqual(typeof(self.id), "string", "self.id is a string");
+ test.assert(self.id.length > 0);
+
+ var source = self.data.load("test-content-symbiont.js");
+ test.assert(source.match(/test-content-symbiont/), "self.data.load() works");
+
+ // Likewise, we can't assert anything about the full URL, because that
+ // depends on self.id . We can only assert that it ends in the right
+ // thing.
+ var url = self.data.url("test-content-symbiont.js");
+ test.assertEqual(typeof(url), "string", "self.data.url('x') returns string");
+ test.assertEqual(/\/test-content-symbiont\.js$/.test(url), true);
+
+ // Make sure 'undefined' is not in url when no string is provided.
+ url = self.data.url();
+ test.assertEqual(typeof(url), "string", "self.data.url() returns string");
+ test.assertEqual(/\/undefined$/.test(url), false);
+
+ // When tests are run on just the api-utils package, self.name is
+ // api-utils. When they're run as 'cfx testall', self.name is testpkgs.
+ test.assert((self.name == "api-utils") || (self.name == "testpkgs"),
+ "self.name is api-utils or testpkgs");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-set-exports.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-set-exports.js
new file mode 100644
index 0000000..be89305
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-set-exports.js
@@ -0,0 +1,31 @@
+let four = require("./modules/exportsEquals");
+exports.testExportsEquals = function(test) {
+ test.assertEqual(four, 4);
+}
+
+/* TODO: Discuss idea of dropping support for this feature that was alternative
+ to `module.exports = ..` that failed.
+let five = require("./modules/setExports");
+exports.testSetExports = function(test) {
+ test.assertEqual(five, 5);
+}
+
+exports.testDupeSetExports = function(test) {
+ var passed = false;
+ try {
+ var dupe = require('./modules/dupeSetExports');
+ } catch(e) {
+ passed = /define\(\) was used, so module\.exports= and module\.setExports\(\) may not be used/.test(e.toString());
+ }
+ test.assertEqual(passed, true, 'define() or setExports(), not both');
+}
+*/
+
+exports.testModule = function(test) {
+ // module.id is not cast in stone yet. In the future, it may include the
+ // package name, or may possibly be a/ URL of some sort. For now, it's a
+ // URL that starts with resource: and ends with this module name, but the
+ // part in between varies depending upon how the test is run.
+ var found = /test-set-exports$/.test(module.id);
+ test.assertEqual(found, true, module.id+" ends with test-set-exports.js");
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab-browser.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab-browser.js
new file mode 100644
index 0000000..10d9614
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab-browser.js
@@ -0,0 +1,525 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var timer = require("timer");
+var {Cc,Ci} = require("chrome");
+
+function onBrowserLoad(callback, event) {
+ if (event.target && event.target.defaultView == this) {
+ this.removeEventListener("load", onBrowserLoad, true);
+ let browsers = this.document.getElementsByTagName("tabbrowser");
+ try {
+ require("timer").setTimeout(function (window) {
+ callback(window, browsers[0]);
+ }, 10, this);
+ } catch (e) { console.exception(e); }
+ }
+}
+// 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", onBrowserLoad.bind(window, callback), true);
+
+ return window;
+}
+
+// Helper for calling code at window close
+function closeBrowserWindow(window, callback) {
+ require("timer").setTimeout(function() {
+ window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload, false);
+ callback();
+ }, false);
+ window.close();
+ }, 0);
+}
+
+// Helper for opening two windows at once
+function openTwoWindows(callback) {
+ openBrowserWindow(function (window1) {
+ openBrowserWindow(function (window2) {
+ callback(window1, window2);
+ });
+ });
+}
+
+// Helper for closing two windows at once
+function closeTwoWindows(window1, window2, callback) {
+ closeBrowserWindow(window1, function() {
+ closeBrowserWindow(window2, callback);
+ });
+}
+
+exports.testAddTab = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(window, browser) {
+ const tabBrowser = require("tab-browser");
+
+ let cache = [];
+ let windowUtils = require("window-utils");
+ new windowUtils.WindowTracker({
+ onTrack: function(win) {
+ cache.push(win);
+ },
+ onUntrack: function(win) {
+ cache.splice(cache.indexOf(win), 1)
+ }
+ });
+ let startWindowCount = cache.length;
+
+ // Test 1: add a tab
+ let firstUrl = "data:text/html,one";
+ tabBrowser.addTab(firstUrl, {
+ onLoad: function(e) {
+ let win1 = cache[startWindowCount - 1];
+ test.assertEqual(win1.content.location, firstUrl, "URL of new tab in first window matches");
+
+ // Test 2: add a tab in a new window
+ let secondUrl = "data:text/html,two";
+ tabBrowser.addTab(secondUrl, {
+ inNewWindow: true,
+ onLoad: function(e) {
+ test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
+ let win2 = cache[startWindowCount];
+ let gBrowser = win2.gBrowser;
+ gBrowser.addEventListener("DOMContentLoaded", function onLoad(e) {
+ gBrowser.removeEventListener("DOMContentLoaded", onLoad, false);
+ test.assertEqual(win2.content.location, secondUrl, "URL of new tab in the new window matches");
+
+ closeBrowserWindow(win2, function() {
+ closeBrowserWindow(win1, function() {
+ test.done();
+ });
+ });
+ }, false);
+ }
+ });
+ }
+ });
+ });
+};
+
+exports.testTrackerWithDelegate = function(test) {
+ test.waitUntilDone();
+ const tabBrowser = require("tab-browser");
+
+ var delegate = {
+ state: "initializing",
+ onTrack: function onTrack(browser) {
+ if (this.state == "initializing") {
+ this.state = "waiting for browser window to open";
+ }
+ else if (this.state == "waiting for browser window to open") {
+ this.state = "waiting for browser window to close";
+ require("timer").setTimeout(function() {
+ closeBrowserWindow(browser.ownerDocument.defaultView, function() {
+ test.assertEqual(delegate.state, "deinitializing");
+ tb.unload();
+ test.done();
+ });
+ }, 0);
+ }
+ else
+ test.fail("invalid state");
+ },
+ onUntrack: function onUntrack(browser) {
+ if (this.state == "waiting for browser window to close") {
+ test.pass("proper state in onUntrack");
+ this.state = "deinitializing";
+ }
+ else if (this.state != "deinitializing")
+ test.fail("invalid state");
+ }
+ };
+ var tb = new tabBrowser.Tracker(delegate);
+
+ delegate.state = "waiting for browser window to open";
+
+ openBrowserWindow();
+};
+
+exports.testWhenContentLoaded = function(test) {
+ test.waitUntilDone();
+ const tabBrowser = require("tab-browser");
+
+ var tracker = tabBrowser.whenContentLoaded(
+ function(window) {
+ var item = window.document.getElementById("foo");
+ test.assertEqual(item.textContent, "bar",
+ "whenContentLoaded() works.");
+ tracker.unload();
+ closeBrowserWindow(activeWindow(), function() {
+ test.done();
+ });
+ });
+
+ openBrowserWindow(function(browserWindow, browser) {
+ var html = '<div id="foo">bar</div>';
+ browser.addTab("data:text/html," + html);
+ });
+};
+
+exports.testTrackerWithoutDelegate = function(test) {
+ test.waitUntilDone();
+ const tabBrowser = require("tab-browser");
+
+ openBrowserWindow(function(browserWindow, browser) {
+ var tb = new tabBrowser.Tracker();
+
+ if (tb.length == 0)
+ test.fail("expect at least one tab browser to exist.");
+
+ for (var i = 0; i < tb.length; i++)
+ test.assertEqual(tb.get(i).nodeName, "tabbrowser",
+ "get() method and length prop should work");
+ for (var b in tb)
+ test.assertEqual(b.nodeName, "tabbrowser",
+ "iterator should work");
+
+ var matches = [b for (b in tb)
+ if (b == browser)];
+ test.assertEqual(matches.length, 1,
+ "New browser should be in tracker.");
+ tb.unload();
+
+ closeBrowserWindow(browserWindow, function() {
+ test.done();
+ });
+ });
+};
+
+exports.testTabTracker = function(test) {
+ test.waitUntilDone();
+ const tabBrowser = require("tab-browser");
+
+ openBrowserWindow(function(browserWindow, browser) {
+ var delegate = {
+ tracked: 0,
+ onTrack: function(tab) {
+ this.tracked++;
+ },
+ onUntrack: function(tab) {
+ this.tracked--;
+ }
+ };
+
+ let tabTracker = tabBrowser.TabTracker(delegate);
+
+ let tracked = delegate.tracked;
+ let url1 = "data:text/html,1";
+ let url2 = "data:text/html,2";
+ let url3 = "data:text/html,3";
+ let tabCount = 0;
+
+ function tabLoadListener(e) {
+ let loadedURL = e.target.defaultView.location;
+ if (loadedURL == url1)
+ tabCount++;
+ else if (loadedURL == url2)
+ tabCount++;
+ else if (loadedURL == url3)
+ tabCount++;
+
+ if (tabCount == 3) {
+ test.assertEqual(delegate.tracked, tracked + 3, "delegate tracked tabs matched count");
+ tabTracker.unload();
+ closeBrowserWindow(browserWindow, function() {
+ require("timer").setTimeout(function() test.done(), 0);
+ });
+ }
+ }
+
+ tabBrowser.addTab(url1, {
+ onLoad: tabLoadListener
+ });
+ tabBrowser.addTab(url2, {
+ onLoad: tabLoadListener
+ });
+ tabBrowser.addTab(url3, {
+ onLoad: tabLoadListener
+ });
+ });
+};
+
+exports.testActiveTab = function(test) {
+ test.waitUntilDone();
+ openBrowserWindow(function(browserWindow, browser) {
+ const tabBrowser = require("tab-browser");
+ const TabModule = require("tab-browser").TabModule;
+ let tm = new TabModule(browserWindow);
+ test.assertEqual(tm.length, 1);
+ let url1 = "data:text/html,foo";
+ let url2 = "data:text/html,bar";
+
+ function tabURL(tab) tab.ownerDocument.defaultView.content.location.toString()
+
+ tabBrowser.addTab(url1, {
+ onLoad: function(e) {
+ // make sure we're running in the right window.
+ test.assertEqual(tabBrowser.activeTab.ownerDocument.defaultView, browserWindow, "active window matches");
+ browserWindow.focus();
+
+ test.assertEqual(tabURL(tabBrowser.activeTab), url1, "url1 matches");
+ let tabIndex = browser.getBrowserIndexForDocument(e.target);
+ let tabAtIndex = browser.tabContainer.getItemAtIndex(tabIndex);
+ test.assertEqual(tabAtIndex, tabBrowser.activeTab, "activeTab element matches");
+
+ tabBrowser.addTab(url2, {
+ inBackground: true,
+ onLoad: function() {
+ test.assertEqual(tabURL(tabBrowser.activeTab), url1, "url1 still matches");
+ let tabAtIndex = browser.tabContainer.getItemAtIndex(tabIndex);
+ test.assertEqual(tabAtIndex, tabBrowser.activeTab, "activeTab element matches");
+ closeBrowserWindow(browserWindow, function() {
+ test.done()
+ });
+ }
+ });
+ }
+ });
+ });
+};
+
+// TabModule tests
+exports.testEventsAndLengthStayInModule = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("tab-browser").TabModule;
+
+ openTwoWindows(function(window1, window2) {
+ let tm1 = new TabModule(window1);
+ let tm2 = new TabModule(window2);
+
+ let counter1 = 0, counter2 = 0;
+ let counterTabs = 0;
+
+ function onOpenListener() {
+ ++counterTabs;
+ if (counterTabs < 5)
+ return;
+ test.assertEqual(counter1, 2, "Correct number of events fired from window 1");
+ test.assertEqual(counter2, 3, "Correct number of events fired from window 2");
+ test.assertEqual(counterTabs, 5, "Correct number of events fired from all windows");
+ test.assertEqual(tm1.length, 3, "Correct number of tabs in window 1");
+ test.assertEqual(tm2.length, 4, "Correct number of tabs in window 2");
+ closeTwoWindows(window1, window2, function() test.done());
+ }
+
+ tm1.onOpen = function() ++counter1 && onOpenListener();
+ tm2.onOpen = function() ++counter2 && onOpenListener();
+
+ let url = "data:text/html,default";
+ tm1.open(url);
+ tm1.open(url);
+
+ tm2.open(url);
+ tm2.open(url);
+ tm2.open(url);
+ });
+}
+
+exports.testTabModuleActiveTab_getterAndSetter = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("tab-browser").TabModule;
+
+ openTwoWindows(function(window1, window2) {
+ let tm1 = new TabModule(window1);
+ let tm2 = new TabModule(window2);
+
+ let tab1 = null;
+ tm1.open({
+ url: "data:text/html,<title>window1,tab1</title>",
+ onOpen: function(tab) tab1 = tab,
+ });
+ tm1.open("data:text/html,<title>window1,tab2</title>");
+
+ tm1.onActivate = function onActivate() {
+ tm1.onActivate.remove(onActivate);
+ require("timer").setTimeout(function() {
+ test.assertEqual(tm1.activeTab.title, "window1,tab1", "activeTab setter works");
+ closeTwoWindows(window1, window2, function() test.done());
+ }, 1000);
+ }
+
+ tm2.open("data:text/html,<title>window2,tab1</title>");
+ tm2.open({
+ url: "data:text/html,<title>window2,tab2</title>",
+ onOpen: function(tab4) {
+ test.assertEqual(tm1.activeTab.title, "window1,tab2", "Correct active tab on window 1");
+ test.assertEqual(tm2.activeTab.title, "window2,tab2", "Correct active tab on window 2");
+
+ tm1.activeTab = tab1;
+ tm1.activeTab = tab4; // Setting activeTab from another window should have no effect
+ }
+ });
+ });
+}
+
+// test tabs iterator
+exports.testTabModuleTabsIterator = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("tab-browser").TabModule;
+
+ openBrowserWindow(function(window) {
+ let tm1 = new TabModule(window);
+ let url = "data:text/html,default";
+ tm1.open(url);
+ tm1.open(url);
+ tm1.open({
+ url: url,
+ onOpen: function(tab) {
+ let count = 0;
+ for each (let t in tm1) count++;
+ test.assertEqual(count, 4, "iterated tab count matches");
+ test.assertEqual(count, tm1.length, "length tab count matches");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// inNewWindow parameter is ignored on single-window modules
+exports.testTabModuleCantOpenInNewWindow = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("tab-browser").TabModule;
+
+ openBrowserWindow(function(window) {
+ let tm = new TabModule(window);
+ let url = "data:text/html,default";
+ tm.open({
+ url: url,
+ inNewWindow: true,
+ onOpen: function() {
+ test.assertEqual(tm.length, 2, "Tab was open on same window");
+ closeBrowserWindow(window, function() test.done());
+ }
+ });
+ });
+};
+
+// Test that having two modules attached to the same
+// window won't duplicate events fired on each module
+exports.testModuleListenersDontInteract = function(test) {
+ test.waitUntilDone();
+ let TabModule = require("tab-browser").TabModule;
+
+ openBrowserWindow(function(window) {
+ let tm1 = new TabModule(window);
+ let tm2 = new TabModule(window);
+
+ let url = "data:text/html,foo";
+ let eventCount = 0, eventModule1 = 0, eventModule2 = 0;
+
+
+ let listener1 = function() {
+ // this should be called twice: when tab is open and when
+ // the url location is changed
+ eventCount++;
+ eventModule1++;
+ }
+ tm1.onReady = listener1;
+
+ tm2.open({
+ url: "about:blank",
+ onOpen: function(tab) {
+ // add listener via property assignment
+ let listener2 = function() {
+ eventCount++;
+ eventModule2++;
+ };
+ tab.onReady = listener2;
+
+ // add listener via collection add
+ let listener3 = function() {
+ eventCount++;
+ eventModule2++;
+ };
+ tab.onReady.add(listener3);
+
+ tab.location = url;
+
+ test.waitUntilEqual(function () eventCount, 4,
+ "Correct global number of events")
+ .then(function () {
+ test.assertEqual(eventModule1, 2,
+ "Correct number of events on module 1");
+ test.assertEqual(eventModule2, 2,
+ "Correct number of events on module 2");
+
+ tm1.onReady.remove(listener1);
+ tab.onReady.remove(listener2);
+ tab.onReady.remove(listener3);
+ closeBrowserWindow(window, function() test.done());
+ });
+ }
+ });
+ });
+};
+
+/******************* helpers *********************/
+
+// Helper for getting the active window
+function activeWindow() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).
+ getMostRecentWindow("navigator:browser");
+};
+
+// If the module doesn't support the app we're being run in, require() will
+// throw. In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+ require("tab-browser");
+}
+catch (err) {
+ // This bug should be mentioned in the error message.
+ let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716";
+ if (err.message.indexOf(bug) < 0)
+ throw err;
+ for (let [prop, val] in Iterator(exports)) {
+ if (/^test/.test(prop) && typeof(val) === "function")
+ delete exports[prop];
+ }
+ exports.testAppNotSupported = function (test) {
+ test.pass("the tab-browser module does not support this application.");
+ };
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab-observer.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab-observer.js
new file mode 100644
index 0000000..b56b1aa
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab-observer.js
@@ -0,0 +1,35 @@
+"use strict";
+
+const { openTab, closeTab } = require("api-utils/tabs/utils");
+const { Loader } = require("./helpers");
+const { setTimeout } = require("timer");
+
+exports["test unload tab observer"] = function(assert, done) {
+ let loader = Loader(module);
+
+ let window = loader.require("api-utils/window-utils").activeBrowserWindow;
+ let observer = loader.require("api-utils/tabs/observer").observer;
+ let opened = 0;
+ let closed = 0;
+
+ observer.on("open", function onOpen(window) { opened++; });
+ observer.on("close", function onClose(window) { closed++; });
+
+ // Open and close tab to trigger observers.
+ closeTab(openTab(window, "data:text/html,tab-1"));
+
+ // Unload the module so that all listeners set by observer are removed.
+ loader.unload();
+
+ // Open and close tab once again.
+ closeTab(openTab(window, "data:text/html,tab-2"));
+
+ // Enqueuing asserts to make sure that assertion is not performed early.
+ setTimeout(function () {
+ assert.equal(1, opened, "observer open was called before unload only");
+ assert.equal(1, closed, "observer close was called before unload only");
+ done();
+ }, 0);
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab.js
new file mode 100644
index 0000000..4015faa
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-tab.js
@@ -0,0 +1,106 @@
+const tabAPI = require("tabs/tab");
+const tabs = require("tabs"); // From addon-kit
+const windowUtils = require("window-utils");
+
+// The primary test tab
+var primaryTab;
+
+// We have an auxiliary tab to test background tabs.
+var auxTab;
+
+// The window for the outer iframe in the primary test page
+var iframeWin;
+
+exports.testGetTabForWindow = function(test) {
+ test.waitUntilDone();
+
+ test.assertEqual(tabAPI.getTabForWindow(windowUtils.activeWindow), null,
+ "getTabForWindow return null on topwindow");
+ test.assertEqual(tabAPI.getTabForWindow(windowUtils.activeBrowserWindow), null,
+ "getTabForWindow return null on topwindow");
+
+ let subSubDocument = encodeURIComponent(
+ 'Sub iframe<br/>'+
+ '<iframe id="sub-sub-iframe" src="data:text/html,SubSubIframe" />');
+ let subDocument = encodeURIComponent(
+ 'Iframe<br/>'+
+ '<iframe id="sub-iframe" src="data:text/html,'+subSubDocument+'" />');
+ let url = 'data:text/html,' + encodeURIComponent(
+ 'Content<br/><iframe id="iframe" src="data:text/html,'+subDocument+'" />');
+
+ // Open up a new tab in the background.
+ //
+ // This lets us test whether GetTabForWindow works even when the tab in
+ // question is not active.
+ tabs.open({
+ inBackground: true,
+ url: "about:mozilla",
+ onReady: function(tab) { auxTab = tab; step2(url, test);},
+ onActivate: function(tab) { step3(test); }
+ });
+}
+
+function step2(url, test) {
+
+ tabs.open({
+ url: url,
+ onReady: function(tab) {
+ primaryTab = tab;
+ let window = windowUtils.activeBrowserWindow.content;
+
+ let matchedTab = tabAPI.getTabForWindow(window);
+ test.assertEqual(matchedTab, tab,
+ "We are able to find the tab with his content window object");
+
+ let timer = require("timer");
+ function waitForFrames() {
+ let iframe = window.document.getElementById("iframe");
+ if (!iframe) {
+ timer.setTimeout(waitForFrames, 100);
+ return;
+ }
+ iframeWin = iframe.contentWindow;
+ let subIframe = iframeWin.document.getElementById("sub-iframe");
+ if (!subIframe) {
+ timer.setTimeout(waitForFrames, 100);
+ return;
+ }
+ let subIframeWin = subIframe.contentWindow;
+ let subSubIframe = subIframeWin.document.getElementById("sub-sub-iframe");
+ if (!subSubIframe) {
+ timer.setTimeout(waitForFrames, 100);
+ return;
+ }
+ let subSubIframeWin = subSubIframe.contentWindow;
+
+ matchedTab = tabAPI.getTabForWindow(iframeWin);
+ test.assertEqual(matchedTab, tab,
+ "We are able to find the tab with an iframe window object");
+
+ matchedTab = tabAPI.getTabForWindow(subIframeWin);
+ test.assertEqual(matchedTab, tab,
+ "We are able to find the tab with a sub-iframe window object");
+
+ matchedTab = tabAPI.getTabForWindow(subSubIframeWin);
+ test.assertEqual(matchedTab, tab,
+ "We are able to find the tab with a sub-sub-iframe window object");
+
+ // Put our primary tab in the background and test again.
+ // The onActivate listener will take us to step3.
+ auxTab.activate();
+ }
+ waitForFrames();
+ }
+ });
+}
+
+function step3(test) {
+
+ let matchedTab = tabAPI.getTabForWindow(iframeWin);
+ test.assertEqual(matchedTab, primaryTab,
+ "We get the correct tab even when it's in the background");
+
+ primaryTab.close(function () {
+ auxTab.close(function () { test.done();});
+ });
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-text-streams.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-text-streams.js
new file mode 100644
index 0000000..2c1c80d
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-text-streams.js
@@ -0,0 +1,190 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const file = require("file");
+const url = require("url");
+const { Loader } = require("./helpers");
+
+const STREAM_CLOSED_ERROR = "The stream is closed and cannot be used.";
+
+// This should match the constant of the same name in text-streams.js.
+const BUFFER_BYTE_LEN = 0x8000;
+
+exports.testWriteRead = function (test) {
+ let fname = dataFileFilename();
+
+ // Write a small string less than the stream's buffer size...
+ let str = "exports.testWriteRead data!";
+ let stream = file.open(fname, "w");
+ test.assert(!stream.closed, "stream.closed after open should be false");
+ stream.write(str);
+ stream.close();
+ test.assert(stream.closed, "stream.closed after close should be true");
+ test.assertRaises(function () stream.close(),
+ STREAM_CLOSED_ERROR,
+ "stream.close after already closed should raise error");
+ test.assertRaises(function () stream.write("This shouldn't be written!"),
+ STREAM_CLOSED_ERROR,
+ "stream.write after close should raise error");
+
+ // ... and read it.
+ stream = file.open(fname);
+ test.assert(!stream.closed, "stream.closed after open should be false");
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ test.assertEqual(stream.read(), "",
+ "stream.read at EOS should return empty string");
+ stream.close();
+ test.assert(stream.closed, "stream.closed after close should be true");
+ test.assertRaises(function () stream.close(),
+ STREAM_CLOSED_ERROR,
+ "stream.close after already closed should raise error");
+ test.assertRaises(function () stream.read(),
+ STREAM_CLOSED_ERROR,
+ "stream.read after close should raise error");
+
+ // Write a big string many times the size of the stream's buffer and read it.
+ // Since it comes after the previous test, this also ensures that the file is
+ // truncated when it's opened for writing.
+ str = "";
+ let bufLen = BUFFER_BYTE_LEN;
+ let fileSize = bufLen * 10;
+ for (let i = 0; i < fileSize; i++)
+ str += i % 10;
+ stream = file.open(fname, "w");
+ stream.write(str);
+ stream.close();
+ stream = file.open(fname);
+ test.assertEqual(stream.read(), str,
+ "stream.read should return string written");
+ stream.close();
+
+ // The same, but write and read in chunks.
+ stream = file.open(fname, "w");
+ 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 = file.open(fname);
+ let readStr = "";
+ bufLen = BUFFER_BYTE_LEN;
+ let readLen = bufLen + 1;
+ do {
+ var frag = stream.read(readLen);
+ readStr += frag;
+ } while (frag);
+ stream.close();
+ test.assertEqual(readStr, str,
+ "stream.write and read in chunks should work as expected");
+
+ // Read the same file, passing in strange numbers of bytes to read.
+ stream = file.open(fname);
+ test.assertEqual(stream.read(fileSize * 100), str,
+ "stream.read with big byte length should return string " +
+ "written");
+ stream.close();
+
+ stream = file.open(fname);
+ test.assertEqual(stream.read(0), "",
+ "string.read with zero byte length should return empty " +
+ "string");
+ stream.close();
+
+ stream = file.open(fname);
+ test.assertEqual(stream.read(-1), "",
+ "string.read with negative byte length should return " +
+ "empty string");
+ stream.close();
+
+ file.remove(fname);
+};
+
+exports.testWriteAsync = function (test) {
+ test.waitUntilDone();
+
+ let fname = dataFileFilename();
+ let str = "exports.testWriteAsync data!";
+ let stream = file.open(fname, "w");
+ test.assert(!stream.closed, "stream.closed after open should be false");
+
+ // Write.
+ stream.writeAsync(str, function (err) {
+ test.assertEqual(this, stream, "|this| should be the stream object");
+ test.assertEqual(err, undefined,
+ "stream.writeAsync should not cause error");
+ test.assert(stream.closed, "stream.closed after write should be true");
+ test.assertRaises(function () stream.close(),
+ STREAM_CLOSED_ERROR,
+ "stream.close after already closed should raise error");
+ test.assertRaises(function () stream.writeAsync("This shouldn't work!"),
+ STREAM_CLOSED_ERROR,
+ "stream.writeAsync after close should raise error");
+
+ // Read.
+ stream = file.open(fname, "r");
+ test.assert(!stream.closed, "stream.closed after open should be false");
+ let readStr = stream.read();
+ test.assertEqual(readStr, str,
+ "string.read should yield string written");
+ stream.close();
+ file.remove(fname);
+ test.done();
+ });
+};
+
+exports.testUnload = function (test) {
+ let loader = Loader(module);
+ let file = loader.require("file");
+
+ let filename = url.toFilename(module.uri);
+ let stream = file.open(filename);
+
+ loader.unload();
+ test.assert(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() {
+ let dir = file.dirname(url.toFilename(module.uri));
+ return file.join(dir, "test-text-streams-data");
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-timer.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-timer.js
new file mode 100644
index 0000000..7c995e2
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-timer.js
@@ -0,0 +1,127 @@
+var timer = require("timer");
+const { Loader } = require("./helpers");
+
+exports.testSetTimeout = function(test) {
+ timer.setTimeout(function() {
+ test.pass("testSetTimeout passed");
+ test.done();
+ }, 1);
+ test.waitUntilDone();
+};
+
+exports.testParamedSetTimeout = function(test) {
+ let params = [1, 'foo', { bar: 'test' }, null, undefined];
+ timer.setTimeout.apply(null, [function() {
+ test.assertEqual(arguments.length, params.length);
+ for (let i = 0, ii = params.length; i < ii; i++)
+ test.assertEqual(params[i], arguments[i]);
+ test.done();
+ }, 1].concat(params));
+ test.waitUntilDone();
+};
+
+exports.testClearTimeout = function(test) {
+ var myFunc = function myFunc() {
+ test.fail("myFunc() should not be called in testClearTimeout");
+ };
+ var id = timer.setTimeout(myFunc, 1);
+ timer.setTimeout(function() {
+ test.pass("testClearTimeout passed");
+ test.done();
+ }, 2);
+ timer.clearTimeout(id);
+ test.waitUntilDone();
+};
+
+exports.testParamedClearTimeout = function(test) {
+ let params = [1, 'foo', { bar: 'test' }, null, undefined];
+ var myFunc = function myFunc() {
+ test.fail("myFunc() should not be called in testClearTimeout");
+ };
+ var id = timer.setTimeout(myFunc, 1);
+ timer.setTimeout.apply(null, [function() {
+ test.assertEqual(arguments.length, params.length);
+ for (let i = 0, ii = params.length; i < ii; i++)
+ test.assertEqual(params[i], arguments[i]);
+ test.done();
+ }, 1].concat(params));
+ timer.clearTimeout(id);
+ test.waitUntilDone();
+};
+
+exports.testSetInterval = function (test) {
+ var count = 0;
+ var id = timer.setInterval(function () {
+ count++;
+ if (count >= 5) {
+ timer.clearInterval(id);
+ test.pass("testSetInterval passed");
+ test.done();
+ }
+ }, 1);
+ test.waitUntilDone();
+};
+
+exports.testParamedSetInerval = function(test) {
+ let params = [1, 'foo', { bar: 'test' }, null, undefined];
+ let count = 0;
+ let id = timer.setInterval.apply(null, [function() {
+ count ++;
+ if (count < 5) {
+ test.assertEqual(arguments.length, params.length);
+ for (let i = 0, ii = params.length; i < ii; i++)
+ test.assertEqual(params[i], arguments[i]);
+ } else {
+ timer.clearInterval(id);
+ test.done();
+ }
+ }, 1].concat(params));
+ test.waitUntilDone();
+};
+
+exports.testClearInterval = function (test) {
+ timer.clearInterval(timer.setInterval(function () {
+ test.fail("setInterval callback should not be called");
+ }, 1));
+ var id = timer.setInterval(function () {
+ timer.clearInterval(id);
+ test.pass("testClearInterval passed");
+ test.done();
+ }, 2);
+ test.waitUntilDone();
+};
+
+exports.testParamedClearInterval = function(test) {
+ timer.clearInterval(timer.setInterval(function () {
+ test.fail("setInterval callback should not be called");
+ }, 1, timer, {}, null));
+
+ let id = timer.setInterval(function() {
+ timer.clearInterval(id);
+ test.assertEqual(3, arguments.length);
+ test.done();
+ }, 2, undefined, 'test', {});
+ test.waitUntilDone();
+};
+
+
+exports.testUnload = function(test) {
+ var loader = Loader(module);
+ var sbtimer = loader.require("timer");
+
+ var myFunc = function myFunc() {
+ test.fail("myFunc() should not be called in testUnload");
+ };
+
+ sbtimer.setTimeout(myFunc, 1);
+ sbtimer.setTimeout(myFunc, 1, 'foo', 4, {}, undefined);
+ sbtimer.setInterval(myFunc, 1);
+ sbtimer.setInterval(myFunc, 1, {}, null, 'bar', undefined, 87);
+ loader.unload();
+ timer.setTimeout(function() {
+ test.pass("timer testUnload passed");
+ test.done();
+ }, 2);
+ test.waitUntilDone();
+};
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-traceback.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-traceback.js
new file mode 100644
index 0000000..6cf50f0
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-traceback.js
@@ -0,0 +1,114 @@
+var traceback = require("traceback");
+var {Cc,Ci,Cr,Cu} = require("chrome");
+
+function throwNsIException() {
+ var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+ ios.newURI("i'm a malformed URI", null, null);
+}
+
+function throwError() {
+ throw new Error("foob");
+}
+
+exports.testFormatDoesNotFetchRemoteFiles = function(test) {
+ var observers = require("observer-service");
+ ["http", "https"].forEach(
+ function(scheme) {
+ var httpRequests = 0;
+ function onHttp() {
+ httpRequests++;
+ }
+
+ observers.add("http-on-modify-request", onHttp);
+
+ try {
+ var tb = [{filename: scheme + "://www.mozilla.org/",
+ lineNo: 1,
+ funcName: "blah"}];
+ traceback.format(tb);
+ } catch (e) {
+ test.exception(e);
+ }
+
+ observers.remove("http-on-modify-request", onHttp);
+
+ test.assertEqual(httpRequests, 0,
+ "traceback.format() does not make " +
+ scheme + " request");
+ });
+};
+
+exports.testFromExceptionWithString = function(test) {
+ try {
+ throw "foob";
+ test.fail("an exception should've been thrown");
+ } catch (e if e == "foob") {
+ var tb = traceback.fromException(e);
+ test.assertEqual(tb.length, 0);
+ }
+};
+
+exports.testFormatWithString = function(test) {
+ // This can happen if e.g. a thrown exception was
+ // a string instead of an Error instance.
+ test.assertEqual(traceback.format("blah"),
+ "Traceback (most recent call last):");
+};
+
+exports.testFromExceptionWithError = function(test) {
+ try {
+ throwError();
+ test.fail("an exception should've been thrown");
+ } catch (e if e instanceof Error) {
+ var tb = traceback.fromException(e);
+ var xulApp = require("xul-app");
+ test.assertEqual(tb.slice(-1)[0].funcName, "throwError");
+ }
+};
+
+exports.testFromExceptionWithNsIException = function(test) {
+ try {
+ throwNsIException();
+ test.fail("an exception should've been thrown");
+ } catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
+ var tb = traceback.fromException(e);
+ test.assertEqual(tb.slice(-1)[0].funcName,
+ "throwNsIException");
+ }
+};
+
+exports.testFormat = function(test) {
+ function getTraceback() {
+ return traceback.format();
+ }
+
+ var formatted = getTraceback();
+ test.assertEqual(typeof(formatted), "string");
+ var lines = formatted.split("\n");
+ test.assertEqual(lines.slice(-2)[0].indexOf("getTraceback") > 0,
+ true,
+ "formatted traceback should include function name");
+ test.assertEqual(lines.slice(-1)[0].trim(),
+ "return traceback.format();",
+ "formatted traceback should include source code");
+};
+
+exports.testExceptionsWithEmptyStacksAreLogged = function(test) {
+ // Ensures that our fix to bug 550368 works.
+ var sandbox = Cu.Sandbox("http://www.foo.com");
+ var excRaised = false;
+ try {
+ Cu.evalInSandbox("returns 1 + 2;", sandbox, "1.8",
+ "blah.js", 25);
+ } catch (e) {
+ excRaised = true;
+ var stack = traceback.fromException(e);
+ test.assertEqual(stack.length, 1, "stack should have one frame");
+ test.assert(stack[0].filename, "blah.js", "frame should have filename");
+ test.assert(stack[0].lineNo, 25, "frame should have line no");
+ test.assertEqual(stack[0].funcName, null, "frame should have null function name");
+ }
+ if (!excRaised)
+ test.fail("Exception should have been raised.");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-traits-core.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-traits-core.js
new file mode 100644
index 0000000..ecc2c51
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-traits-core.js
@@ -0,0 +1,834 @@
+"use strict";
+
+const ERR_CONFLICT = 'Remaining conflicting property: ',
+ ERR_REQUIRED = 'Missing required property: ';
+
+function assertSametrait(test, trait1, trait2) {
+ let names1 = Object.getOwnPropertyNames(trait1),
+ names2 = Object.getOwnPropertyNames(trait2);
+
+ test.assertEqual(
+ names1.length,
+ names2.length,
+ 'equal traits must have same amount of properties'
+ );
+
+ for (let i = 0; i < names1.length; i++) {
+ let name = names1[i];
+ test.assertNotEqual(
+ -1,
+ names2.indexOf(name),
+ 'equal traits must contain same named properties: ' + name
+ );
+ assertSameDescriptor(test, name, trait1[name], trait2[name]);
+ }
+}
+
+function assertSameDescriptor(test, name, desc1, desc2) {
+ if (desc1.conflict || desc2.conflict) {
+ test.assertEqual(
+ desc1.conflict,
+ desc2.conflict,
+ 'if one of same descriptors has `conflict` another must have it: '
+ + name
+ );
+ } else if (desc1.required || desc2.required) {
+ test.assertEqual(
+ desc1.required,
+ desc2.required,
+ 'if one of same descriptors is has `required` another must have it: '
+ + name
+ );
+ } else {
+ test.assertEqual(
+ desc1.get,
+ desc2.get,
+ 'get must be the same on both descriptors: ' + name
+ );
+ test.assertEqual(
+ desc1.set,
+ desc2.set,
+ 'set must be the same on both descriptors: ' + name
+ );
+ test.assertEqual(
+ desc1.value,
+ desc2.value,
+ 'value must be the same on both descriptors: ' + name
+ );
+ test.assertEqual(
+ desc1.enumerable,
+ desc2.enumerable,
+ 'enumerable must be the same on both descriptors: ' + name
+ );
+ test.assertEqual(
+ desc1.required,
+ desc2.required,
+ 'value must be the same on both descriptors: ' + name
+ );
+ }
+}
+
+function Data(value, enumerable, confligurable, writable) {
+ return {
+ value: value,
+ enumerable: false !== enumerable,
+ confligurable: false !== confligurable,
+ writable: false !== writable
+ };
+}
+
+function Method(method, enumerable, confligurable, writable) {
+ return {
+ value: method,
+ enumerable: false !== enumerable,
+ confligurable: false !== confligurable,
+ writable: false !== writable
+ };
+}
+
+function Accessor(get, set, enumerable, confligurable) {
+ return {
+ get: get,
+ set: set,
+ enumerable: false !== enumerable,
+ confligurable: false !== confligurable,
+ };
+}
+
+function Required(name) {
+ function required() { throw new Error(ERR_REQUIRED + name) }
+ return {
+ get: required,
+ set: required,
+ required: true
+ };
+}
+
+function Conflict(name) {
+ function conflict() { throw new Error(ERR_CONFLICT + name) }
+ return {
+ get: conflict,
+ set: conflict,
+ conflict: true
+ };
+}
+
+function testMethod() {};
+
+const { trait, compose, resolve, required, override, create } =
+ require('traits/core');
+
+
+exports['test:empty trait'] = function(test) {
+ assertSametrait(
+ test,
+ trait({}),
+ {}
+ );
+};
+
+exports['test:simple trait'] = function(test) {
+ assertSametrait(
+ test,
+ trait({
+ a: 0,
+ b: testMethod
+ }),
+ {
+ a: Data(0, true, true, true),
+ b: Method(testMethod, true, true, true)
+ }
+ );
+};
+
+exports['test:simple trait with required prop'] = function(test) {
+ assertSametrait(
+ test,
+ trait({
+ a: required,
+ b: 1
+ }),
+ {
+ a: Required('a'),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:ordering of trait properties is irrelevant'] = function(test) {
+ assertSametrait(test,
+ trait({ a: 0, b: 1, c: required }),
+ trait({ b: 1, c: required, a: 0 })
+ );
+};
+
+exports['test:trait with accessor property'] = function(test) {
+ let record = { get a() {}, set a(v) {} };
+ let get = Object.getOwnPropertyDescriptor(record,'a').get;
+ let set = Object.getOwnPropertyDescriptor(record,'a').set;
+ assertSametrait(test,
+ trait(record),
+ { a: Accessor(get, set ) }
+ );
+};
+
+exports['test:simple composition'] = function(test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1 }),
+ trait({ c: 2, d: testMethod })
+ ),
+ {
+ a: Data(0),
+ b: Data(1),
+ c: Data(2),
+ d: Method(testMethod)
+ }
+ );
+};
+
+exports['test:composition with conflict'] = function(test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1 }),
+ trait({ a: 2, c: testMethod })
+ ),
+ {
+ a: Conflict('a'),
+ b: Data(1),
+ c: Method(testMethod)
+ }
+ );
+};
+
+exports['test:composition of identical props does not cause conflict'] =
+function(test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1 }),
+ trait({ a: 0, c: testMethod })
+ ),
+ {
+ a: Data(0),
+ b: Data(1),
+ c: Method(testMethod) }
+ )
+};
+
+exports['test:composition with identical required props'] =
+function(test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: required, b: 1 }),
+ trait({ a: required, c: testMethod })
+ ),
+ {
+ a: Required(),
+ b: Data(1),
+ c: Method(testMethod)
+ }
+ );
+};
+
+exports['test:composition satisfying a required prop'] = function (test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: required, b: 1 }),
+ trait({ a: testMethod })
+ ),
+ {
+ a: Method(testMethod),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:compose is neutral wrt conflicts'] = function (test) {
+ assertSametrait(test,
+ compose(
+ compose(
+ trait({ a: 1 }),
+ trait({ a: 2 })
+ ),
+ trait({ b: 0 })
+ ),
+ {
+ a: Conflict('a'),
+ b: Data(0)
+ }
+ );
+};
+
+exports['test:conflicting prop overrides required prop'] = function (test) {
+ assertSametrait(test,
+ compose(
+ compose(
+ trait({ a: 1 }),
+ trait({ a: 2 })
+ ),
+ trait({ a: required })
+ ),
+ {
+ a: Conflict('a')
+ }
+ );
+};
+
+exports['test:compose is commutative'] = function (test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1 }),
+ trait({ c: 2, d: testMethod })
+ ),
+ compose(
+ trait({ c: 2, d: testMethod }),
+ trait({ a: 0, b: 1 })
+ )
+ );
+};
+
+exports['test:compose is commutative, also for required/conflicting props'] =
+function (test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1, c: 3, e: required }),
+ trait({ c: 2, d: testMethod })
+ ),
+ compose(
+ trait({ c: 2, d: testMethod }),
+ trait({ a: 0, b: 1, c: 3, e: required })
+ )
+ );
+};
+exports['test:compose is associative'] = function (test) {
+ assertSametrait(test,
+ compose(
+ trait({ a: 0, b: 1, c: 3, d: required }),
+ compose(
+ trait({ c: 3, d: required }),
+ trait({ c: 2, d: testMethod, e: 'foo' })
+ )
+ ),
+ compose(
+ compose(
+ trait({ a: 0, b: 1, c: 3, d: required }),
+ trait({ c: 3, d: required })
+ ),
+ trait({ c: 2, d: testMethod, e: 'foo' })
+ )
+ );
+};
+
+exports['test:diamond import of same prop does not generate conflict'] =
+function (test) {
+ assertSametrait(test,
+ compose(
+ compose(
+ trait({ b: 2 }),
+ trait({ a: 1 })
+ ),
+ compose(
+ trait({ c: 3 }),
+ trait({ a: 1 })
+ ),
+ trait({ d: 4 })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Data(3),
+ d: Data(4)
+ }
+ );
+};
+
+exports['test:resolve with empty resolutions has no effect'] =
+function (test) {
+ assertSametrait(test, resolve({}, trait({
+ a: 1,
+ b: required,
+ c: testMethod
+ })), {
+ a: Data(1),
+ b: Required(),
+ c: Method(testMethod)
+ });
+};
+
+exports['test:resolve: renaming'] = function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'A', c: 'C' },
+ trait({ a: 1, b: required, c: testMethod })
+ ),
+ {
+ A: Data(1),
+ b: Required(),
+ C: Method(testMethod),
+ a: Required(),
+ c: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming to conflicting name causes conflict, order 1']
+= function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'b'},
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ b: Conflict('b'),
+ a: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming to conflicting name causes conflict, order 2']
+= function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'b' },
+ trait({ b: 2, a: 1 })
+ ),
+ {
+ b: Conflict('b'),
+ a: Required()
+ }
+ );
+};
+
+exports['test:resolve: simple exclusion'] = function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: undefined },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Required(),
+ b: Data(2)
+ }
+ );
+};
+
+exports['test:resolve: exclusion to "empty" trait'] = function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: undefined, b: undefined },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Required(),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: exclusion and renaming of disjoint props'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: undefined, b: 'c' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Required(),
+ c: Data(2),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: exclusion and renaming of overlapping props'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: undefined, b: 'a' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Data(2),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming to a common alias causes conflict'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'c', b: 'c' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ c: Conflict('c'),
+ a: Required(),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming overrides required target'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: 'a' },
+ trait({ a: required, b: 2 })
+ ),
+ {
+ a: Data(2),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming required properties has no effect'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: 'a' },
+ trait({ a: 2, b: required })
+ ),
+ {
+ a: Data(2),
+ b: Required()
+ }
+ );
+};
+
+exports['test:resolve: renaming of non-existent props has no effect'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'c', d: 'c' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ c: Data(1),
+ b: Data(2),
+ a: Required()
+ }
+ );
+};
+
+exports['test:resolve: exclusion of non-existent props has no effect'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: undefined },
+ trait({ a: 1 })
+ ),
+ {
+ a: Data(1)
+ }
+ );
+};
+
+exports['test:resolve is neutral w.r.t. required properties'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'c', b: undefined },
+ trait({ a: required, b: required, c: 'foo', d: 1 })
+ ),
+ {
+ a: Required(),
+ b: Required(),
+ c: Data('foo'),
+ d: Data(1)
+ }
+ );
+};
+
+exports['test:resolve supports swapping of property names, ordering 1'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'b', b: 'a' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Data(2),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:resolve supports swapping of property names, ordering 2'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: 'a', a: 'b' },
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Data(2),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:resolve supports swapping of property names, ordering 3'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { b: 'a', a: 'b' },
+ trait({ b: 2, a: 1 })
+ ),
+ {
+ a: Data(2),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:resolve supports swapping of property names, ordering 4'] =
+function (test) {
+ assertSametrait(test,
+ resolve(
+ { a: 'b', b: 'a' },
+ trait({ b: 2, a: 1 })
+ ),
+ {
+ a: Data(2),
+ b: Data(1)
+ }
+ );
+};
+
+exports['test:override of mutually exclusive traits'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ c: 3, d: testMethod })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Data(3),
+ d: Method(testMethod)
+ }
+ );
+};
+
+exports['test:override of mutually exclusive traits is compose'] =
+function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ c: 3, d: testMethod })
+ ),
+ compose(
+ trait({ d: testMethod, c: 3 }),
+ trait({ b: 2, a: 1 })
+ )
+ );
+};
+
+exports['test:override of overlapping traits'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ a: 3, c: testMethod })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Method(testMethod)
+ }
+ );
+};
+
+exports['test:three-way override of overlapping traits'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ b: 4, c: 3 }),
+ trait({ a: 3, c: testMethod, d: 5 })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Data(3),
+ d: Data(5)
+ }
+ );
+};
+
+exports['test:override replaces required properties'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: required, b: 2 }),
+ trait({ a: 1, c: testMethod })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Method(testMethod)
+ }
+ );
+};
+
+exports['test:override is not commutative'] = function (test) {
+ assertSametrait(test,
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ a: 3, c: 4 })
+ ),
+ {
+ a: Data(1),
+ b: Data(2),
+ c: Data(4)
+ }
+ );
+
+ assertSametrait(test,
+ override(
+ trait({ a: 3, c: 4 }),
+ trait({ a: 1, b: 2 })
+ ),
+ {
+ a: Data(3),
+ b: Data(2),
+ c: Data(4)
+ }
+ );
+};
+
+exports['test:override is associative'] = function (test) {
+ assertSametrait(test,
+ override(
+ override(
+ trait({ a: 1, b: 2 }),
+ trait({ a: 3, c: 4, d: 5 })
+ ),
+ trait({ a: 6, c: 7, e: 8 })
+ ),
+ override(
+ trait({ a: 1, b: 2 }),
+ override(
+ trait({ a: 3, c: 4, d: 5 }),
+ trait({ a: 6, c: 7, e: 8 })
+ )
+ )
+ );
+};
+
+exports['test:create simple'] = function(test) {
+ let o1 = create(
+ Object.prototype,
+ trait({ a: 1, b: function() { return this.a; } })
+ );
+
+ test.assertEqual(
+ Object.prototype,
+ Object.getPrototypeOf(o1),
+ 'o1 prototype'
+ );
+ test.assertEqual(1, o1.a, 'o1.a');
+ test.assertEqual(1, o1.b(), 'o1.b()');
+ test.assertEqual(
+ 2,
+ Object.getOwnPropertyNames(o1).length,
+ 'Object.keys(o1).length === 2'
+ );
+};
+
+exports['test:create with Array.prototype'] = function(test) {
+ let o2 = create(Array.prototype, trait({}));
+ test.assertEqual(
+ Array.prototype,
+ Object.getPrototypeOf(o2),
+ "o2 prototype"
+ );
+};
+
+exports['test:exception for incomplete required properties'] =
+function(test) {
+ try {
+ create(Object.prototype, trait({ foo: required }));
+ test.fail('expected create to complain about missing required props');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Missing required property: foo',
+ e.toString(),
+ 'required prop error'
+ );
+ }
+};
+
+exports['test:exception for unresolved conflicts'] = function(test) {
+ try {
+ create({}, compose(trait({ a: 0 }), trait({ a: 1 })));
+ test.fail('expected create to complain about unresolved conflicts');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Remaining conflicting property: a',
+ e.toString(),
+ 'conflicting prop error'
+ );
+ }
+};
+
+exports['test:verify that required properties are present but undefined'] =
+function(test) {
+ try {
+ let o4 = Object.create(Object.prototype, trait({ foo: required }));
+ test.assertEqual(true, 'foo' in o4, 'required property present');
+ try {
+ let foo = o4.foo;
+ test.fail('access to required property must throw');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Missing required property: foo',
+ e.toString(),
+ 'required prop error'
+ )
+ }
+ } catch(e) {
+ test.fail('did not expect create to complain about required props');
+ }
+};
+
+exports['test:verify that conflicting properties are present'] =
+function(test) {
+ try {
+ let o5 = Object.create(
+ Object.prototype,
+ compose(trait({ a: 0 }), trait({ a: 1 }))
+ );
+ test.assertEqual(true, 'a' in o5, 'conflicting property present');
+ try {
+ let a = o5.a; // accessors or data prop
+ test.fail('expected conflicting prop to cause exception');
+ } catch (e) {
+ test.assertEqual(
+ 'Error: Remaining conflicting property: a',
+ e.toString(),
+ 'conflicting prop access error'
+ );
+ }
+ } catch(e) {
+ test.fail('did not expect create to complain about conflicting props');
+ }
+};
+
+exports['test diamond with conflicts'] = function(test) {
+ function makeT1(x) trait({ m: function() { return x; } })
+ function makeT2(x) compose(trait({ t2: 'foo' }), makeT1(x))
+ function makeT3(x) compose(trait({ t3: 'bar' }), makeT1(x))
+
+ let T4 = compose(makeT2(5), makeT3(5));
+ try {
+ let o = create(Object.prototype, T4);
+ test.fail('expected diamond prop to cause exception');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Remaining conflicting property: m',
+ e.toString(),
+ 'diamond prop conflict'
+ );
+ }
+};
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-traits.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-traits.js
new file mode 100644
index 0000000..6940616
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-traits.js
@@ -0,0 +1,394 @@
+"use strict";
+
+const { Trait } = require('traits');
+
+exports['test:simple compose'] = function(test) {
+ let List = Trait.compose({
+ _list: null,
+ constructor: function List() {
+ this._list = [];
+ },
+ list: function list() this._list.slice(0),
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ test.assertNotEqual(undefined, List, 'should not be undefined');
+ test.assertEqual('function', typeof List, 'type should be function');
+ test.assertEqual(
+ Trait.compose,
+ List.compose,
+ 'should inherit static compose'
+ );
+ test.assertEqual(
+ Trait.override,
+ List.override,
+ 'should inherit static override'
+ );
+ test.assertEqual(
+ Trait.required,
+ List.required,
+ 'should inherit static required'
+ );
+ test.assertEqual(
+ Trait.resolve,
+ List.resolve,
+ 'should inherit static resolve'
+ );
+
+ test.assert(
+ !('_list' in List.prototype),
+ 'should not expose private API'
+ );
+}
+exports['test: compose trait instance and create instance'] = function(test) {
+ let List = Trait.compose({
+ constructor: function List(options) {
+ this._list = [];
+ this._public.publicMember = options.publicMember;
+ },
+ _privateMember: true,
+ get privateMember() this._privateMember,
+ get list() this._list.slice(0),
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list
+ let index = list.indexOf(item)
+ if (0 <= index) list.slice(index, 1)
+ }
+ });
+ let list = List({ publicMember: true });
+
+ test.assertEqual('object', typeof list, 'should return an object')
+ test.assertEqual(
+ true,
+ list instanceof List,
+ 'should be instance of a List'
+ );
+
+ test.assertEqual(
+ undefined,
+ list._privateMember,
+ 'instance should not expose private API'
+ );
+
+ test.assertEqual(
+ true,
+ list.privateMember,
+ 'privates are accessible by public API'
+ );
+
+ list._privateMember = false;
+
+ test.assertEqual(
+ true,
+ list.privateMember,
+ 'property changes on instance must not affect privates'
+ );
+
+ test.assert(
+ !('_list' in list),
+ 'instance should not expose private members'
+ );
+
+ test.assertEqual(
+ true,
+ list.publicMember,
+ 'public members are exposed'
+ )
+ test.assertEqual(
+ 'function',
+ typeof list.add,
+ 'should be function'
+ )
+ test.assertEqual(
+ 'function',
+ typeof list.remove,
+ 'should be function'
+ );
+
+ list.add(1);
+ test.assertEqual(
+ 1,
+ list.list[0],
+ 'exposed public API should be able of modifying privates'
+ )
+};
+
+
+exports['test:instances must not be hackable'] = function(test) {
+ let SECRET = 'There is no secret!',
+ secret = null;
+
+ let Class = Trait.compose({
+ _secret: null,
+ protect: function(data) this._secret = data
+ });
+
+ let i1 = Class();
+ i1.protect(SECRET);
+
+ test.assertEqual(
+ undefined,
+ (function() this._secret).call(i1),
+ 'call / apply can\'t access private state'
+ );
+
+ let proto = Object.getPrototypeOf(i1);
+ try {
+ proto.reveal = function() this._secret;
+ secret = i1.reveal();
+ } catch(e) {}
+ test.assertNotEqual(
+ SECRET,
+ secret,
+ 'public __proto__ changes should not affect privates'
+ );
+ secret = null;
+
+ let Class2 = Trait.compose({
+ _secret: null,
+ protect: function(data) this._secret = data
+ });
+ let i2 = Class2();
+ i2.protect(SECRET);
+ try {
+ Object.prototype.reveal = function() this._secret;
+ secret = i2.reveal();
+ } catch(e) {}
+ test.assertNotEqual(
+ SECRET,
+ secret,
+ 'Object.prototype changes must not affect instances'
+ );
+}
+
+exports['test:instanceof'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = []
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ test.assert(List() instanceof List, 'Must be instance of List');
+ test.assert(new List() instanceof List, 'Must be instance of List');
+};
+
+exports['test:privates are unaccessible'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ let list = List();
+ test.assert(!('_list' in list), 'no privates on instance');
+ test.assert(
+ !('_list' in List.prototype),
+ 'no privates on prototype'
+ );
+};
+
+exports['test:public API can access private API'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+ let list = List();
+
+ list.add('test');
+
+ test.assertEqual(
+ 1,
+ list.length,
+ 'should be able to add element and access it from public getter'
+ );
+};
+
+exports['test:required'] = function(test) {
+ const Enumerable = Trait.compose({
+ list: Trait.required,
+ forEach: function forEach(consumer) {
+ return this.list.forEach(consumer);
+ }
+ });
+
+ try {
+ let i = Enumerable();
+ test.fail('should throw when creating instance with required properties');
+ } catch(e) {
+ test.assertEqual(
+ 'Error: Missing required property: list',
+ e.toString(),
+ 'required prop error'
+ );
+ }
+};
+
+exports['test:compose with required'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ const Enumerable = Trait.compose({
+ list: Trait.required,
+ forEach: function forEach(consumer) {
+ return this.list.forEach(consumer);
+ }
+ });
+
+ const EnumerableList = Enumerable.compose({
+ get list() this._list.slice(0)
+ }, List);
+
+ let array = [1,2, 'ab']
+ let l = EnumerableList(array);
+ array.forEach(function(element) l.add(element));
+ let number = 0;
+ l.forEach(function(element, index) {
+ number ++;
+ test.assertEqual(array[index], element, 'should mach array element')
+ });
+ test.assertEqual(
+ array.length,
+ number,
+ 'should perform as many asserts as elements in array'
+ );
+};
+
+exports['test:resolve'] = function(test) {
+ const List = Trait.compose({
+ // private API:
+ _list: null,
+ // public API
+ constructor: function List() {
+ this._list = [];
+ },
+ get length() this._list.length,
+ add: function add(item) this._list.push(item),
+ remove: function remove(item) {
+ let list = this._list;
+ let index = list.indexOf(item);
+ if (0 <= index) list.slice(index, 1);
+ }
+ });
+
+ const Range = List.resolve({
+ constructor: null,
+ add: '_add',
+ }).compose({
+ min: null,
+ max: null,
+ get list() this._list.slice(0),
+ constructor: function Range(min, max) {
+ this.min = min;
+ this.max = max;
+ this._list = [];
+ },
+ add: function(item) {
+ if (item <= this.max && item >= this.min)
+ this._add(item)
+ }
+ });
+
+ let r = Range(0, 10);
+
+ test.assertEqual(
+ 0,
+ r.min,
+ 'constructor must have set min'
+ );
+ test.assertEqual(
+ 10,
+ r.max,
+ 'constructor must have set max'
+ );
+
+ test.assertEqual(
+ 0,
+ r.length,
+ 'should not contain any elements'
+ );
+
+ r.add(5);
+
+ test.assertEqual(
+ 1,
+ r.length,
+ 'should add `5` to list'
+ );
+
+ r.add(12);
+
+ test.assertEqual(
+ 1,
+ r.length,
+ 'should not add `12` to list'
+ );
+};
+
+exports['test:custom iterator'] = function(test) {
+ let Sub = Trait.compose({
+ foo: "foo",
+ bar: "bar",
+ baz: "baz",
+ __iterator__: function() {
+ yield 1;
+ yield 2;
+ yield 3;
+ }
+ });
+
+ let (i = 0, sub = Sub()) {
+ for (let item in sub)
+ test.assertEqual(++i, item, "iterated item has the right value");
+ };
+};
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-type.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-type.js
new file mode 100644
index 0000000..072075c
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-type.js
@@ -0,0 +1,88 @@
+"use strict"
+
+var utils = require("type");
+
+exports["test function"] = function (assert) {
+ assert.ok(utils.isFunction(function(){}), "value is function");
+ assert.ok(utils.isFunction(Object), "Object is function");
+ assert.ok(utils.isFunction(new Function("")), "Genertaed value is function");
+ assert.ok(!utils.isFunction({}), "object is not a function");
+ assert.ok(!utils.isFunction(4), "number is not a function");
+};
+
+exports["test atoms"] = function (assert) {
+ assert.ok(utils.isPrimitive(2), "number is primitive");
+ assert.ok(utils.isPrimitive(NaN), "`NaN` is primitve");
+ assert.ok(utils.isPrimitive(undefined), "`undefined` is primitive");
+ assert.ok(utils.isPrimitive(null), "`null` is primitive");
+ assert.ok(utils.isPrimitive(Infinity), "`Infinity` is primitive");
+ assert.ok(utils.isPrimitive("foo"), "strings are primitive");
+ assert.ok(utils.isPrimitive(true) && utils.isPrimitive(false),
+ "booleans are primitive");
+};
+
+exports["test object"] = function (assert) {
+ assert.ok(utils.isObject({}), "`{}` is object");
+ assert.ok(!utils.isObject(null), "`null` is not an object");
+ assert.ok(!utils.isObject(Object), "functions is not an object");
+};
+
+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 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("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-unit-test.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-unit-test.js
new file mode 100644
index 0000000..eeef994
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-unit-test.js
@@ -0,0 +1,247 @@
+const timer = require("timer");
+const { Loader } = require("./helpers");
+
+var setupCalled = false, teardownCalled = false;
+
+exports.setup = function() {
+ setupCalled = true;
+};
+
+exports.teardown = function() {
+ teardownCalled = true;
+ setupCalled = false;
+};
+
+// Important note - unit tests are run in alphabetical order. The following
+// unit tests for setup/teardown are order dependent, sometimes the result of
+// one test is checked in the next test (testing for teardown does this). When
+// tests are cohesively a single unit, they are named <test_name> - partN where
+// N is their order in the sequence. Secondly, because these tests should be
+// run before all others, they start with an A.
+exports.testASetupTeardownSyncTestPart1 = function(test) {
+ test.assertEqual(true, setupCalled, 'setup function was called before this');
+ test.assertEqual(false, teardownCalled, 'teardown function was not called before this');
+};
+
+exports.testASetupTeardownSyncTestPart2 = function(test) {
+ test.assertEqual(true, setupCalled, 'setup was re-called before this');
+ test.assertEqual(true, teardownCalled, 'teardown was called after first function');
+};
+
+exports.testATeardownAsyncTestPart1 = function(test) {
+ teardownCalled = false;
+
+ timer.setTimeout(function() {
+ test.assertEqual(false, teardownCalled, "teardown not called until done");
+ test.done();
+ }, 200);
+ test.waitUntilDone();
+};
+
+exports.testATeardownAsyncTestPart2 = function(test) {
+ test.assertEqual(true, teardownCalled, "teardown called after done");
+};
+
+exports.testWaitUntilInstant = function(test) {
+ test.waitUntilDone();
+
+ test.waitUntil(function () true, "waitUntil with instant true pass")
+ .then(function () test.done());
+}
+
+exports.testWaitUntil = function(test) {
+ test.waitUntilDone();
+ let succeed = false;
+
+ test.waitUntil(function () succeed, "waitUntil pass")
+ .then(function () test.done());
+
+ timer.setTimeout(function () {
+ succeed = true;
+ }, 200);
+}
+
+exports.testWaitUntilEqual = function(test) {
+ test.waitUntilDone();
+ let succeed = false;
+
+ test.waitUntilEqual("foo", function () succeed ? "foo" : "bar",
+ "waitUntilEqual pass")
+ .then(function () test.done());
+
+ timer.setTimeout(function () {
+ succeed = true;
+ }, 200);
+}
+
+exports.testWaitUntilNotEqual = function(test) {
+ test.waitUntilDone();
+ let succeed = false;
+
+ test.waitUntilNotEqual("foo", function () succeed ? "bar" : "foo",
+ "waitUntilNotEqual pass")
+ .then(function () test.done());
+
+ timer.setTimeout(function () {
+ succeed = true;
+ }, 200);
+}
+
+exports.testWaitUntilMatches = function(test) {
+ test.waitUntilDone();
+ let succeed = false;
+
+ test.waitUntilMatches(function () succeed ? "foo" : "bar",
+ /foo/, "waitUntilEqual pass")
+ .then(function () test.done());
+
+ timer.setTimeout(function () {
+ succeed = true;
+ }, 200);
+}
+
+exports.testWaitUntilErrorInCallback = function(test) {
+ test.waitUntilDone();
+
+ test.expectFail(function() {
+ test.waitUntil(function () {throw "oops"}, "waitUntil pass")
+ .then(function () test.done());
+ });
+}
+
+exports.testWaitUntilTimeoutInCallback = function(test) {
+ test.waitUntilDone(1000);
+
+ let runner = new (require("unit-test").TestRunner)({
+ console: {
+ calls: 0,
+ error: function(msg) {
+ this.calls++;
+ if (this.calls == 1)
+ test.assertEqual(arguments[0], "TEST FAILED: wait4ever (timed out)");
+ else if (this.calls == 2) {
+ test.assertEqual(arguments[0], "test assertion never became true:\n");
+ test.assertEqual(arguments[1], "assertion failed, value is false\n");
+ // We could additionally check that arguments[1] contains the correct
+ // stack, but it would be difficult to do so given that it contains
+ // resource: URLs with a randomly generated string embedded in them
+ // (the ID of the test addon created to run the tests). And in any
+ // case, checking the arguments seems sufficient.
+
+ test.done();
+ }
+ else {
+ test.fail("We got unexpected console.error() calls from waitUntil" +
+ " assertion callback: '" + arguments[1] + "'");
+ }
+ },
+ trace: function () {}
+ }
+ });
+
+ runner.start({
+ test: {
+ name: "wait4ever",
+ testFunction: function(test) {
+ test.waitUntilDone(100);
+ test.waitUntil(function() false);
+ }
+ },
+ onDone: function() {}
+ });
+};
+
+exports.testExpectFail = function(test) {
+ test.expectFail(function() {
+ test.fail('expectFail masking .fail');
+ });
+
+ test.expectFail(function() {
+ test.assert(false, 'expectFail masking .assert');
+ });
+
+ test.assert(true, 'assert should pass with no expectFail');
+/*
+ test.expectFail(function() {
+ test.expectFail(function() {
+ test.fail('this should blow up');
+ });
+ });
+*/
+};
+
+exports.testAssertFunction = function(test) {
+ test.assertFunction(function() {}, 'assertFunction with function');
+ test.expectFail(function() {
+ test.assertFunction(null, 'assertFunction with non-function');
+ });
+};
+
+exports.testAssertUndefined = function(test) {
+ test.assertUndefined(undefined, 'assertUndefined with undefined');
+ test.expectFail(function() {
+ test.assertUndefined(null, 'assertUndefined with null');
+ });
+ test.expectFail(function() {
+ test.assertUndefined(false, 'assertUndefined with false');
+ });
+ test.expectFail(function() {
+ test.assertUndefined(0, 'assertUndefined with 0');
+ });
+};
+
+exports.testAssertNotUndefined = function(test) {
+ test.expectFail(function() {
+ test.assertNotUndefined(undefined, 'assertNotUndefined with undefined');
+ });
+ test.assertNotUndefined(null, 'assertNotUndefined with null');
+ test.assertNotUndefined(false, 'assertNotUndefined with false');
+ test.assertNotUndefined(0, 'assertNotUndefined with 0');
+};
+
+exports.testAssertNull = function(test) {
+ test.assertNull(null, 'assertNull with null');
+ test.expectFail(function() {
+ test.assertNull(undefined, 'assertNull with undefined');
+ });
+ test.expectFail(function() {
+ test.assertNull(false, 'assertNull with false');
+ });
+ test.expectFail(function() {
+ test.assertNull(0, 'assertNull with 0');
+ });
+};
+
+exports.testAssertNotNull = function(test) {
+ test.assertNotNull(undefined, 'assertNotNull with undefined');
+ test.assertNotNull(false, 'assertNotNull with false');
+ test.assertNotNull(0, 'assertNotNull with 0');
+
+ test.expectFail(function() {
+ test.assertNotNull(null, 'testAssertNotNull with null');
+ });
+};
+
+exports.testAssertObject = function(test) {
+ test.assertObject({}, 'assertObject with {}' );
+ test.assertObject(new Object(), 'assertObject with new Object');
+ test.expectFail(function() {
+ test.assertObject('fail', 'assertObject with string');
+ });
+};
+
+exports.testAssertString = function(test) {
+ test.assertString('', 'assertString with ""');
+ test.assertString(new String(), 'assertString with new String');
+};
+
+exports.testAssertArray = function(test) {
+ test.assertArray([], 'assertArray with []');
+ test.assertArray(new Array(), 'assertArray with new Array');
+};
+
+exports.testNumber = function(test) {
+ test.assertNumber(1, 'assertNumber with 1');
+ test.assertNumber(new Number('2'), 'assertNumber with new Number("2")' );
+};
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-unload.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-unload.js
new file mode 100644
index 0000000..33e84d0
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-unload.js
@@ -0,0 +1,196 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com> (Original Author)
+ * Drew Willcoxon <adw@mozilla.com>
+ * Erik Vold <erikvvold@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var unload = require("unload");
+var { Loader } = require("./helpers");
+
+exports.testUnloading = function(test) {
+ var loader = Loader(module);
+ var ul = loader.require("unload");
+ var unloadCalled = 0;
+ var errorsReported = 0;
+ function unload() {
+ unloadCalled++;
+ throw new Error("error");
+ }
+ ul.when(unload);
+
+ // This should be ignored, as we already registered it
+ ul.when(unload);
+
+ function unload2() { unloadCalled++; }
+ ul.when(unload2);
+ loader.unload(undefined, function onError() { errorsReported++; });
+ test.assertEqual(unloadCalled, 2,
+ "Unloader functions are called on unload.");
+ test.assertEqual(errorsReported, 1,
+ "One unload handler threw exception");
+};
+
+exports.testEnsure = function(test) {
+ test.assertRaises(function() { unload.ensure({}); },
+ "object has no 'unload' property",
+ "passing obj with no unload prop should fail");
+ test.assertRaises(function() { unload.ensure({}, "destroy"); },
+ "object has no 'destroy' property",
+ "passing obj with no custom unload prop should fail");
+
+ var called = 0;
+ var obj = {unload: function() { called++; }};
+
+ unload.ensure(obj);
+ obj.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called");
+ obj.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called only once");
+};
+
+/**
+ * Check that destructors are called only once with Traits.
+ * - check that public API is calling the destructor and unregister it,
+ * - check that composed traits with multiple ensure calls, leads to only
+ * one destructor call.
+ */
+exports.testEnsureWithTraits = function(test) {
+
+ let { Trait } = require("traits");
+ let loader = Loader(module);
+ let ul = loader.require("unload");
+
+ let called = 0;
+ let composedCalled = 0;
+ let composedTrait = Trait.compose({
+ constructor: function () {
+ // We have to give "public interface" of this trait, as we want to
+ // call public `unload` method and ensure that we call it only once,
+ // either when we call this public function manually or on add-on unload
+ ul.ensure(this._public);
+ },
+ unload: function unload() {
+ composedCalled++;
+ }
+ });
+ let obj = Trait.compose(
+ composedTrait.resolve({
+ constructor: "_constructor",
+ unload : "_unload"
+ }), {
+ constructor: function constructor() {
+ // Same thing applies here, we need to pass public interface
+ ul.ensure(this._public);
+ this._constructor();
+ },
+ unload: function unload() {
+ called++;
+ this._unload();
+ }
+ })();
+
+ obj.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called");
+
+ test.assertEqual(composedCalled, 1,
+ "composed object unload() should be called");
+
+ obj.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called only once");
+ test.assertEqual(composedCalled, 1,
+ "composed object unload() should be called only once");
+
+ loader.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called only once, after addon unload");
+ test.assertEqual(composedCalled, 1,
+ "composed object unload() should be called only once, " +
+ "after addon unload");
+};
+
+exports.testEnsureWithTraitsPrivate = function(test) {
+
+ let { Trait } = require("traits");
+ let loader = Loader(module);
+ let ul = loader.require("unload");
+
+ let called = 0;
+ let privateObj = null;
+ let obj = Trait.compose({
+ constructor: function constructor() {
+ // This time wa don't have to give public interface,
+ // as we want to call a private method:
+ ul.ensure(this, "_unload");
+ privateObj = this;
+ },
+ _unload: function unload() {
+ called++;
+ this._unload();
+ }
+ })();
+
+ loader.unload();
+ test.assertEqual(called, 1,
+ "unload() should be called");
+
+ privateObj._unload();
+ test.assertEqual(called, 1,
+ "_unload() should be called only once, after addon unload");
+};
+
+exports.testReason = function (test) {
+ var reason = "Reason doesn't actually have to be anything in particular.";
+ var loader = Loader(module);
+ var ul = loader.require("unload");
+ ul.when(function (rsn) {
+ test.assertEqual(rsn, reason,
+ "when() reason should be reason given to loader");
+ });
+ var obj = {
+ unload: function (rsn) {
+ test.assertEqual(rsn, reason,
+ "ensure() reason should be reason given to loader");
+ }
+ };
+ ul.ensure(obj);
+ loader.unload(reason);
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-url.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-url.js
new file mode 100644
index 0000000..3307769
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-url.js
@@ -0,0 +1,157 @@
+var url = require("url");
+
+exports.testResolve = function(test) {
+ test.assertEqual(url.URL("bar", "http://www.foo.com/").toString(),
+ "http://www.foo.com/bar");
+
+ test.assertEqual(url.URL("bar", "http://www.foo.com"),
+ "http://www.foo.com/bar");
+
+ test.assertEqual(url.URL("http://bar.com/", "http://foo.com/"),
+ "http://bar.com/",
+ "relative should override base");
+
+ test.assertRaises(function() { url.URL("blah"); },
+ "malformed URI: blah",
+ "url.resolve() should throw malformed URI on base");
+
+ test.assertRaises(function() { url.URL("chrome://global"); },
+ "invalid URI: chrome://global",
+ "url.resolve() should throw invalid URI on base");
+
+ test.assertRaises(function() { url.URL("chrome://foo/bar"); },
+ "invalid URI: chrome://foo/bar",
+ "url.resolve() should throw on bad chrome URI");
+
+ test.assertEqual(url.URL("", "http://www.foo.com"),
+ "http://www.foo.com/",
+ "url.resolve() should add slash to end of domain");
+};
+
+exports.testParseHttp = function(test) {
+ var info = url.URL("http://foo.com/bar");
+ test.assertEqual(info.scheme, "http");
+ test.assertEqual(info.host, "foo.com");
+ test.assertEqual(info.port, null);
+ test.assertEqual(info.userPass, null);
+ test.assertEqual(info.path, "/bar");
+};
+
+exports.testParseHttpWithPort = function(test) {
+ var info = url.URL("http://foo.com:5/bar");
+ test.assertEqual(info.port, 5);
+};
+
+exports.testParseChrome = function(test) {
+ var info = url.URL("chrome://global/content/blah");
+ test.assertEqual(info.scheme, "chrome");
+ test.assertEqual(info.host, "global");
+ test.assertEqual(info.port, null);
+ test.assertEqual(info.userPass, null);
+ test.assertEqual(info.path, "/content/blah");
+};
+
+exports.testParseAbout = function(test) {
+ var info = url.URL("about:boop");
+ test.assertEqual(info.scheme, "about");
+ test.assertEqual(info.host, null);
+ test.assertEqual(info.port, null);
+ test.assertEqual(info.userPass, null);
+ test.assertEqual(info.path, "boop");
+};
+
+exports.testParseFTP = function(test) {
+ var info = url.URL("ftp://1.2.3.4/foo");
+ test.assertEqual(info.scheme, "ftp");
+ test.assertEqual(info.host, "1.2.3.4");
+ test.assertEqual(info.port, null);
+ test.assertEqual(info.userPass, null);
+ test.assertEqual(info.path, "/foo");
+};
+
+exports.testParseFTPWithUserPass = function(test) {
+ var info = url.URL("ftp://user:pass@1.2.3.4/foo");
+ test.assertEqual(info.userPass, "user:pass");
+};
+
+exports.testToFilename = function(test) {
+ test.assertRaises(
+ function() { url.toFilename("resource://nonexistent"); },
+ "resource does not exist: resource://nonexistent/",
+ "url.toFilename() on nonexistent resources should throw"
+ );
+
+ test.assertMatches(url.toFilename(module.uri),
+ /.*test-url\.js$/,
+ "url.toFilename() on resource: URIs should work");
+
+ test.assertRaises(
+ function() { url.toFilename("http://foo.com/"); },
+ "cannot map to filename: http://foo.com/",
+ "url.toFilename() on http: URIs should raise error"
+ );
+
+ try {
+ test.assertMatches(
+ url.toFilename("chrome://global/content/console.xul"),
+ /.*console\.xul$/,
+ "url.toFilename() w/ console.xul works when it maps to filesystem"
+ );
+ } catch (e) {
+ if (/chrome url isn\'t on filesystem/.test(e.message))
+ test.pass("accessing console.xul in jar raises exception");
+ else
+ test.fail("accessing console.xul raises " + e);
+ }
+
+ // TODO: Are there any chrome URLs that we're certain exist on the
+ // filesystem?
+ // test.assertMatches(url.toFilename("chrome://myapp/content/main.js"),
+ // /.*main\.js$/);
+};
+
+exports.testFromFilename = function(test) {
+ var fileUrl = url.fromFilename(url.toFilename(module.uri));
+ test.assertEqual(url.URL(fileUrl).scheme, 'file',
+ 'url.toFilename() should return a file: url');
+ test.assertEqual(url.fromFilename(url.toFilename(fileUrl)),
+ fileUrl);
+};
+
+exports.testURL = function(test) {
+ let URL = url.URL;
+ test.assert(URL("h:foo") instanceof URL, "instance is of correct type");
+ test.assertRaises(function() URL(),
+ "malformed URI: undefined",
+ "url.URL should throw on undefined");
+ test.assertRaises(function() URL(""),
+ "malformed URI: ",
+ "url.URL should throw on empty string");
+ test.assertRaises(function() URL("foo"),
+ "malformed URI: foo",
+ "url.URL should throw on invalid URI");
+ test.assert(URL("h:foo").scheme, "has scheme");
+ test.assertEqual(URL("h:foo").toString(),
+ "h:foo",
+ "toString should roundtrip");
+ // test relative + base
+ test.assertEqual(URL("mypath", "http://foo").toString(),
+ "http://foo/mypath",
+ "relative URL resolved to base");
+ // test relative + no base
+ test.assertRaises(function() URL("path").toString(),
+ "malformed URI: path",
+ "no base for relative URI should throw");
+
+ let a = URL("h:foo");
+ let b = URL(a);
+ test.assertEqual(b.toString(),
+ "h:foo",
+ "a URL can be initialized from another URL");
+ test.assertNotStrictEqual(a, b,
+ "a URL initialized from another URL is not the same object");
+ test.assert(a == "h:foo",
+ "toString is implicit when a URL is compared to a string via ==");
+ test.assertStrictEqual(a + "", "h:foo",
+ "toString is implicit when a URL is concatenated to a string");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-loader.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-loader.js
new file mode 100644
index 0000000..fc6f144
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-loader.js
@@ -0,0 +1,152 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Irakli Gozalishvili <gozala@mozilla.com> (Original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+"use strict";
+
+const { WindowLoader } = require('windows/loader'),
+ { Trait } = require('traits');
+
+const Loader = Trait.compose(
+ WindowLoader,
+ {
+ constructor: function Loader(options) {
+ this._onLoad = options.onLoad;
+ this._onUnload = options.onUnload;
+ if ('window' in options)
+ this._window = options.window;
+ this._load();
+ this.window = this._window;
+ },
+ window: null,
+ _onLoad: null,
+ _onUnload: null,
+ _tabOptions: []
+ }
+);
+
+exports['test compositions with missing required properties'] = function(test) {
+ test.assertRaises(
+ function() WindowLoader.compose({})(),
+ 'Missing required property: _onLoad',
+ 'should throw missing required property exception'
+ );
+ test.assertRaises(
+ function() WindowLoader.compose({ _onLoad: null, _tabOptions: null })(),
+ 'Missing required property: _onUnload',
+ 'should throw missing required property `_onUnload`'
+ );
+ test.assertRaises(
+ function() WindowLoader.compose({ _onUnload: null, _tabOptions: null })(),
+ 'Missing required property: _onLoad',
+ 'should throw missing required property `_onLoad`'
+ );
+ test.assertRaises(
+ function() WindowLoader.compose({ _onUnload: null, _onLoad: null })(),
+ 'Missing required property: _tabOptions',
+ 'should throw missing required property `_tabOptions`'
+ );
+};
+
+exports['test `load` events'] = function(test) {
+ test.waitUntilDone();
+ let onLoadCalled = false;
+ Loader({
+ onLoad: function(window) {
+ onLoadCalled = true;
+ test.assertEqual(
+ window, this._window, 'windows should match'
+ );
+ test.assertEqual(
+ window.document.readyState, 'complete', 'window must be fully loaded'
+ );
+ window.close();
+ },
+ onUnload: function(window) {
+ test.assertEqual(
+ window, this._window, 'windows should match'
+ );
+ test.assertEqual(
+ window.document.readyState, 'complete', 'window must be fully loaded'
+ );
+ test.assert(onLoadCalled, 'load callback is supposed to be called');
+ test.done();
+ }
+ });
+};
+
+exports['test removeing listeners'] = function(test) {
+ test.waitUntilDone();
+ Loader({
+ onLoad: function(window) {
+ test.assertEqual(
+ window, this._window, 'windows should match'
+ );
+ window.close();
+ },
+ onUnload: function(window) {
+ test.done();
+ }
+ });
+};
+
+exports['test create loader from opened window'] = function(test) {
+ test.waitUntilDone();
+ let onUnloadCalled = false;
+ Loader({
+ onLoad: function(window) {
+ test.assertEqual(
+ window, this._window, 'windows should match'
+ );
+ test.assertEqual(
+ window.document.readyState, 'complete', 'window must be fully loaded'
+ );
+ Loader({
+ window: window,
+ onLoad: function(win) {
+ test.assertEqual(win, window, 'windows should match');
+ window.close();
+ },
+ onUnload: function(window) {
+ test.assert(onUnloadCalled, 'first handler should be called already');
+ test.done();
+ }
+ });
+ },
+ onUnload: function(window) {
+ onUnloadCalled = true;
+ }
+ });
+};
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-observer.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-observer.js
new file mode 100644
index 0000000..3f3bfc2
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-observer.js
@@ -0,0 +1,44 @@
+"use strict";
+
+const { Loader } = require("./helpers");
+
+exports["test unload window observer"] = function(assert, done) {
+ // Hacky way to be able to create unloadable modules via makeSandboxedLoader.
+ let loader = Loader(module);
+
+ let utils = loader.require("api-utils/window-utils");
+ let { isBrowser, activeBrowserWindow: activeWindow } = utils;
+ let observer = loader.require("api-utils/windows/observer").observer;
+ let opened = 0;
+ let closed = 0;
+
+ observer.on("open", function onOpen(window) {
+ // Ignoring non-browser windows
+ if (isBrowser(window))
+ opened++;
+ });
+ observer.on("close", function onClose(window) {
+ // Ignore non-browser windows & already opened `activeWindow` (unload will
+ // emit close on it even though it is not actually closed).
+ if (isBrowser(window) && window !== activeWindow)
+ closed++;
+ });
+
+ // Open window and close it to trigger observers.
+ activeWindow.open().close();
+
+ // Unload the module so that all listeners set by observer are removed.
+ loader.unload();
+
+ // Open and close window once again.
+ activeWindow.open().close();
+
+ // Enqueuing asserts to make sure that assertion is not performed early.
+ require("timer").setTimeout(function () {
+ assert.equal(1, opened, "observer open was called before unload only");
+ assert.equal(1, closed, "observer close was called before unload only");
+ done();
+ }, 0);
+};
+
+require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-utils.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-utils.js
new file mode 100644
index 0000000..eff9f2b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-window-utils.js
@@ -0,0 +1,271 @@
+var windowUtils = require("window-utils");
+var timer = require("timer");
+var {Cc,Ci} = require("chrome");
+var { Loader } = require("./helpers");
+
+function makeEmptyWindow() {
+ var xulNs = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var blankXul = ('<?xml version="1.0"?>' +
+ '<?xml-stylesheet href="chrome://global/skin/" ' +
+ ' type="text/css"?>' +
+ '<window xmlns="' + xulNs + '" windowtype="test:window">' +
+ '</window>');
+ var url = "data:application/vnd.mozilla.xul+xml," + escape(blankXul);
+ var features = ["chrome", "width=10", "height=10"];
+
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIWindowWatcher);
+ return ww.openWindow(null, url, null, features.join(","), null);
+}
+
+exports.testCloseOnUnload = function(test) {
+ var timesClosed = 0;
+ var fakeWindow = {
+ _listeners: [],
+ addEventListener: function(name, func, bool) {
+ this._listeners.push(func);
+ },
+ removeEventListener: function(name, func, bool) {
+ var index = this._listeners.indexOf(func);
+ if (index == -1)
+ throw new Error("event listener not found");
+ this._listeners.splice(index, 1);
+ },
+ close: function() {
+ timesClosed++;
+ this._listeners.forEach(
+ function(func) {
+ func({target: fakeWindow.document});
+ });
+ },
+ document: {
+ get defaultView() { return fakeWindow; }
+ }
+ };
+
+ let loader = Loader(module);
+ loader.require("window-utils").closeOnUnload(fakeWindow);
+ test.assertEqual(fakeWindow._listeners.length, 1,
+ "unload listener added on closeOnUnload()");
+ test.assertEqual(timesClosed, 0,
+ "window not closed when registered.");
+ loader.require("unload").send();
+ test.assertEqual(timesClosed, 1,
+ "window closed on module unload.");
+ test.assertEqual(fakeWindow._listeners.length, 0,
+ "unload event listener removed on module unload");
+
+ timesClosed = 0;
+ loader.require("window-utils").closeOnUnload(fakeWindow);
+ test.assertEqual(timesClosed, 0,
+ "window not closed when registered.");
+ fakeWindow.close();
+ test.assertEqual(timesClosed, 1,
+ "window closed when close() called.");
+ test.assertEqual(fakeWindow._listeners.length, 0,
+ "unload event listener removed on window close");
+ loader.require("unload").send();
+ test.assertEqual(timesClosed, 1,
+ "window not closed again on module unload.");
+ loader.unload();
+};
+
+exports.testWindowWatcher = function(test) {
+ var myWindow;
+ var finished = false;
+
+ var delegate = {
+ onTrack: function(window) {
+ if (window == myWindow) {
+ test.pass("onTrack() called with our test window");
+ timer.setTimeout(function() { myWindow.close(); }, 1);
+ }
+ },
+ onUntrack: function(window) {
+ if (window == myWindow) {
+ test.pass("onUntrack() called with our test window");
+ timer.setTimeout(function() {
+ if (!finished) {
+ finished = true;
+ myWindow = null;
+ wt.unload();
+ test.done();
+ } else
+ test.fail("finishTest() called multiple times.");
+ }, 1);
+ }
+ }
+ };
+
+ var wt = new windowUtils.WindowTracker(delegate);
+ myWindow = makeEmptyWindow();
+ test.waitUntilDone(5000);
+};
+
+// test that _unregWindow calls _unregLoadingWindow
+exports.testWindowWatcherUnregs4LoadingWindows = function(test) {
+ var myWindow;
+ var finished = false;
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser");
+ var counter = 0;
+
+ var delegate = {
+ onTrack: function(window) {
+ var type = window.document.documentElement.getAttribute("windowtype");
+ if (type == "test:window")
+ test.fail("onTrack shouldn't have been executed.");
+ }
+ };
+ var wt = new windowUtils.WindowTracker(delegate);
+
+ // make a new window
+ myWindow = makeEmptyWindow();
+
+ // make sure that the window hasn't loaded yet
+ test.assertNotEqual(
+ myWindow.document.readyState,
+ "complete",
+ "window hasn't loaded yet.");
+
+ // unload WindowTracker
+ wt.unload();
+
+ // make sure that the window still hasn't loaded, which means that the onTrack
+ // would have been removed successfully assuming that it doesn't execute.
+ test.assertNotEqual(
+ myWindow.document.readyState,
+ "complete",
+ "window still hasn't loaded yet.");
+
+ // wait for the window to load and then close it. onTrack wouldn't be called
+ // until the window loads, so we must let it load before closing it to be
+ // certain that onTrack was removed.
+ myWindow.addEventListener("load", function() {
+ // allow all of the load handles to execute before closing
+ myWindow.setTimeout(function() {
+ myWindow.addEventListener("unload", function() {
+ // once the window unloads test is done
+ test.done();
+ }, false);
+ myWindow.close();
+ }, 0);
+ }, false);
+
+ test.waitUntilDone(5000);
+}
+
+exports.testWindowWatcherWithoutUntracker = function(test) {
+ var myWindow;
+ var finished = false;
+
+ var delegate = {
+ onTrack: function(window) {
+ if (window == myWindow) {
+ test.pass("onTrack() called with our test window");
+ timer.setTimeout(function() {
+ myWindow.close();
+
+ if (!finished) {
+ finished = true;
+ myWindow = null;
+ wt.unload();
+ test.done();
+ } else {
+ test.fail("onTrack() called multiple times.");
+ }
+ }, 1);
+ }
+ }
+ };
+
+ var wt = new windowUtils.WindowTracker(delegate);
+ myWindow = makeEmptyWindow();
+ test.waitUntilDone(5000);
+};
+
+exports.testActiveWindow = function(test) {
+ test.waitUntilDone(5000);
+
+ let testRunnerWindow = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("test:runner");
+ let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser");
+
+ test.assertEqual(windowUtils.activeBrowserWindow, browserWindow,
+ "Browser window is the active browser window.");
+
+
+ let testSteps = [
+ function() {
+ windowUtils.activeWindow = browserWindow;
+ continueAfterFocus(browserWindow);
+ },
+ function() {
+ test.assertEqual(windowUtils.activeWindow, browserWindow,
+ "Correct active window [1]");
+ continueAfterFocus(windowUtils.activeWindow = testRunnerWindow);
+ },
+ function() {
+ test.assertEqual(windowUtils.activeWindow, testRunnerWindow,
+ "Correct active window [2]");
+ test.assertEqual(windowUtils.activeBrowserWindow, browserWindow,
+ "Correct active browser window [3]");
+ continueAfterFocus(windowUtils.activeWindow = browserWindow);
+ },
+ function() {
+ test.assertEqual(windowUtils.activeWindow, browserWindow,
+ "Correct active window [4]");
+ continueAfterFocus(windowUtils.activeWindow = testRunnerWindow);
+ },
+ function() {
+ test.assertEqual(windowUtils.activeWindow, testRunnerWindow,
+ "Correct active window [5]");
+ test.assertEqual(windowUtils.activeBrowserWindow, browserWindow,
+ "Correct active browser window [6]");
+ testRunnerWindow = null;
+ browserWindow = null;
+ test.done()
+ }
+ ];
+
+ let nextTest = function() {
+ let func = testSteps.shift();
+ if (func) {
+ func();
+ }
+ }
+
+ function continueAfterFocus(targetWindow) {
+
+ // Based on SimpleTest.waitForFocus
+ var fm = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+
+ var childTargetWindow = {};
+ fm.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
+ childTargetWindow = childTargetWindow.value;
+
+ var focusedChildWindow = {};
+ if (fm.activeWindow) {
+ fm.getFocusedElementForWindow(fm.activeWindow, true, focusedChildWindow);
+ focusedChildWindow = focusedChildWindow.value;
+ }
+
+ var focused = (focusedChildWindow == childTargetWindow);
+ if (focused) {
+ nextTest();
+ } else {
+ childTargetWindow.addEventListener("focus", function focusListener() {
+ childTargetWindow.removeEventListener("focus", focusListener, true);
+ nextTest();
+ }, true);
+ }
+
+ }
+
+ nextTest();
+}
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-xhr.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-xhr.js
new file mode 100644
index 0000000..2912139
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-xhr.js
@@ -0,0 +1,70 @@
+var xhr = require("xhr");
+var timer = require("timer");
+var { Loader } = require("./helpers");
+
+/* Test is intentionally disabled until platform bug 707256 is fixed.
+exports.testAbortedXhr = function(test) {
+ var req = new xhr.XMLHttpRequest();
+ test.assertEqual(xhr.getRequestCount(), 1);
+ req.abort();
+ test.assertEqual(xhr.getRequestCount(), 0);
+};
+*/
+
+exports.testLocalXhr = function(test) {
+ var req = new xhr.XMLHttpRequest();
+ req.overrideMimeType("text/plain");
+ req.open("GET", module.uri);
+ req.onreadystatechange = function() {
+ if (req.readyState == 4 && req.status == 0) {
+ test.assertMatches(req.responseText,
+ /onreadystatechange/,
+ "XMLHttpRequest should get local files");
+ timer.setTimeout(
+ function() { test.assertEqual(xhr.getRequestCount(), 0);
+ test.done(); },
+ 0
+ );
+ }
+ };
+ req.send(null);
+ test.assertEqual(xhr.getRequestCount(), 1);
+ test.waitUntilDone(4000);
+};
+
+exports.testUnload = function(test) {
+ var loader = Loader(module);
+ var sbxhr = loader.require("xhr");
+ var req = new sbxhr.XMLHttpRequest();
+ req.overrideMimeType("text/plain");
+ req.open("GET", module.uri);
+ req.send(null);
+ test.assertEqual(sbxhr.getRequestCount(), 1);
+ loader.unload();
+ test.assertEqual(sbxhr.getRequestCount(), 0);
+};
+
+exports.testDelegatedReturns = function(test) {
+ var req = new xhr.XMLHttpRequest();
+ req.overrideMimeType("text/plain");
+ req.open("GET", module.uri);
+ req.onreadystatechange = function() {
+ if (req.readyState == 4 && req.status == 0) {
+ // This response isn't going to have any headers, so the return value
+ // should be null. Previously it wasn't returning anything, and thus was
+ // undefined.
+
+ // Depending on whether Bug 608939 has been applied
+ // to the platform, getAllResponseHeaders() may return
+ // null or the empty string; accept either.
+ var headers = req.getAllResponseHeaders();
+ test.assert(headers === null || headers === "",
+ "XHR's delegated methods should return");
+ test.done();
+ }
+ };
+ req.send(null);
+ test.assertEqual(xhr.getRequestCount(), 1);
+ test.waitUntilDone(4000);
+}
+
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-xpcom.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-xpcom.js
new file mode 100644
index 0000000..fdcb6e8
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-xpcom.js
@@ -0,0 +1,107 @@
+var traceback = require("traceback");
+var xpcom = require("xpcom");
+var {Cc,Ci,Cm,Cr} = require("chrome");
+var { Loader } = require("./helpers");
+
+exports.testRegister = function(test, text) {
+ if (!text)
+ text = "hai2u";
+
+ function Component() {}
+
+ Component.prototype = {
+ newChannel : function(aURI) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var channel = ios.newChannel(
+ "data:text/plain," + text,
+ null,
+ null
+ );
+
+ channel.originalURI = aURI;
+ return channel;
+ },
+ getURIFlags: function(aURI) {
+ return Ci.nsIAboutModule.ALLOW_SCRIPT;
+ },
+ QueryInterface: xpcom.utils.generateQI([Ci.nsIAboutModule])
+ };
+
+ var contractID = "@mozilla.org/network/protocol/about;1?what=boop";
+
+ var factory = xpcom.register({name: "test about:boop page",
+ contractID: contractID,
+ create: Component});
+
+ var manager = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ test.assertEqual(manager.isCIDRegistered(factory.uuid), true);
+
+ // We don't want to use Cc[contractID] here because it's immutable,
+ // so it can't accept updated versions of a contractID during the
+ // same application session.
+ var aboutFactory = xpcom.getClass(contractID, Ci.nsIFactory);
+
+ test.assertNotEqual(aboutFactory.wrappedJSObject,
+ undefined,
+ "Factory wrappedJSObject should exist.");
+
+ var about = aboutFactory.createInstance(null, Ci.nsIAboutModule);
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ test.assertEqual(
+ about.getURIFlags(ios.newURI("http://foo.com", null, null)),
+ Ci.nsIAboutModule.ALLOW_SCRIPT
+ );
+
+ var aboutURI = ios.newURI("about:boop", null, null);
+ var channel = ios.newChannelFromURI(aboutURI);
+ var iStream = channel.open();
+ var siStream = Cc['@mozilla.org/scriptableinputstream;1']
+ .createInstance(Ci.nsIScriptableInputStream);
+ siStream.init(iStream);
+ var data = new String();
+ data += siStream.read(-1);
+ siStream.close();
+ iStream.close();
+ test.assertEqual(data, text);
+
+ factory.unregister();
+ test.assertEqual(manager.isCIDRegistered(factory.uuid), false);
+};
+
+exports.testReRegister = function(test) {
+ exports.testRegister(test, "hai2u again");
+};
+
+exports.testMakeUuid = function(test) {
+ var first = xpcom.makeUuid().toString();
+ var second = xpcom.makeUuid().toString();
+ test.assertMatches(first, /{[0-9a-f\-]+}/);
+ test.assertMatches(second, /{[0-9a-f\-]+}/);
+ test.assertNotEqual(first, second);
+};
+
+exports.testUnload = function(test) {
+ var loader = Loader(module);
+ var sbxpcom = loader.require("xpcom");
+
+ function Component() {}
+
+ Component.prototype = {
+ QueryInterface: sbxpcom.utils.generateQI([Ci.nsISupports])
+ };
+
+ var contractID = "@mozilla.org/blargle;1";
+ var factory = sbxpcom.register({name: "test component",
+ contractID: contractID,
+ create: Component});
+
+ var manager = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ test.assertEqual(manager.isCIDRegistered(factory.uuid), true);
+
+ loader.unload();
+
+ test.assertEqual(manager.isCIDRegistered(factory.uuid), false);
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/test-xul-app.js b/tools/addon-sdk-1.4/packages/api-utils/tests/test-xul-app.js
new file mode 100644
index 0000000..cda4a2e
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/test-xul-app.js
@@ -0,0 +1,41 @@
+var xulApp = require("xul-app");
+
+exports.testXulApp = function(test) {
+ test.assertEqual(typeof(xulApp.ID), "string",
+ "ID is a string");
+ test.assertEqual(typeof(xulApp.name), "string",
+ "name is a string");
+ test.assertEqual(typeof(xulApp.version), "string",
+ "version is a string");
+ test.assertEqual(typeof(xulApp.platformVersion), "string",
+ "platformVersion is a string");
+
+ test.assertRaises(function() { xulApp.is("blargy"); },
+ "Unkown Mozilla Application: blargy",
+ "is() throws error on bad app name");
+ test.assertRaises(function() { xulApp.isOneOf(["blargy"]); },
+ "Unkown Mozilla Application: blargy",
+ "isOneOf() throws error on bad app name");
+
+ function testSupport(name) {
+ var item = xulApp.is(name);
+ test.assert(item === true || item === false,
+ "is('" + name + "') is true or false.");
+ }
+
+ var apps = ["Firefox", "Mozilla", "Sunbird", "SeaMonkey",
+ "Fennec", "Thunderbird"];
+
+ apps.forEach(function(name) { testSupport(name); });
+
+ test.assert(xulApp.isOneOf(apps) == true ||
+ xulApp.isOneOf(apps) == false,
+ "isOneOf() returns true or false.");
+
+ test.assertEqual(xulApp.versionInRange(xulApp.platformVersion, "1.9", "*"),
+ true, "platformVersion in range [1.9, *)");
+ test.assertEqual(xulApp.versionInRange("3.6.4", "3.6.4", "3.6.*"),
+ true, "3.6.4 in [3.6.4, 3.6.*)");
+ test.assertEqual(xulApp.versionInRange("1.9.3", "1.9.2", "1.9.3"),
+ false, "1.9.3 not in [1.9.2, 1.9.3)");
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/traits/assert.js b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/assert.js
new file mode 100644
index 0000000..dd662a4
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/assert.js
@@ -0,0 +1,94 @@
+"use strict";
+
+var BaseAssert = require("test/assert").Assert;
+
+/**
+ * Whether or not given property descriptors are equivalent. They are
+ * equivalent either if both are marked as "conflict" or "required" property
+ * or if all the properties of descriptors are equal.
+ * @param {Object} actual
+ * @param {Object} expected
+ */
+function equivalentDescriptors(actual, expected) {
+ return (actual.conflict && expected.conflict) ||
+ (actual.required && expected.required) ||
+ equalDescriptors(actual, expected);
+}
+
+function equalDescriptors(actual, expected) {
+ return actual.get === expected.get &&
+ actual.set === expected.set &&
+ actual.value === expected.value &&
+ !!actual.enumerable === !!expected.enumerable &&
+ !!actual.configurable === !!expected.configurable &&
+ !!actual.writable === !!expected.writable;
+}
+
+/**
+ * Whether or not given `target` array contains all the element
+ * from a given `source` array.
+ */
+function containsSet(source, target) {
+ return source.some(function(element) {
+ return 0 > target.indexOf(element);
+ });
+}
+
+/**
+ * Whether or not given two arrays contain all elements from another.
+ */
+function equivalentSets(source, target) {
+ return containsSet(source, target) && containsSet(target, source);
+}
+
+/**
+ * Finds name of the property from `source` property descriptor map, that
+ * is not equivalent of the name named property in the `target` property
+ * descriptor map. If not found `null` is returned instead.
+ */
+function findNonEquivalentPropertyName(source, target) {
+ var value = null;
+ Object.getOwnPropertyNames(source).some(function(key) {
+ var areEquivalent = false;
+ if (!equivalentDescriptors(source[key], target[key])) {
+ value = key;
+ areEquivalent = true;
+ }
+ return areEquivalent;
+ });
+ return value;
+}
+
+var AssertDescriptor = {
+ equalTraits: {
+ value: function equivalentTraits(actual, expected, message) {
+ var difference;
+ var actualKeys = Object.getOwnPropertyNames(actual);
+ var expectedKeys = Object.getOwnPropertyNames(expected);
+
+ if (equivalentSets(actualKeys, expectedKeys)) {
+ this.fail({
+ operator: "equalTraits",
+ message: "Traits define different properties",
+ actual: actualKeys.sort().join(","),
+ expected: expectedKeys.sort().join(","),
+ });
+ }
+ else if ((difference = findNonEquivalentPropertyName(actual, expected))) {
+ this.fail({
+ operator: "equalTraits",
+ message: "Traits define non-equivalent property `" + difference + "`",
+ actual: actual[difference],
+ expected: expected[difference]
+ });
+ }
+ else {
+ this.pass(message || "Traits are equivalent.");
+ }
+ }
+ }
+};
+
+exports.Assert = function Assert() {
+ return Object.create(BaseAssert.apply(null, arguments), AssertDescriptor);
+};
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/traits/descriptor-tests.js b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/descriptor-tests.js
new file mode 100644
index 0000000..7c27ac4
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/descriptor-tests.js
@@ -0,0 +1,331 @@
+"use strict";
+
+var Trait = require("light-traits").Trait;
+var utils = require("./utils");
+var Data = utils.Data;
+var Method = utils.Method;
+var Accessor = utils.Accessor;
+var Required = utils.Required;
+var Conflict = utils.Conflict;
+
+function method() {}
+
+exports.Assert = require("./assert").Assert
+exports["test simple composition"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: 0, b: 1 }),
+ { c: { value: 2 }, d: { value: method, enumerable: true } }
+ );
+
+ var expected = {
+ a: Data(0),
+ b: Data(1),
+ c: Data(2, false, false, false),
+ d: Method(method, true, false, false)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition with conflict"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: 0, b: 1 }),
+ {
+ a: {
+ value: 2,
+ writable: true,
+ configurable: true,
+ enumerable: true
+ },
+ c: {
+ value: method,
+ configurable: true
+ }
+ }
+ );
+
+ var expected = {
+ a: Conflict("a"),
+ b: Data(1),
+ c: Method(method, false, true, false)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test identical props does not cause conflict"] = function(assert) {
+ var actual = Trait.compose(
+ {
+ a: {
+ value: 0,
+ writable: true,
+ configurable: true,
+ enumerable: true
+ },
+ b: {
+ value: 1
+ }
+ },
+ Trait({
+ a: 0,
+ c: method
+ })
+ );
+
+ var expected = {
+ a: Data(0),
+ b: Data(1, false, false, false),
+ c: Method(method)
+ }
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition with identical required props"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: Trait.required, b: 1 }),
+ { a: { required: true }, c: { value: method } }
+ );
+
+ var expected = {
+ a: Required(),
+ b: Data(1),
+ c: Method(method, false, false, false)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition satisfying a required prop"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: Trait.required, b: 1 }),
+ { a: { value: method, enumerable: true } }
+ );
+
+ var expected = {
+ a: Method(method, true, false, false),
+ b: Data(1)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is neutral wrt conflicts"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: { value: 1 } }, Trait({ a: 2 })),
+ { b: { value: 0, writable: true, configurable: true, enumerable: false } }
+ );
+
+ var expected = { a: Conflict("a"), b: Data(0, false) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test conflicting prop overrides Trait.required"] = function(assert) {
+ var actual = Trait.compose(
+ Trait.compose(
+ Trait({ a: 1 }),
+ { a: { value: 2 } }
+ ),
+ { a: { value: Trait.required } }
+ );
+
+ var expected = { a: Conflict("a") };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is commutative"] = function(assert) {
+ var actual = Trait.compose(
+ Trait({ a: 0, b: 1 }),
+ { c: { value: 2 }, d: { value: method } }
+ );
+
+ var expected = Trait.compose(
+ { c: { value: 2 }, d: { value: method } },
+ Trait({ a: 0, b: 1 })
+ );
+
+ assert.equalTraits(actual, expected);
+}
+
+exports["test compose is commutative, also for required/conflicting props"] = function(assert) {
+ var actual = Trait.compose(
+ {
+ a: { value: 0 },
+ b: { value: 1 },
+ c: { value: 3 },
+ e: { value: Trait.required }
+ },
+ {
+ c: { value: 2 },
+ d: { get: method }
+ }
+ );
+
+ var expected = Trait.compose(
+ Trait({ c: 3 }),
+ {
+ c: { value: 2 },
+ d: { get: method },
+ a: { value: 0 },
+ b: { value: 1 },
+ e: { value: Trait.required },
+ }
+ );
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is associative"] = function(assert) {
+ var actual = Trait.compose(
+ {
+ a: { value: 0 },
+ b: { value: 1 },
+ c: { value: 3 },
+ d: { value: Trait.required }
+ },
+ Trait.compose(
+ { c: { value: 3 }, d: { value: Trait.required } },
+ { c: { value: 2 }, d: { value: method }, e: { value: "foo" } }
+ )
+ );
+
+ var expected = Trait.compose(
+ Trait.compose(
+ {
+ a: { value: 0 },
+ b: { value: 1 },
+ c: { value: 3 },
+ d: { value: Trait.required }
+ },
+ {
+ c: { value: 3 },
+ d: { value: Trait.required }
+ }
+ ),
+ {
+ c: { value: 2 },
+ d: { value: method },
+ e: { value: "foo" }
+ }
+ );
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test diamond import of same prop do not conflict"] = function(assert) {
+ var actual = Trait.compose(
+ Trait.compose(
+ { b: { value: 2 } },
+ { a: { value: 1, enumerable: true, configurable: true, writable: true } }
+ ),
+ Trait.compose(
+ { c: { value: 3 } },
+ Trait({ a: 1 })
+ ),
+ Trait({ d: 4 })
+ );
+
+ var expected = {
+ a: Data(1),
+ b: Data(2, false, false, false),
+ c: Data(3, false, false, false),
+ d: Data(4)
+ };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test create simple"] = function(assert) {
+ var o1 = Trait.compose(
+ Trait({ a: 1 }),
+ {
+ b: {
+ value: function() {
+ return this.a;
+ }
+ }
+ }
+ ).create(Object.prototype);
+
+ assert.equal(Object.getPrototypeOf(o1), Object.prototype, "o1 prototype");
+ assert.equal(1, o1.a, "o1.a");
+ assert.equal(1, o1.b(), "o1.b()");
+ assert.equal(Object.keys(o1).length, 1, "Object.keys(o1).length === 2");
+};
+
+exports["test create with Array.prototype"] = function(assert) {
+ var o2 = Trait.compose({}, {}).create(Array.prototype);
+ assert.equal(Object.getPrototypeOf(o2), Array.prototype, "o2 prototype");
+};
+
+exports["test exception for incomplete required properties"] = function(assert) {
+ assert.throws(function() {
+ Trait({ foo: Trait.required }).create(Object.prototype)
+ }, /Missing required property: `foo`/, "required prop error");
+}
+
+exports["test exception for unresolved conflicts"] = function(assert) {
+ assert.throws(function() {
+ Trait(Trait({ a: 0 }), Trait({ a: 1 })).create({})
+ }, /Remaining conflicting property: `a`/, "conflicting prop error");
+}
+
+exports["test conflicting properties are present"] = function(assert) {
+ var o5 = Object.create(Object.prototype, Trait.compose(
+ { a: { value: 0 } },
+ { a: { value: 1 } }
+ ));
+
+ assert.ok("a" in o5, "conflicting property present");
+ assert.throws(function() {
+ o5.a
+ }, /Remaining conflicting property: `a`/, "conflicting prop access error");
+};
+
+exports["test diamond with conflicts"] = function(assert) {
+ function makeT1(x) {
+ return {
+ m: {
+ value: function() {
+ return x
+ }
+ }
+ };
+ };
+
+ function makeT2(x) {
+ return Trait.compose(
+ Trait({ t2: "foo" }),
+ makeT1(x)
+ );
+ };
+
+ function makeT3(x) {
+ return Trait.compose(
+ {
+ t3: { value: "bar" }
+ },
+ makeT1(x)
+ );
+ };
+
+ var T4 = Trait.compose(makeT2(5), makeT3(5));
+
+ assert.throws(function() {
+ T4.create(Object.prototype);
+ }, /Remaining conflicting property: `m`/, "diamond prop conflict");
+};
+
+exports["test providing requirements through proto"] = function(assert) {
+ var t = Trait.compose(
+ {},
+ { required: { required: true } }
+ ).create({ required: "test" });
+
+ assert.equal(t.required, "test", "property from proto is inherited");
+};
+
+if (module == require.main)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/traits/inheritance-tests.js b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/inheritance-tests.js
new file mode 100644
index 0000000..73a23b7
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/inheritance-tests.js
@@ -0,0 +1,100 @@
+"use strict";
+
+var Trait = require("light-traits").Trait;
+
+exports["test custom constructor and inherited toString"] = function(assert) {
+ function Type() {
+ return Object.create(Type.prototype);
+ }
+ Type.prototype = Trait({
+ method: function method() {
+ return 2;
+ }
+ }).create(Object.freeze(Type.prototype));
+
+ var fixture = Type();
+
+ assert.equal(fixture.constructor, Type, "must override constructor");
+ assert.equal(fixture.toString(), "[object Type]", "must inherit toString");
+};
+
+exports["test custom toString and inherited constructor"] = function(assert) {
+ function Type() {
+ return Object.create(Type.prototype);
+ }
+ Type.prototype = Trait({
+ toString: function toString() {
+ return "<toString>";
+ }
+ }).create();
+
+ var fixture = Type();
+
+ assert.equal(fixture.constructor, Trait, "must inherit constructor Trait");
+ assert.equal(fixture.toString(), "<toString>", "Must override toString");
+};
+
+exports["test custom toString and constructor"] = function(assert) {
+ function Type() {
+ return TypeTrait.create(Type.prototype);
+ }
+ Object.freeze(Type.prototype);
+ var TypeTrait = Trait({
+ toString: function toString() {
+ return "<toString>";
+ }
+ });
+
+ var fixture = Type();
+
+ assert.equal(fixture.constructor, Type, "constructor is provided to create");
+ assert.equal(fixture.toString(), "<toString>", "toString was overridden");
+};
+
+exports["test resolve constructor"] = function (assert) {
+ function Type() {}
+ var T1 = Trait({ constructor: Type }).resolve({ constructor: '_foo' });
+ var f1 = T1.create();
+
+ assert.equal(f1._foo, Type, "constructor was resolved");
+ assert.equal(f1.constructor, Trait, "constructor of prototype is inherited");
+ assert.equal(f1.toString(), "[object Trait]", "toString is inherited");
+};
+
+exports["test compose read-only"] = function (assert) {
+ function Type() {}
+ Type.prototype = Trait.compose(Trait({}), {
+ constructor: { value: Type },
+ a: { value: "b", enumerable: true }
+ }).resolve({ a: "b" }).create({ a: "a" });
+
+ var f1 = new Type();
+
+ assert.equal(Object.getPrototypeOf(f1), Type.prototype, "inherits correctly");
+ assert.equal(f1.constructor, Type, "constructor was overridden");
+ assert.equal(f1.toString(), "[object Type]", "toString was inherited");
+ assert.equal(f1.a, "a", "property a was resolved");
+ assert.equal(f1.b, "b", "property a was renamed to b");
+ assert.ok(!Object.getOwnPropertyDescriptor(Type.prototype, "a"),
+ "a is not on the prototype of the instance");
+
+ var proto = Object.getPrototypeOf(Type.prototype);
+ var dc = Object.getOwnPropertyDescriptor(Type.prototype, "constructor");
+ var db = Object.getOwnPropertyDescriptor(Type.prototype, "b");
+ var da = Object.getOwnPropertyDescriptor(proto, "a");
+
+ assert.ok(!dc.writable, "constructor is not writable");
+ assert.ok(!dc.enumerable, "constructor is not enumerable");
+ assert.ok(dc.configurable, "constructor inherits configurability");
+
+ assert.ok(!db.writable, "a -> b is not writable");
+ assert.ok(db.enumerable, "a -> b is enumerable");
+ assert.ok(!db.configurable, "a -> b is not configurable");
+
+ assert.ok(da.writable, "a is writable");
+ assert.ok(da.enumerable, "a is enumerable");
+ assert.ok(da.configurable, "a is configurable");
+};
+
+if (require.main == module)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/traits/object-tests.js b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/object-tests.js
new file mode 100644
index 0000000..afea3ce
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/object-tests.js
@@ -0,0 +1,317 @@
+"use strict";
+
+var Trait = require("light-traits").Trait;
+var utils = require("./utils");
+var Data = utils.Data;
+var Method = utils.Method;
+var Accessor = utils.Accessor;
+var Required = utils.Required;
+var Conflict = utils.Conflict;
+
+function method() {}
+
+exports.Assert = require("./assert").Assert;
+
+exports["test empty trait"] = function (assert) {
+ assert.equalTraits(Trait({}), {});
+};
+
+exports["test simple trait"] = function (assert) {
+ var expected = {
+ a: Data(0, true, true, true),
+ b: Method(method, true, true, true)
+ };
+
+ assert.equalTraits(Trait({ a: 0, b: method }), expected);
+};
+
+exports["test simple trait with Trait.required property"] = function (assert) {
+ var actual = Trait({ a: Trait.required, b: 1 });
+ var expected = { a: Required("a"), b: Data(1) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test ordering of trait properties is irrelevant"] = function (assert) {
+ var actual = Trait({ a: 0, b: 1, c: Trait.required });
+ var expected = Trait({ b: 1, c: Trait.required, a: 0 });
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test trait with accessor property"] = function (assert) {
+ var record = { get a() {}, set a(v) {} };
+ var get = Object.getOwnPropertyDescriptor(record, "a").get;
+ var set = Object.getOwnPropertyDescriptor(record, "a").set;
+
+ assert.equalTraits(Trait(record), { a: Accessor(get, set) });
+};
+
+exports["test simple composition"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ c: 2, d: method }));
+ var expected = { a: Data(0), b: Data(1), c: Data(2), d: Method(method) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition with conflict"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ a: 2, c: method }));
+ var expected = { a: Conflict("a"), b: Data(1), c: Method(method) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test composition of identical props does not cause conflict"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ a: 0, c: method }));
+
+ assert.equalTraits(actual, { a: Data(0), b: Data(1), c: Method(method) });
+};
+
+exports["test composition with identical Trait.required props"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: Trait.required, b: 1 }),
+ Trait({ a: Trait.required, c: method }));
+
+ assert.equalTraits(actual, { a: Required(), b: Data(1), c: Method(method) });
+};
+
+exports["test composition satisfying a Trait.required prop"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: Trait.required, b: 1 }),
+ Trait({ a: method }));
+
+ assert.equalTraits(actual, { a: Method(method), b: Data(1) });
+};
+
+exports["test compose is neutral wrt conflicts"] = function (assert) {
+ var actual = Trait.compose(Trait.compose(Trait({ a: 1 }), Trait({ a: 2 })),
+ Trait({ b: 0 }));
+
+ assert.equalTraits(actual, { a: Conflict("a"), b: Data(0) });
+};
+
+exports["test conflicting prop overrides Trait.required prop"] = function (assert) {
+ var actual = Trait.compose(Trait.compose(Trait({ a: 1 }),
+ Trait({ a: 2 })),
+ Trait({ a: Trait.required }));
+
+ assert.equalTraits(actual, { a: Conflict("a") });
+};
+
+exports["test compose is commutative"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ c: 2, d: method }));
+ var expected = Trait.compose(Trait({ c: 2, d: method }),
+ Trait({ a: 0, b: 1 }));
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is commutative, also for Trait.required/conflicting props"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1, c: 3, e: Trait.required }),
+ Trait({ c: 2, d: method }));
+
+ var expected = Trait.compose(Trait({ c: 2, d: method }),
+ Trait({ a: 0, b: 1, c: 3, e: Trait.required }));
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test compose is associative"] = function (assert) {
+ var actual = Trait.compose(Trait({ a: 0, b: 1, c: 3, d: Trait.required }),
+ Trait.compose(Trait({ c: 3, d: Trait.required }),
+ Trait({ c: 2, d: method,
+ e: "foo" })));
+
+ var expected = Trait.compose(
+ Trait.compose(Trait({ a: 0, b: 1, c: 3, d: Trait.required }),
+ Trait({ c: 3, d: Trait.required })),
+ Trait({ c: 2, d: method, e: "foo" }));
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test diamond import of same prop does not generate conflict"] = function (assert) {
+ var actual = Trait.compose(Trait.compose(Trait({ b: 2 }), Trait({ a: 1 })),
+ Trait.compose(Trait({ c: 3 }), Trait({ a: 1 })),
+ Trait({ d: 4 }));
+ var expected = { a: Data(1), b: Data(2), c: Data(3), d: Data(4) };
+
+ assert.equalTraits(actual, expected);
+};
+
+exports["test resolve with empty resolutions has no effect"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: Trait.required, c: method }).resolve({}),
+ { a: Data(1), b: Required(), c: Method(method) });
+};
+
+exports["test resolve: renaming"] = function (assert) {
+ var actual = Trait({ a: 1, b: Trait.required, c: method });
+
+ assert.equalTraits(actual.resolve({ a: "A", c: "C" }),
+ { A: Data(1), b: Required(), C: Method(method),
+ a: Required(), c: Required() });
+};
+
+exports["test resolve: renaming to conflicting name causes conflict, order 1"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "b" }),
+ { b: Conflict("b"), a: Required() });
+};
+
+exports["test resolve: renaming to conflicting name causes conflict, order 2"] = function (assert) {
+ assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ a: "b" }),
+ { b: Conflict("b"), a: Required() });
+};
+
+exports["test resolve: simple exclusion"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined }),
+ { a: Required(), b: Data(2) });
+};
+
+exports["test resolve: exclusion to empty trait"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: null, b: undefined }),
+ { a: Required(), b: Required() });
+};
+
+exports["test resolve: exclusion and renaming of disjoint props"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined, b: "c" }),
+ { a: Required(), c: Data(2), b: Required() });
+};
+
+exports["test resolve: exclusion and renaming of overlapping props"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined, b: "a" }),
+ { a: Data(2), b: Required() });
+};
+
+exports["test resolve: renaming to a common alias causes conflict"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "c", b: "c" }),
+ { c: Conflict("c"), a: Required(), b: Required() });
+};
+
+exports["test resolve: renaming overrides Trait.required target"] = function (assert) {
+ assert.equalTraits(Trait({ a: Trait.required, b: 2 }).resolve({ b: "a" }),
+ { a: Data(2), b: Required() });
+};
+
+exports["test resolve: renaming Trait.required properties has no effect"] = function (assert) {
+ assert.equalTraits(Trait({ a: 2, b: Trait.required }).resolve({ b: "a" }),
+ { a: Data(2), b: Required() });
+};
+
+exports["test resolve: renaming of non-existent props has no effect"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "c", d: "c" }),
+ { c: Data(1), b: Data(2), a: Required() });
+};
+
+exports["test resolve: exclusion of non-existent props has no effect"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1 }).resolve({ b: undefined }), { a: Data(1) });
+};
+
+exports["test resolve is neutral w.r.t. Trait.required properties"] = function (assert) {
+ var actual = Trait({ a: Trait.required, b: Trait.required, c: "foo", d: 1 });
+ var expected = { a: Required(), b: Required(), c: Data("foo"), d: Data(1) };
+ assert.equalTraits(actual.resolve({ a: "c", b: undefined }), expected);
+};
+
+exports["test resolve supports swapping of property names, ordering 1"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "b", b: "a" }),
+ { a: Data(2), b: Data(1) });
+};
+
+exports["test resolve supports swapping of property names, ordering 2"] = function (assert) {
+ assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ b: "a", a: "b" }),
+ { a: Data(2), b: Data(1) });
+};
+
+exports["test resolve supports swapping of property names, ordering 3"] = function (assert) {
+ assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ b: "a", a: "b" }),
+ { a: Data(2), b: Data(1) });
+};
+
+exports["test resolve supports swapping of property names, ordering 4"] = function (assert) {
+ assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ a: "b", b: "a" }),
+ { a: Data(2), b: Data(1) });
+};
+
+exports["test create simple"] = function (assert) {
+ var o1 = Trait({
+ a: 1,
+ b: function () {
+ return this.a;
+ }
+ }).create(Object.prototype);
+
+ assert.equal(Object.getPrototypeOf(o1), Object.prototype, "o1 prototype");
+ assert.equal(1, o1.a, "o1.a");
+ assert.equal(1, o1.b(), "o1.b()");
+ assert.equal(Object.keys(o1).length, 2, "Object.keys(o1).length === 2");
+};
+
+exports["test create with Array.prototype"] = function (assert) {
+ var o2 = Trait({}).create(Array.prototype);
+ assert.equal(Object.getPrototypeOf(o2), Array.prototype, "o2 prototype");
+};
+
+exports["test exception for incomplete required properties"] = function (assert) {
+ assert.throws(function () {
+ Trait({ foo: Trait.required }).create(Object.prototype);
+ }, /Missing required property: `foo`/, "required prop error");
+};
+
+exports["test exception for unresolved conflicts"] = function (assert) {
+ assert.throws(function () {
+ Trait.compose(Trait({ a: 0 }), Trait({ a: 1 })).create({});
+ }, /Remaining conflicting property: `a`/, "conflicting prop error");
+};
+
+exports["test verify that required properties are present but undefined"] = function (assert) {
+ var o4 = Object.create(Object.prototype, Trait({ foo: Trait.required }));
+
+ assert.ok("foo" in o4, "required property present");
+ assert.throws(function () {
+ o4.foo;
+ }, /Missing required property: `foo`/, "required prop error");
+};
+
+exports["test verify that conflicting properties are present"] = function (assert) {
+ var o5 = Object.create(Object.prototype, Trait.compose(Trait({ a: 0 }),
+ Trait({ a: 1 })));
+
+ assert.ok("a" in o5, "conflicting property present");
+ assert.throws(function () {
+ o5.a;
+ }, /Remaining conflicting property: `a`/, "conflicting prop access error");
+};
+
+exports["test diamond with conflicts"] = function (assert) {
+ function makeT1(x) {
+ return Trait({
+ m: function () {
+ return x
+ }
+ })
+ };
+
+ function makeT2(x) {
+ return Trait.compose(Trait({
+ t2: "foo"
+ }), makeT1(x));
+ };
+
+ function makeT3(x) {
+ return Trait.compose(Trait({
+ t3: "bar"
+ }), makeT1(x));
+ };
+
+ var T4 = Trait.compose(makeT2(5), makeT3(5));
+
+ assert.throws(function () {
+ T4.create(Object.prototype);
+ }, /Remaining conflicting property: `m`/, "diamond prop conflict");
+};
+
+exports["test providing requirements through proto"] = function (assert) {
+ var t = Trait({ required: Trait.required }).create({ required: "test" });
+ assert.equal(t.required, "test", "property from proto is inherited");
+};
+
+if (module == require.main)
+ require("test").run(exports);
diff --git a/tools/addon-sdk-1.4/packages/api-utils/tests/traits/utils.js b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/utils.js
new file mode 100644
index 0000000..5647fb9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/api-utils/tests/traits/utils.js
@@ -0,0 +1,52 @@
+"use strict";
+
+var ERR_CONFLICT = "Remaining conflicting property: ";
+var ERR_REQUIRED = "Missing required property: ";
+
+exports.Data = function Data(value, enumerable, configurable, writable) {
+ return ({
+ value: value,
+ enumerable: enumerable !== false,
+ configurable: configurable !== false,
+ writable: writable !== false
+ });
+};
+
+exports.Method = function Method(method, enumerable, configurable, writable) {
+ return ({
+ value: method,
+ enumerable: enumerable !== false,
+ configurable: configurable !== false,
+ writable: writable !== false
+ });
+};
+
+exports.Accessor = function Accessor(get, set, enumerable, configurable) {
+ return ({
+ get: get,
+ set: set,
+ enumerable: enumerable !== false,
+ configurable: configurable !== false
+ });
+};
+
+exports.Required = function Required(name) {
+ function required() { throw new Error(ERR_REQUIRED + name) }
+
+ return ({
+ get: required,
+ set: required,
+ required: true
+ });
+};
+
+exports.Conflict = function Conflict(name) {
+ function conflict() { throw new Error(ERR_CONFLICT + name) }
+
+ return ({
+ get: conflict,
+ set: conflict,
+ conflict: true
+ });
+};
+
diff --git a/tools/addon-sdk-1.4/packages/development-mode/README.md b/tools/addon-sdk-1.4/packages/development-mode/README.md
new file mode 100644
index 0000000..af674d1
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/development-mode/README.md
@@ -0,0 +1,8 @@
+This package contains a program that pings a local server for
+information about other programs to run. If the same program
+is requested more than once, it is first unloaded before being run
+again. This can be very useful for developing add-ons without
+having to restart the parent application.
+
+In the future, functionality will be added that allows developers to
+debug and profile their programs' behavior.
diff --git a/tools/addon-sdk-1.4/packages/development-mode/docs/bootstrap.md b/tools/addon-sdk-1.4/packages/development-mode/docs/bootstrap.md
new file mode 100644
index 0000000..2e0156a
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/development-mode/docs/bootstrap.md
@@ -0,0 +1,6 @@
+This module contains functionality that allows a program to
+"bootstrap" other programs: that is, set up a runtime
+environment for them and execute them.
+
+At present, the functions defined by this module are
+implementation-specific and subject to change.
diff --git a/tools/addon-sdk-1.4/packages/development-mode/docs/main.md b/tools/addon-sdk-1.4/packages/development-mode/docs/main.md
new file mode 100644
index 0000000..1669c49
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/development-mode/docs/main.md
@@ -0,0 +1,5 @@
+This program simply pings a local "task queue" server using AJAX long
+polling. When a program needs to be run, this program obtains
+a JSON blob containing configuration information from the task queue
+server and uses it to bootstrap the program. This goes on
+indefinitely, until the application exits.
diff --git a/tools/addon-sdk-1.4/packages/development-mode/lib/bootstrap.js b/tools/addon-sdk-1.4/packages/development-mode/lib/bootstrap.js
new file mode 100644
index 0000000..6986c60
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/development-mode/lib/bootstrap.js
@@ -0,0 +1,153 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack SDK.
+ *
+ * The Initial Developer of the Original Code is
+ * Atul Varma <atul@mozilla.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+var {Cc,Cm,Ci,components} = require("chrome");
+var file = require("api-utils/file");
+
+// A global registry of all Jetpack Programs that we're responsible
+// for loading and unloading. Keys are XPCOM contract IDs of
+// the harness services of Jetpack Programs, values are the
+// wrappedJSObject of the harness services themselves.
+var gServices = {};
+
+var manager = Cm;
+manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+// Attempts to unload and then unregister the XPCOM component with the
+// given contract ID, if it is managed by us.
+function maybeUnload(contractID) {
+ if (contractID in gServices) {
+ try {
+ gServices[contractID].unload();
+ } catch (e) {
+ console.exception(e);
+ }
+ var classID = gServices[contractID].classID;
+ delete gServices[contractID];
+ maybeUnregister(contractID, classID);
+ }
+}
+
+// Attempts to unregister the XPCOM component with the given
+// contract ID and class ID, if it is managed by us.
+function maybeUnregister(contractID, classID) {
+ try {
+ var factory = manager.getClassObjectByContractID(contractID,
+ Ci.nsIFactory);
+ manager.unregisterFactory(classID, factory);
+ } catch (e) {
+ console.exception(e);
+ }
+}
+
+// A quit callable that is passed to the main() function of any
+// Jetpack Program we manage. Whenever said program quits, we will
+// automatically take care of unloading and unregistering it.
+function makeQuit(contractID) {
+ return function quit(status) {
+ maybeUnload(contractID);
+ };
+}
+
+function logError(e) {
+ console.exception(e);
+}
+
+function makeUnloader(contractID) {
+ return {unload: function unload() { maybeUnload(contractID); }};
+}
+
+// The main public function of this module; given a JSON harness options
+// blob and a root directory of where the Jetpack Program
+// is installed, takes care of loading the program, running it, and
+// unloading its resources when they're no longer needed.
+exports.run = function run(options, rootDirPath, dump) {
+ var harnessService;
+ var contractID = options.bootstrap.contractID;
+ var classID = components.ID(options.bootstrap.classID);
+
+ maybeUnload(contractID);
+ options.runImmediately = true;
+
+ var rootDir = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ rootDir.initWithPath(rootDirPath);
+
+ // Note that we're reusing our own bootstrapping code here, rather
+ // than directly invoking the target Jetpack Program's bootstrapping
+ // infrastructure. If one works differently than the other,
+ // we could have problems, but for now we'll assume that this
+ // bootstrapping infrastructure is stable.
+ var HarnessService = packaging.buildHarnessService(rootDir,
+ dump,
+ logError,
+ makeQuit(contractID),
+ options);
+ var factory = HarnessService.prototype._xpcom_factory;
+ var proto = HarnessService.prototype;
+ manager.registerFactory(proto.classID,
+ proto.classDescription,
+ proto.contractID,
+ factory);
+
+ try {
+ harnessService = factory.createInstance(null, Ci.nsISupports);
+ harnessService = harnessService.wrappedJSObject;
+ gServices[contractID] = harnessService;
+ harnessService.load();
+ return makeUnloader(contractID, classID);
+ } catch (e) {
+ console.exception(e);
+ return null;
+ }
+};
+
+// When this module is unloaded, shut down all currently-running
+// Jetpack Programs we manage and free their resources.
+require("unload").when(
+ function() {
+ var argLists = [];
+ for (contractID in gServices)
+ argLists.push([contractID, gServices[contractID].classID]);
+
+ argLists.forEach(
+ function(args) {
+ maybeUnload.apply(undefined, args);
+ });
+ });
diff --git a/tools/addon-sdk-1.4/packages/development-mode/lib/main.js b/tools/addon-sdk-1.4/packages/development-mode/lib/main.js
new file mode 100644
index 0000000..61854c9
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/development-mode/lib/main.js
@@ -0,0 +1,95 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack SDK.
+ *
+ * The Initial Developer of the Original Code is
+ * Atul Varma <atul@mozilla.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+var print;
+
+var {Cc,Ci} = require("chrome");
+var xhr = require("api-utils/xhr");
+
+// TODO: Eventually we should be able to e.g. require("os").environ
+// rather than access this XPCOM service directly.
+var environ = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+
+function runTask(options) {
+ require("./bootstrap").run(options, packaging.root.path, print);
+ processNextTask();
+}
+
+function processNextTask() {
+ var req = new xhr.XMLHttpRequest();
+ var port = environ.get("JETPACK_DEV_SERVER_PORT");
+ var url = "http://localhost:" + port + "/api/task-queue/get";
+ req.open("GET", url);
+ req.onreadystatechange = function() {
+ if (req.readyState == 4) {
+ if (req.status == 200) {
+ if (req.responseText) {
+ runTask(JSON.parse(req.responseText));
+ } else
+ processNextTask();
+ } else {
+ require("api-utils/timer").setTimeout(processNextTask, 1000);
+ }
+ }
+ };
+ req.send(null);
+}
+
+function makeMainWindow(quit) {
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIWindowWatcher);
+ var text = "Now in development mode. Close this window to exit.";
+ var window = ww.openWindow(null, "data:text/plain," + encodeURI(text),
+ "development-mode", "centerscreen", null);
+
+ window.addEventListener("close", function() quit("OK"), false);
+}
+
+exports.main = function(options, callbacks) {
+ var appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+
+ print = callbacks.print;
+ if (appInfo.ID == "xulapp@toolness.com")
+ // We're running barebones XULRunner, open a default window.
+ makeMainWindow(callbacks.quit);
+ console.log("Starting.");
+ processNextTask();
+};
diff --git a/tools/addon-sdk-1.4/packages/development-mode/package.json b/tools/addon-sdk-1.4/packages/development-mode/package.json
new file mode 100644
index 0000000..1dc71a3
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/development-mode/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "development-mode",
+ "description": "Adds Add-on SDK development functionality to a program.",
+ "keywords": ["jetpack-low-level"],
+ "author": "Atul Varma (http://toolness.com/)",
+ "license": "MPL 1.1/GPL 2.0/LGPL 2.1",
+ "main": "main",
+ "dependencies": ["api-utils", "test-harness"],
+ "id": "anonid0-development-mode"
+}
diff --git a/tools/addon-sdk-1.4/packages/test-harness/README.md b/tools/addon-sdk-1.4/packages/test-harness/README.md
new file mode 100644
index 0000000..a833d34
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/test-harness/README.md
@@ -0,0 +1,8 @@
+<span class="aside">
+For more information on testing in the Add-on SDK, see the
+[Reusable Modules](dev-guide/addon-development/implementing-reusable-module.html)
+tutorial.
+</span>
+
+This package contains a program that finds and runs tests. It is
+automatically used whenever the `cfx test` command is executed.
diff --git a/tools/addon-sdk-1.4/packages/test-harness/docs/harness.md b/tools/addon-sdk-1.4/packages/test-harness/docs/harness.md
new file mode 100644
index 0000000..9f6cd7b
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/test-harness/docs/harness.md
@@ -0,0 +1,2 @@
+This module contains the bulk of the test harness setup and execution
+implementation.
diff --git a/tools/addon-sdk-1.4/packages/test-harness/docs/run-tests.md b/tools/addon-sdk-1.4/packages/test-harness/docs/run-tests.md
new file mode 100644
index 0000000..38f4569
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/test-harness/docs/run-tests.md
@@ -0,0 +1,9 @@
+<span class="aside">
+For more information on testing in the Add-on SDK, see the
+[Reusable Modules](dev-guide/addon-development/implementing-reusable-module.html)
+tutorial.
+</span>
+
+This module contains the package's main program, which does a
+bit of high-level setup and then delegates test finding and running to
+the `harness` module.
diff --git a/tools/addon-sdk-1.4/packages/test-harness/lib/harness.js b/tools/addon-sdk-1.4/packages/test-harness/lib/harness.js
new file mode 100644
index 0000000..a5a9284
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/test-harness/lib/harness.js
@@ -0,0 +1,352 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const { Cc,Ci } = require("chrome");
+const { Loader } = require("@loader")
+
+var cService = Cc['@mozilla.org/consoleservice;1'].getService()
+ .QueryInterface(Ci.nsIConsoleService);
+
+// Cuddlefish loader for the sandbox in which we load and
+// execute tests.
+var sandbox;
+
+// Function to call when we're done running tests.
+var onDone;
+
+// Function to print text to a console, w/o CR at the end.
+var print;
+
+// How many more times to run all tests.
+var iterationsLeft;
+
+// Only tests in files whose names match this regexp filter will be run.
+var filter;
+
+// Whether to report memory profiling information.
+var profileMemory;
+
+// Combined information from all test runs.
+var results = {
+ passed: 0,
+ failed: 0,
+ testRuns: []
+};
+
+// JSON serialization of last memory usage stats; we keep it stringified
+// so we don't actually change the memory usage stats (in terms of objects)
+// of the JSRuntime we're profiling.
+var lastMemoryUsage;
+
+function analyzeRawProfilingData(data) {
+ var graph = data.graph;
+ var shapes = {};
+
+ // Convert keys in the graph from strings to ints.
+ // TODO: Can we get rid of this ridiculousness?
+ var newGraph = {};
+ for (id in graph) {
+ newGraph[parseInt(id)] = graph[id];
+ }
+ graph = newGraph;
+
+ var modules = 0;
+ var moduleIds = [];
+ var moduleObjs = {UNKNOWN: 0};
+ for (let name in data.namedObjects) {
+ moduleObjs[name] = 0;
+ moduleIds[data.namedObjects[name]] = name;
+ modules++;
+ }
+
+ var count = 0;
+ for (id in graph) {
+ var parent = graph[id].parent;
+ while (parent) {
+ if (parent in moduleIds) {
+ var name = moduleIds[parent];
+ moduleObjs[name]++;
+ break;
+ }
+ if (!(parent in graph)) {
+ moduleObjs.UNKNOWN++;
+ break;
+ }
+ parent = graph[parent].parent;
+ }
+ count++;
+ }
+
+ print("\nobject count is " + count + " in " + modules + " modules" +
+ " (" + data.totalObjectCount + " across entire JS runtime)\n");
+ if (lastMemoryUsage) {
+ var last = JSON.parse(lastMemoryUsage);
+ var diff = {
+ moduleObjs: dictDiff(last.moduleObjs, moduleObjs),
+ totalObjectClasses: dictDiff(last.totalObjectClasses,
+ data.totalObjectClasses)
+ };
+
+ for (let name in diff.moduleObjs)
+ print(" " + diff.moduleObjs[name] + " in " + name + "\n");
+ for (let name in diff.totalObjectClasses)
+ print(" " + diff.totalObjectClasses[name] + " instances of " +
+ name + "\n");
+ }
+ lastMemoryUsage = JSON.stringify(
+ {moduleObjs: moduleObjs,
+ totalObjectClasses: data.totalObjectClasses}
+ );
+}
+
+function dictDiff(last, curr) {
+ var diff = {};
+
+ for (let name in last) {
+ var result = (curr[name] || 0) - last[name];
+ if (result)
+ diff[name] = (result > 0 ? "+" : "") + result;
+ }
+ for (let name in curr) {
+ var result = curr[name] - (last[name] || 0);
+ if (result)
+ diff[name] = (result > 0 ? "+" : "") + result;
+ }
+ return diff;
+}
+
+function reportMemoryUsage() {
+ memory.gc();
+ sandbox.memory.gc();
+
+ var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+ .getService(Ci.nsIMemoryReporterManager);
+ var reporters = mgr.enumerateReporters();
+ if (reporters.hasMoreElements())
+ print("\n");
+ while (reporters.hasMoreElements()) {
+ var reporter = reporters.getNext();
+ reporter.QueryInterface(Ci.nsIMemoryReporter);
+ print(reporter.description + ": " + reporter.memoryUsed + "\n");
+ }
+
+ var weakrefs = [info.weakref.get()
+ for each (info in sandbox.memory.getObjects())];
+ weakrefs = [weakref for each (weakref in weakrefs) if (weakref)];
+ print("Tracked memory objects in testing sandbox: " +
+ weakrefs.length + "\n");
+}
+
+var gWeakrefInfo;
+
+function showResults() {
+ memory.gc();
+
+ if (gWeakrefInfo) {
+ gWeakrefInfo.forEach(
+ function(info) {
+ var ref = info.weakref.get();
+ if (ref !== null) {
+ var data = ref.__url__ ? ref.__url__ : ref;
+ var warning = data == "[object Object]"
+ ? "[object " + data.constructor.name + "(" +
+ [p for (p in data)].join(", ") + ")]"
+ : data;
+ console.warn("LEAK", warning, info.bin);
+ }
+ }
+ );
+ }
+
+ print("\n");
+ var total = results.passed + results.failed;
+ print(results.passed + " of " + total + " tests passed.\n");
+ onDone(results);
+}
+
+function cleanup() {
+ try {
+ for (let name in sandbox.modules)
+ sandbox.globals.memory.track(sandbox.modules[name],
+ "module global scope: " + name);
+ sandbox.globals.memory.track(sandbox, "Cuddlefish Loader");
+
+ if (profileMemory) {
+ gWeakrefInfo = [{ weakref: info.weakref, bin: info.bin }
+ for each (info in sandbox.globals.memory.getObjects())];
+ }
+
+ sandbox.unload();
+
+ if (sandbox.globals.console.errorsLogged && !results.failed) {
+ results.failed++;
+ console.error("warnings and/or errors were logged.");
+ }
+
+ if (consoleListener.errorsLogged && !results.failed) {
+ console.warn(consoleListener.errorsLogged + " " +
+ "warnings or errors were logged to the " +
+ "platform's nsIConsoleService, which could " +
+ "be of no consequence; however, they could also " +
+ "be indicative of aberrant behavior.");
+ }
+
+ consoleListener.errorsLogged = 0;
+ sandbox = null;
+
+ memory.gc();
+ } catch (e) {
+ results.failed++;
+ console.error("unload.send() threw an exception.");
+ console.exception(e);
+ };
+
+ require("api-utils/timer").setTimeout(showResults, 1);
+}
+
+function nextIteration(tests) {
+ if (tests) {
+ results.passed += tests.passed;
+ results.failed += tests.failed;
+
+ if (profileMemory)
+ reportMemoryUsage();
+
+ let testRun = [];
+ for each (let test in tests.testRunSummary) {
+ let testCopy = {};
+ for (let info in test) {
+ testCopy[info] = test[info];
+ }
+ testRun.push(testCopy);
+ }
+
+ results.testRuns.push(testRun);
+ iterationsLeft--;
+ }
+
+ if (iterationsLeft) {
+ let require = Loader.require.bind(sandbox, module.path);
+ require("api-utils/unit-test").findAndRunTests({
+ testOutOfProcess: require('@packaging').enableE10s,
+ testInProcess: true,
+ filter: filter,
+ onDone: nextIteration
+ });
+ }
+ else {
+ require("api-utils/timer").setTimeout(cleanup, 0);
+ }
+}
+
+var POINTLESS_ERRORS = [
+ "Invalid chrome URI:",
+ "OpenGL LayerManager Initialized Succesfully."
+];
+
+var consoleListener = {
+ errorsLogged: 0,
+ observe: function(object) {
+ if (!(object instanceof Ci.nsIScriptError))
+ return;
+ this.errorsLogged++;
+ var message = object.QueryInterface(Ci.nsIConsoleMessage).message;
+ var pointless = [err for each (err in POINTLESS_ERRORS)
+ if (message.indexOf(err) == 0)];
+ if (pointless.length == 0 && message)
+ print("console: " + message + "\n");
+ }
+};
+
+function TestRunnerConsole(base, options) {
+ this.__proto__ = {
+ errorsLogged: 0,
+ warn: function warn() {
+ this.errorsLogged++;
+ base.warn.apply(base, arguments);
+ },
+ error: function error() {
+ this.errorsLogged++;
+ base.error.apply(base, arguments);
+ },
+ info: function info(first) {
+ if (options.verbose)
+ base.info.apply(base, arguments);
+ else
+ if (first == "pass:")
+ print(".");
+ },
+ __proto__: base
+ };
+}
+
+var runTests = exports.runTests = function runTests(options) {
+ iterationsLeft = options.iterations;
+ filter = options.filter;
+ profileMemory = options.profileMemory;
+ onDone = options.onDone;
+ print = options.print;
+
+ try {
+ cService.registerListener(consoleListener);
+
+ var ptc = require("api-utils/plain-text-console");
+ var url = require("api-utils/url");
+ var system = require("api-utils/system");
+
+ print("Running tests on " + system.name + " " + system.version +
+ "/Gecko " + system.platformVersion + " (" +
+ system.id + ") under " +
+ system.platform + "/" + system.architecture + ".\n");
+
+ sandbox = Loader.new(require("@packaging"));
+ Object.defineProperty(sandbox.globals, 'console', {
+ value: new TestRunnerConsole(new ptc.PlainTextConsole(print), options)
+ });
+
+ nextIteration();
+ } catch (e) {
+ print(require("api-utils/traceback").format(e) + "\n" + e + "\n");
+ onDone({passed: 0, failed: 1});
+ }
+};
+
+require("api-utils/unload").when(function() {
+ cService.unregisterListener(consoleListener);
+});
diff --git a/tools/addon-sdk-1.4/packages/test-harness/lib/run-tests.js b/tools/addon-sdk-1.4/packages/test-harness/lib/run-tests.js
new file mode 100644
index 0000000..4ea6e3f
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/test-harness/lib/run-tests.js
@@ -0,0 +1,133 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+var obsvc = require("api-utils/observer-service");
+var system = require("api-utils/system");
+var options = require('@packaging');
+var {Cc,Ci} = require("chrome");
+
+function runTests(iterations, filter, profileMemory, verbose, rootPaths, exit, print) {
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIWindowWatcher);
+
+ let ns = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+ let msg = 'Running tests...';
+ let markup = '<?xml version="1.0"?><window xmlns="' + ns +
+ '" windowtype="test:runner"><label>' + msg + '</label></window>';
+ let url = "data:application/vnd.mozilla.xul+xml," + escape(markup);
+
+
+ var window = ww.openWindow(null, url, "harness", "centerscreen", null);
+
+ var harness = require("./harness");
+
+ function onDone(tests) {
+ window.close();
+ if (tests.failed == 0) {
+ if (tests.passed === 0)
+ print("No tests were run\n");
+ exit(0);
+ } else {
+ printFailedTests(tests, verbose, print);
+ exit(1);
+ }
+ };
+
+ // We have to wait for this window to be fully loaded *and* focused
+ // in order to avoid it to mess with our various window/focus tests.
+ // We are first waiting for our window to be fully loaded before ensuring
+ // that it will take the focus, and then we wait for it to be focused.
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ window.addEventListener("focus", function onfocus() {
+ window.removeEventListener("focus", onfocus, true);
+ // Finally, we have to run test on next cycle, otherwise XPCOM components
+ // are not correctly updated.
+ // For ex: nsIFocusManager.getFocusedElementForWindow may throw
+ // NS_ERROR_ILLEGAL_VALUE exception.
+ require("timer").setTimeout(function () {
+ harness.runTests({iterations: iterations,
+ filter: filter,
+ profileMemory: profileMemory,
+ verbose: verbose,
+ rootPaths: rootPaths,
+ print: print,
+ onDone: onDone});
+ }, 0);
+ }, true);
+ window.focus();
+ }, true);
+}
+
+function printFailedTests(tests, verbose, print) {
+ if (!verbose)
+ return;
+
+ let iterationNumber = 0;
+ let singleIteration = tests.testRuns.length == 1;
+ let padding = singleIteration ? "" : " ";
+
+ print("\nThe following tests failed:\n");
+
+ for each (let testRun in tests.testRuns) {
+ iterationNumber++;
+
+ if (!singleIteration)
+ print(" Iteration " + iterationNumber + ":\n");
+
+ for each (let test in testRun) {
+ if (test.failed > 0) {
+ print(padding + " " + test.name + ": " + test.errors +"\n");
+ }
+ }
+ print("\n");
+ }
+}
+
+exports.main = function main() {
+ var testsStarted = false;
+
+ if (!testsStarted) {
+ testsStarted = true;
+ runTests(options.iterations, options.filter,
+ options.profileMemory, options.verbose,
+ options.rootPaths, system.exit,
+ dump);
+ }
+};
diff --git a/tools/addon-sdk-1.4/packages/test-harness/package.json b/tools/addon-sdk-1.4/packages/test-harness/package.json
new file mode 100644
index 0000000..1ef10a5
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/test-harness/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "test-harness",
+ "description": "A harness for running Jetpack tests.",
+ "author": "Atul Varma (http://toolness.com/)",
+ "keywords": ["jetpack-low-level"],
+ "license": "MPL 1.1/GPL 2.0/LGPL 2.1",
+ "dependencies": ["api-utils"]
+}
diff --git a/tools/addon-sdk-1.4/packages/test-harness/tests/test-packaging.js b/tools/addon-sdk-1.4/packages/test-harness/tests/test-packaging.js
new file mode 100644
index 0000000..d030267
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/test-harness/tests/test-packaging.js
@@ -0,0 +1,14 @@
+var url = require("url");
+var file = require("file");
+var {Cm,Ci} = require("chrome");
+var options = require("@packaging");
+
+exports.testPackaging = function(test) {
+ test.assertEqual(options.main,
+ 'test-harness/run-tests',
+ "main program should be the test harness");
+
+ test.assertEqual(options.metadata['test-harness'].author,
+ 'Atul Varma (http://toolness.com/)',
+ "packaging metadata should be available");
+};