├── license.ja.txt
├── locale
├── ja
│ ├── messages.properties
│ └── messages.dtd
└── en-US
│ ├── messages.properties
│ └── messages.dtd
├── .gitmodules
├── chrome.manifest
├── make.sh
├── make.bat
├── README.md
├── modules
├── locale
│ ├── messages.properties.ja
│ └── messages.properties.en-US
├── config.js
├── install.js
├── lib
│ ├── here.js
│ ├── locale.js
│ ├── easyTemplate.js
│ ├── WindowManager.js
│ ├── prefs.js
│ ├── KeyboardShortcut.js
│ ├── http.js
│ ├── config.js
│ └── ToolbarItem.js
└── main.js
├── Makefile
├── license.txt
├── install.rdf
├── content
└── config.xul
├── bootstrap.js
└── components
└── loader.js
/license.ja.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/piroor/restartless/HEAD/license.ja.txt
--------------------------------------------------------------------------------
/locale/ja/messages.properties:
--------------------------------------------------------------------------------
1 | config.labelFromProperties=Propertiesファイルで定義されたラベル
2 |
--------------------------------------------------------------------------------
/locale/en-US/messages.properties:
--------------------------------------------------------------------------------
1 | config.labelFromProperties=Label defined in a properties file
2 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "makexpi"]
2 | path = makexpi
3 | url = https://github.com/piroor/makexpi.git
4 |
--------------------------------------------------------------------------------
/chrome.manifest:
--------------------------------------------------------------------------------
1 | content restartless content/
2 | locale restartless en-US locale/en-US/
3 | locale restartless ja locale/ja/
4 |
--------------------------------------------------------------------------------
/make.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | appname=restartless
4 |
5 | cp makexpi/makexpi.sh ./
6 | ./makexpi.sh -n $appname -o
7 | rm ./makexpi.sh
8 |
9 |
--------------------------------------------------------------------------------
/make.bat:
--------------------------------------------------------------------------------
1 | setlocal
2 | set appname=restartless
3 |
4 | copy makexpi\makexpi.sh .\
5 | bash makexpi.sh -n %appname% -o
6 | del makexpi.sh
7 | endlocal
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # restartless
2 | Restartless Addon, A template for restartless addons, for Firefox older than its version 57.
3 |
4 | This project is obsolete and not maintained anymore.
5 |
--------------------------------------------------------------------------------
/modules/locale/messages.properties.ja:
--------------------------------------------------------------------------------
1 | message=ローカライズ済みのメッセージです。
2 |
3 | config.title=設定ダイアログ
4 | config.general=全般
5 | config.testBoolean=真偽値の設定
6 | config.appearance=外観
7 | config.testInteger=整数値の設定
8 |
9 |
--------------------------------------------------------------------------------
/locale/ja/messages.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/modules/config.js:
--------------------------------------------------------------------------------
1 | var config = require('lib/config');
2 | config.setDefault('extensions.restartless@piro.sakura.ne.jp.testBoolean', true);
3 | config.setDefault('extensions.restartless@piro.sakura.ne.jp.testInteger', 10);
4 |
--------------------------------------------------------------------------------
/modules/locale/messages.properties.en-US:
--------------------------------------------------------------------------------
1 | message=This is a localized message.
2 |
3 | config.title=Configuration Dialog
4 | config.general=General
5 | config.testBoolean=This is a boolean setting.
6 | config.appearance=Appearance
7 | config.testInteger=This is an integer.
8 |
9 |
--------------------------------------------------------------------------------
/locale/en-US/messages.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PACKAGE_NAME = restartless
2 |
3 | .PHONY: all xpi signed clean
4 |
5 | all: xpi
6 |
7 | xpi: makexpi/makexpi.sh
8 | makexpi/makexpi.sh -n $(PACKAGE_NAME) -o
9 |
10 | makexpi/makexpi.sh:
11 | git submodule update --init
12 |
13 | signed: xpi
14 | makexpi/sign_xpi.sh -k $(JWT_KEY) -s $(JWT_SECRET) -p ./$(PACKAGE_NAME)_noupdate.xpi
15 |
16 | clean:
17 | rm $(PACKAGE_NAME).xpi $(PACKAGE_NAME)_noupdate.xpi sha1hash.txt
18 |
--------------------------------------------------------------------------------
/modules/install.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Installation module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 1
5 | *
6 | * @license
7 | * The MIT License, Copyright (c) 2011 YUKI "Piro" Hiroshi.
8 | * https://github.com/piroor/restartless/blob/master/license.txt
9 | * @url http://github.com/piroor/restartless
10 | */
11 |
12 | dump('install.js loaded\n');
13 |
14 | function install()
15 | {
16 | dump('install!\n');
17 | }
18 |
19 | function uninstall()
20 | {
21 | dump('uninstall!\n');
22 | }
23 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2010 YUKI "Piro" Hiroshi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/modules/lib/here.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Here-document module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 3
5 | * @description Inspired from https://github.com/cho45/node-here.js
6 | *
7 | * @license
8 | * The MIT License, Copyright (c) 2012 YUKI "Piro" Hiroshi.
9 | * https://github.com/piroor/restartless/blob/master/license.txt
10 | * @url http://github.com/piroor/restartless
11 | */
12 |
13 | var EXPORTED_SYMBOLS = ['here'];
14 |
15 | var cache = {};
16 |
17 | function here() {
18 | var caller = Components.stack.caller;
19 | var filename = caller.filename.split(' -> ').slice(-1)[0];
20 | var line = caller.lineNumber-1;
21 | var key = filename + ':' + line;
22 | if (key in cache) return cache[key];
23 |
24 | var source = read(filename);
25 | var part = source.split(/\r?\n/).slice(line).join('\n');
26 | part = part.replace(/.*\bhere\([^\/]*\/\*/, '');
27 | part = part.split('*/')[0];
28 | cache[key] = part;
29 | return part;
30 | }
31 |
32 | function shutdown() {
33 | cache = undefined;
34 | }
35 |
36 | if (typeof read == 'undefined') {
37 | var Cc = Components.classes;
38 | var Ci = Components.interfaces;
39 | var IOService = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
40 |
41 | read = function read(aURI) {
42 | var uri = IOService.newURI(aURI, null, null);
43 | var channel = IOService.newChannelFromURI(uri);
44 | var stream = channel.open();
45 |
46 | var fileContents = null;
47 | try {
48 | var scriptableStream = Cc['@mozilla.org/scriptableinputstream;1']
49 | .createInstance(Ci.nsIScriptableInputStream);
50 | scriptableStream.init(stream);
51 | fileContents = scriptableStream.read(scriptableStream.available());
52 | scriptableStream.close();
53 | }
54 | finally {
55 | stream.close();
56 | }
57 |
58 | return fileContents;
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/install.rdf:
--------------------------------------------------------------------------------
1 |
2 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
43 |
44 |
45 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/content/config.xul:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
17 |
18 |
20 |
21 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
45 |
48 |
49 |
51 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
69 |
70 |
--------------------------------------------------------------------------------
/modules/lib/locale.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Locale module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 7
5 | *
6 | * @license
7 | * The MIT License, Copyright (c) 2010-2013 YUKI "Piro" Hiroshi.
8 | * https://github.com/piroor/restartless/blob/master/license.txt
9 | * @url http://github.com/piroor/restartless
10 | */
11 |
12 | var EXPORTED_SYMBOLS = ['locale'];
13 |
14 | var DEFAULT_LOCALE = 'en-US';
15 |
16 | var gCache = {}
17 | var get = function(aPath, aBaseURI) {
18 | if (/^\w+:/.test(aPath))
19 | aBaseURI = aPath;
20 |
21 | var uri = aPath;
22 | if (!/^chrome:\/\/[^\/]+\/locale\//.test(uri)) {
23 | let locale = DEFAULT_LOCALE;
24 | try {
25 | let prefs = Cc['@mozilla.org/preferences;1'].getService(Ci.nsIPrefBranch);
26 | locale = prefs.getCharPref('general.useragent.locale');
27 | if (/\w+:/.test(locale))
28 | locale = prefs.getComplexValue('general.useragent.locale', Ci.nsIPrefLocalizedString).data;
29 | locale = locale || DEFAULT_LOCALE;
30 | }
31 | catch(e) {
32 | dump(e+'\n');
33 | }
34 | [
35 | aPath+'.'+locale,
36 | aPath+'.'+(locale.split('-')[0]),
37 | aPath+'.'+DEFAULT_LOCALE,
38 | aPath+'.'+(DEFAULT_LOCALE.split('-')[0])
39 | ].some(function(aURI) {
40 | let resolved = exists(aURI, aBaseURI);
41 | if (resolved) {
42 | uri = resolved;
43 | return true;
44 | }
45 | return false;
46 | });
47 | }
48 |
49 | if (!(uri in gCache)) {
50 | gCache[uri] = new StringBundle(uri);
51 | }
52 | return gCache[uri];
53 | };
54 | exports.get = get;
55 |
56 | var locale = { 'get' : get };
57 |
58 | const Service = Cc['@mozilla.org/intl/stringbundle;1']
59 | .getService(Ci.nsIStringBundleService);
60 |
61 | function StringBundle(aURI)
62 | {
63 | this._bundle = Service.createBundle(aURI);
64 | }
65 | StringBundle.prototype = {
66 | getString : function(aKey) {
67 | try {
68 | return this._bundle.GetStringFromName(aKey);
69 | }
70 | catch(e) {
71 | Cu.reportError(new Error('locale.js: failed to call GetStringFromName() with: ' + aKey + '\n' + e));
72 | }
73 | return '';
74 | },
75 | getFormattedString : function(aKey, aArray) {
76 | try {
77 | return this._bundle.formatStringFromName(aKey, aArray, aArray.length);
78 | }
79 | catch(e) {
80 | Cu.reportError(new Error('locale.js: failed to call formatStringFromName() with: ' + JSON.stringify({ key: aKey, args: aArray }) + '\n' + e));
81 | Cu.reportError(e);
82 | }
83 | return '';
84 | },
85 | get strings() {
86 | return this._bundle.getSimpleEnumeration();
87 | }
88 | };
89 |
90 | /** A handler for bootstrap.js */
91 | function shutdown()
92 | {
93 | gCache = {};
94 | Service.flushBundles();
95 | }
96 |
--------------------------------------------------------------------------------
/modules/lib/easyTemplate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Easy template module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 5
5 | *
6 | * @license
7 | * The MIT License, Copyright (c) 2012-2016 YUKI "Piro" Hiroshi.
8 | * https://github.com/piroor/restartless/blob/master/license.txt
9 | * @url http://github.com/piroor/restartless
10 | */
11 |
12 | if (typeof window == 'undefined' ||
13 | (window && typeof window.constructor == 'function')) {
14 | this.EXPORTED_SYMBOLS = ['easyTemplate'];
15 |
16 | /** A handler for bootstrap.js */
17 | function shutdown() {
18 | easyTemplate = undefined;
19 | }
20 | }
21 |
22 | var easyTemplate = {
23 | apply : function() {
24 | if (arguments.length)
25 | return this._applyToString.apply(this, arguments);
26 | else
27 | return this._applyToDocument();
28 | },
29 |
30 | _systemPrincipal : Components.classes['@mozilla.org/systemprincipal;1']
31 | .createInstance(Components.interfaces.nsIPrincipal),
32 | _documentPrincipal : (function() {
33 | return (typeof window != 'undefined' && window && window.document) ?
34 | window.document.nodePrincipal : null;
35 | })(),
36 |
37 | _applyToString : function(aString, aNamespace) {
38 | var sandbox = new Components.utils.Sandbox(
39 | this._documentPrincipal || this._systemPrincipal,
40 | { sandboxPrototype: aNamespace }
41 | );
42 | return aString.split('}}')
43 | .map(function(aPart) {
44 | let [string, code] = aPart.split('{{');
45 | return string + (code && Components.utils.evalInSandbox(code, sandbox) || '');
46 | })
47 | .join('');
48 | },
49 |
50 | _applyToDocument : function() {
51 | var sandbox = new Components.utils.Sandbox(
52 | this._documentPrincipal,
53 | { sandboxPrototype: window }
54 | );
55 |
56 | for (let aBundle of document.querySelectorAll('stringbundle'))
57 | {
58 | if (aBundle.id)
59 | sandbox[aBundle.id] = aBundle;
60 | }
61 |
62 | ['title', 'label', 'value'].forEach(function(aAttribute) {
63 | var selector = '*[' + aAttribute + '^="{{"][' + aAttribute + '$="}}"]';
64 | var anonymousRoot = document.getAnonymousNodes(document.documentElement)[0];
65 | [...document.querySelectorAll(selector)]
66 | .concat([...anonymousRoot.querySelectorAll(selector)])
67 | .forEach(function(aNode) {
68 | var definition = aNode.getAttribute(aAttribute);
69 | definition = definition.replace(/^\{\{|\}\}$/g, '');
70 | var label = Components.utils.evalInSandbox(definition, sandbox);
71 | aNode.setAttribute(aAttribute, label);
72 | });
73 | });
74 |
75 | var textNodes = document.evaluate(
76 | 'descendant::text()[starts-with(self::text(), "{{")]',
77 | document,
78 | null,
79 | Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
80 | null
81 | );
82 | for (var i = textNodes.snapshotLength-1; i > -1; i--) {
83 | let node = textNodes.snapshotItem(i);
84 | let definition = node.nodeValue;
85 | if (/\}\}$/.test(definition)) { // because "ends-with()" is not available yet
86 | definition = definition.replace(/^\{\{|\}\}$/g, '');
87 | node.nodeValue = Components.utils.evalInSandbox(definition, sandbox);
88 | }
89 | }
90 | }
91 | };
92 |
--------------------------------------------------------------------------------
/bootstrap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Bootstrap code for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 3
5 | *
6 | * @description
7 | * This provides ability to load a script file placed to "modules/main.js".
8 | * Functions named "shutdown", defined in main.js and any loaded script
9 | * will be called when the addon is disabled or uninstalled (include
10 | * updating).
11 | *
12 | * @license
13 | * The MIT License, Copyright (c) 2010-2012 YUKI "Piro" Hiroshi.
14 | * https://github.com/piroor/restartless/blob/master/license.txt
15 | * @url http://github.com/piroor/restartless
16 | */
17 |
18 | var _gLoader;
19 | var _gResourceRegistered = false;
20 |
21 | function _load(aScriptName, aId, aRoot, aReason)
22 | {
23 | const IOService = Components.classes['@mozilla.org/network/io-service;1']
24 | .getService(Components.interfaces.nsIIOService);
25 |
26 | var resource, loader, script;
27 | if (aRoot.isDirectory()) {
28 | resource = IOService.newFileURI(aRoot);
29 |
30 | loader = aRoot.clone();
31 | loader.append('components');
32 | loader.append('loader.js');
33 | loader = IOService.newFileURI(loader).spec;
34 |
35 | script = aRoot.clone();
36 | script.append('modules');
37 | script.append(aScriptName+'.js');
38 | script = IOService.newFileURI(script).spec;
39 | }
40 | else {
41 | let base = 'jar:'+IOService.newFileURI(aRoot).spec+'!/';
42 | loader = base + 'components/loader.js';
43 | script = base + 'modules/'+aScriptName+'.js';
44 | resource = IOService.newURI(base, null, null);
45 | }
46 |
47 | if (!_gLoader) {
48 | _gLoader = {};
49 | Components.classes['@mozilla.org/moz/jssubscript-loader;1']
50 | .getService(Components.interfaces.mozIJSSubScriptLoader)
51 | .loadSubScript(loader, _gLoader);
52 | }
53 |
54 | if (!_gLoader.exists('modules/'+aScriptName+'.js', resource.spec))
55 | return;
56 |
57 | if (!_gResourceRegistered) {
58 | _gLoader.registerResource(aId.split('@')[0]+'-resources', resource);
59 | _gResourceRegistered = true;
60 | }
61 | _gLoader.load(script);
62 | }
63 |
64 | function _reasonToString(aReason)
65 | {
66 | switch (aReason)
67 | {
68 | case APP_STARTUP: return 'APP_STARTUP';
69 | case APP_SHUTDOWN: return 'APP_SHUTDOWN';
70 | case ADDON_ENABLE: return 'ADDON_ENABLE';
71 | case ADDON_DISABLE: return 'ADDON_DISABLE';
72 | case ADDON_INSTALL: return 'ADDON_INSTALL';
73 | case ADDON_UNINSTALL: return 'ADDON_UNINSTALL';
74 | case ADDON_UPGRADE: return 'ADDON_UPGRADE';
75 | case ADDON_DOWNGRADE: return 'ADDON_DOWNGRADE';
76 | }
77 | return aReason;
78 | }
79 |
80 | function _free()
81 | {
82 | _gLoader =
83 | _load =
84 | _reasonToString =
85 | _free =
86 | _gResourceRegistered =
87 | install =
88 | uninstall =
89 | startup =
90 | shoutdown =
91 | undefined;
92 | }
93 |
94 | /**
95 | * handlers for bootstrap
96 | */
97 |
98 | function install(aData, aReason)
99 | {
100 | _load('install', aData.id, aData.installPath, _reasonToString(aReason));
101 | _gLoader.install(_reasonToString(aReason));
102 | }
103 |
104 | function startup(aData, aReason)
105 | {
106 | _load('main', aData.id, aData.installPath, _reasonToString(aReason));
107 | }
108 |
109 | function shutdown(aData, aReason)
110 | {
111 | if (!_gLoader) return;
112 | if (_gResourceRegistered) {
113 | _gLoader.unregisterResource(aData.id.split('@')[0]+'-resources');
114 | }
115 | _gLoader.shutdown(_reasonToString(aReason));
116 | _free();
117 | }
118 |
119 | function uninstall(aData, aReason)
120 | {
121 | if (!_gLoader) {
122 | _load('install', aData.id, aData.installPath, _reasonToString(aReason));
123 | }
124 | _gLoader.uninstall(_reasonToString(aReason));
125 | if (_gResourceRegistered) {
126 | _gLoader.unregisterResource(aData.id.split('@')[0]+'-resources');
127 | }
128 | _free();
129 | }
130 |
--------------------------------------------------------------------------------
/modules/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Main module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 4
5 | *
6 | * @license
7 | * The MIT License, Copyright (c) 2015 YUKI "Piro" Hiroshi.
8 | * https://github.com/piroor/restartless/blob/master/license.txt
9 | * @url http://github.com/piroor/restartless
10 | */
11 |
12 | dump('main.js loaded\n');
13 |
14 | /**
15 | * load() works like Components.utils.import(). EXPORTED_SYMBOLS
16 | * in loaded scripts are exported to the global object of this script.
17 | */
18 | load('lib/WindowManager');
19 | load('lib/ToolbarItem');
20 | load('lib/KeyboardShortcut');
21 | load('lib/here');
22 | load('lib/easyTemplate');
23 | load('lib/prefs');
24 | // this.import() also available instead of load(), as an alias.
25 | // Note: don't use simply "import()" without the prefix "this.",
26 | // because the keyword "import" will be a reserved word in future.
27 | // https://developer.mozilla.org/en/JavaScript/Strict_mode#Paving_the_way_for_future_ECMAScript_versions
28 | this.import('config');
29 |
30 | var http = require('lib/http');
31 |
32 | /**
33 | * Localized messages sample.
34 | */
35 | var bundle = require('lib/locale')
36 | .get(location.href+'/../locale/messages.properties');
37 | dump(bundle.getString('message')+'\n');
38 |
39 |
40 | /**
41 | * Preferences example
42 | */
43 | var myPrefs = prefs.createStore('extensions.restartless@piro.sakura.ne.jp.');
44 | // property name, default value, preference key (optional)
45 | myPrefs.define('booleanProp', false, 'testBoolean2');
46 | myPrefs.define('integerProp', 64, 'testInteger2');
47 | dump('current boolean value is: '+myPrefs.booleanProp+'\n');
48 |
49 |
50 | /**
51 | * Sample code for addons around browser windows.
52 | */
53 | const TYPE_BROWSER = 'navigator:browser';
54 |
55 | var global = this;
56 | function handleWindow(aWindow)
57 | {
58 | var doc = aWindow.document;
59 | if (doc.documentElement.getAttribute('windowtype') != TYPE_BROWSER)
60 | return;
61 |
62 | /* sample: hello world */
63 | var range = doc.createRange();
64 | range.selectNodeContents(doc.documentElement);
65 | range.collapse(false);
66 |
67 | var fragment = range.createContextualFragment(here(/*
68 |
70 | */));
71 | range.insertNode(fragment);
72 |
73 | range.detach();
74 |
75 | /* sample: customizable toolbar button */
76 | var button = ToolbarItem.create(
77 | easyTemplate.apply(here(/*
78 |
79 |
80 |
81 | */), global),
82 | doc.getElementById('nav-bar'),
83 | { // options
84 | onInit : function() {
85 | console.log('restartless-test-button: inserted ' + this.node);
86 | },
87 | onDestroy : function() {
88 | console.log('restartless-test-button: going to be removed: ' + this.node);
89 | }
90 | }
91 | );
92 | button.addEventListener('command', function(aEvent) {
93 | var uri = aWindow.content.location.href;
94 | http.getAsBinary(uri)
95 | .next(function(aResponse) {
96 | aWindow.alert([
97 | uri,
98 | aResponse.status,
99 | aResponse.getAllResponseHeaders(),
100 | aResponse.responseText.substring(0, 40) + '...'
101 | ].join('\n'));
102 | })
103 | .error(function(aError) {
104 | aWindow.alert(aError);
105 | });
106 | });
107 |
108 | /* sample: keyboard shortcut */
109 | KeyboardShortcut.create({
110 | shortcut : 'Ctrl-F2',
111 | oncommand : 'alert("hello!");'
112 | }, doc.getElementById('mainKeyset'));
113 | }
114 |
115 | WindowManager.getWindows(TYPE_BROWSER).forEach(handleWindow);
116 | WindowManager.addHandler(handleWindow);
117 |
118 | /**
119 | * A handler for shutdown event. This will be called when the addon
120 | * is disabled or uninstalled (include updating).
121 | */
122 | function shutdown()
123 | {
124 | WindowManager.getWindows(TYPE_BROWSER).forEach(function(aWindow) {
125 | /* sample: destructor for hello world */
126 | var doc = aWindow.document;
127 | var label = doc.getElementById('helloworld');
128 | label.parentNode.removeChild(label);
129 | });
130 |
131 | // free loaded symbols
132 | WindowManager = undefined;
133 | ToolbarItem = undefined;
134 | KeyboardShortcut = undefined;
135 | bundle = undefined;
136 | here = undefined;
137 | easyTemplate = undefined;
138 | http = undefined;
139 | global = undefined;
140 | }
141 |
--------------------------------------------------------------------------------
/modules/lib/WindowManager.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Window manager module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 5
5 | *
6 | * @license
7 | * The MIT License, Copyright (c) 2010-2014 YUKI "Piro" Hiroshi.
8 | * https://github.com/piroor/restartless/blob/master/license.txt
9 | * @url http://github.com/piroor/restartless
10 | */
11 |
12 | var EXPORTED_SYMBOLS = ['WindowManager'];
13 |
14 | var _WindowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']
15 | .getService(Ci.nsIWindowWatcher);
16 | var _WindowMediator = Cc['@mozilla.org/appshell/window-mediator;1']
17 | .getService(Ci.nsIWindowMediator);
18 |
19 | var _gListener = {
20 | observe : function(aSubject, aTopic, aData)
21 | {
22 | if (
23 | aTopic == 'domwindowopened' &&
24 | !aSubject
25 | .QueryInterface(Ci.nsIInterfaceRequestor)
26 | .getInterface(Ci.nsIWebNavigation)
27 | .QueryInterface(Ci.nsIDocShell)
28 | .QueryInterface(Ci.nsIDocShellTreeNode || Ci.nsIDocShellTreeItem) // nsIDocShellTreeNode is merged to nsIDocShellTreeItem by https://bugzilla.mozilla.org/show_bug.cgi?id=331376
29 | .QueryInterface(Ci.nsIDocShellTreeItem)
30 | .parent
31 | )
32 | aSubject
33 | .QueryInterface(Ci.nsIDOMWindow)
34 | .addEventListener('DOMContentLoaded', this, false);
35 | },
36 | handleEvent : function(aEvent)
37 | {
38 | aEvent.currentTarget.removeEventListener(aEvent.type, this, false);
39 |
40 | var window = aEvent.target.defaultView;
41 | this.listeners.forEach(function(aListener) {
42 | try {
43 | if (aListener.handleEvent &&
44 | typeof aListener.handleEvent == 'function')
45 | aListener.handleEvent(aEvent);
46 | if (aListener.handleWindow &&
47 | typeof aListener.handleWindow == 'function')
48 | aListener.handleWindow(window);
49 | if (typeof aListener == 'function')
50 | aListener(window);
51 | }
52 | catch(e) {
53 | dump(e+'\n');
54 | }
55 | });
56 | },
57 | listeners : []
58 | };
59 | _WindowWatcher.registerNotification(_gListener);
60 |
61 | /**
62 | * @class
63 | * Provides features to get existing chrome windows, etc.
64 | */
65 | var WindowManager = {
66 | /**
67 | * Registers a handler for newly opened chrome windows. Handlers will
68 | * be called when DOMContentLoaded events are fired in newly opened
69 | * windows.
70 | *
71 | * @param {Object} aHandler
72 | * A handler for new windows. If you specify a function, it will be
73 | * called with the DOMWindow object as the first argument. If the
74 | * specified object has a method named "handleWindow", then the
75 | * method will be called with the DOMWindow. If the object has a
76 | * method named "handleEvent", then it will be called with the
77 | * DOMContentLoaded event object (not DOMWindow object.)
78 | */
79 | addHandler : function(aListener)
80 | {
81 | if (!_gListener) return;
82 | if (
83 | aListener &&
84 | (
85 | typeof aListener == 'function' ||
86 | (aListener.handleWindow && typeof aListener.handleWindow == 'function') ||
87 | (aListener.handleEvent && typeof aListener.handleEvent == 'function')
88 | ) &&
89 | _gListener.listeners.indexOf(aListener) < 0
90 | )
91 | _gListener.listeners.push(aListener);
92 | },
93 | /**
94 | * Unregisters a handler.
95 | */
96 | removeHandler : function(aListener)
97 | {
98 | if (!_gListener) return;
99 | let index = _gListener.listeners.indexOf(aListener);
100 | if (index > -1)
101 | _gListener.listeners.splice(index, 1);
102 | },
103 | /**
104 | * Returns the most recent chrome window (DOMWindow).
105 | *
106 | * @param {string=} aWindowType
107 | * The window type you want to get, ex. "navigator:browser". If you
108 | * specify no type (null, blank string, etc.) then this returns
109 | * the most recent window of any type.
110 | *
111 | * @returns {nsIDOMWindow}
112 | * A found DOMWindow.
113 | */
114 | getWindow : function(aType)
115 | {
116 | return _WindowMediator.getMostRecentWindow(aType || null);
117 | },
118 | /**
119 | * Returns an array of chrome windows (DOMWindow).
120 | *
121 | * @param {string=} aWindowType
122 | * The window type you want to filter, ex. "navigator:browser". If
123 | * you specify no type (null, blank string, etc.) then this returns
124 | * an array of all chrome windows.
125 | *
126 | * @returns {Array}
127 | * An array of found DOMWindows.
128 | */
129 | getWindows : function(aType)
130 | {
131 | var array = [];
132 | var windows = _WindowMediator.getZOrderDOMWindowEnumerator(aType || null, true);
133 |
134 | // By the bug 156333, we cannot find windows by their Z order on Linux.
135 | // https://bugzilla.mozilla.org/show_bug.cgi?id=156333
136 | if (!windows.hasMoreElements())
137 | windows = _WindowMediator.getEnumerator(aType || null);
138 |
139 | while (windows.hasMoreElements())
140 | {
141 | array.push(windows.getNext().QueryInterface(Ci.nsIDOMWindow));
142 | }
143 | return array;
144 | }
145 | };
146 | for (let i in WindowManager)
147 | {
148 | exports[i] = (function(aSymbol) {
149 | return function() {
150 | return WindowManager[aSymbol].apply(WindowManager, arguments);
151 | };
152 | })(i);
153 | }
154 |
155 | /** A handler for bootstrap.js */
156 | function shutdown()
157 | {
158 | _WindowWatcher.unregisterNotification(_gListener);
159 | _WindowWatcher = void(0);
160 | _gListener.listeners = [];
161 | }
162 |
--------------------------------------------------------------------------------
/modules/lib/prefs.js:
--------------------------------------------------------------------------------
1 | /*
2 | Preferences Library
3 |
4 | Usage:
5 | var value = prefs.getPref('my.extension.pref');
6 | prefs.setPref('my.extension.pref', true);
7 | prefs.clearPref('my.extension.pref');
8 | var listener = {
9 | domains : [
10 | 'browser.tabs',
11 | 'extensions.someextension'
12 | ],
13 | observe : function(aSubject, aTopic, aData)
14 | {
15 | if (aTopic != 'nsPref:changed') return;
16 | var value = prefs.getPref(aData);
17 | }
18 | };
19 | prefs.addPrefListener(listener);
20 | prefs.removePrefListener(listener);
21 |
22 | // utility
23 | var store = prefs.createStore('extensions.someextension.');
24 | // property name/key, default value
25 | store.define('enabled', true);
26 | // property name, default value, pref key (different to the name)
27 | store.define('leftMargin', true, 'margin.left');
28 | var enabled = store.enabled;
29 | store.destroy(); // free the memory.
30 |
31 | license: The MIT License, Copyright (c) 2009-2017 YUKI "Piro" Hiroshi
32 | original:
33 | http://github.com/piroor/fxaddonlib-prefs
34 | */
35 |
36 | var EXPORTED_SYMBOLS = ['prefs'];
37 | var prefs;
38 |
39 | (function() {
40 | const currentRevision = 20;
41 |
42 | const Cc = Components.classes;
43 | const Ci = Components.interfaces;
44 |
45 | prefs = {
46 | revision : currentRevision,
47 |
48 | Prefs : Cc['@mozilla.org/preferences-service;1']
49 | .getService(Ci.nsIPrefBranch),
50 |
51 | DefaultPrefs : Cc['@mozilla.org/preferences-service;1']
52 | .getService(Ci.nsIPrefService)
53 | .getDefaultBranch(null),
54 |
55 | getPref : function(aPrefstring, aInterface, aBranch)
56 | {
57 | if (!aInterface || aInterface instanceof Ci.nsIPrefBranch)
58 | [aBranch, aInterface] = [aInterface, aBranch];
59 |
60 | aBranch = aBranch || this.Prefs;
61 |
62 | var type = aBranch.getPrefType(aPrefstring);
63 | if (type == aBranch.PREF_INVALID)
64 | return null;
65 |
66 | if (aInterface)
67 | return aBranch.getComplexValue(aPrefstring, aInterface);
68 |
69 | try {
70 | switch (type)
71 | {
72 | case aBranch.PREF_STRING:
73 | return decodeURIComponent(escape(aBranch.getCharPref(aPrefstring)));
74 |
75 | case aBranch.PREF_INT:
76 | return aBranch.getIntPref(aPrefstring);
77 |
78 | case aBranch.PREF_BOOL:
79 | return aBranch.getBoolPref(aPrefstring);
80 |
81 | case aBranch.PREF_INVALID:
82 | default:
83 | return null;
84 | }
85 | } catch(e) {
86 | // getXXXPref can raise an error if it is the default branch.
87 | return null;
88 | }
89 | },
90 |
91 | getLocalizedPref : function(aPrefstring)
92 | {
93 | try {
94 | return this.getPref(aPrefstring, Ci.nsIPrefLocalizedString).data;
95 | } catch(e) {
96 | return this.getPref(aPrefstring);
97 | }
98 | },
99 |
100 | getDefaultPref : function(aPrefstring, aInterface)
101 | {
102 | return this.getPref(aPrefstring, this.DefaultPrefs, aInterface);
103 | },
104 |
105 | setPref : function(aPrefstring, aNewValue)
106 | {
107 | var branch = this.Prefs;
108 | var dataTypeInterface = null;
109 | if (arguments.length > 2) {
110 | for (let i = 2; i < arguments.length; i++)
111 | {
112 | let arg = arguments[i];
113 | if (!arg)
114 | continue;
115 | if (arg instanceof Ci.nsIPrefBranch)
116 | branch = arg;
117 | else
118 | dataTypeInterface = arg;
119 | }
120 | }
121 | if (dataTypeInterface &&
122 | aNewValue instanceof Ci.nsISupports) {
123 | return branch.setComplexValue(aPrefstring, dataTypeInterface, aNewValue);
124 | }
125 | switch (typeof aNewValue)
126 | {
127 | case 'string':
128 | return branch.setCharPref(aPrefstring, unescape(encodeURIComponent(aNewValue)));
129 |
130 | case 'number':
131 | return branch.setIntPref(aPrefstring, parseInt(aNewValue));
132 |
133 | default:
134 | return branch.setBoolPref(aPrefstring, !!aNewValue);
135 | }
136 | },
137 |
138 | setDefaultPref : function(aPrefstring, aNewValue)
139 | {
140 | return this.setPref(aPrefstring, aNewValue, this.DefaultPrefs);
141 | },
142 |
143 | clearPref : function(aPrefstring)
144 | {
145 | if (this.Prefs.prefHasUserValue(aPrefstring))
146 | this.Prefs.clearUserPref(aPrefstring);
147 | },
148 |
149 | getDescendant : function(aRoot, aBranch)
150 | {
151 | aBranch = aBranch || this.Prefs;
152 | return aBranch.getChildList(aRoot, {}).sort();
153 | },
154 |
155 | getChildren : function(aRoot, aBranch)
156 | {
157 | aRoot = aRoot.replace(/\.$/, '');
158 | var foundChildren = {};
159 | var possibleChildren = [];
160 | this.getDescendant(aRoot, aBranch)
161 | .forEach(function(aPrefstring) {
162 | var name = aPrefstring.replace(aRoot + '.', '');
163 | let possibleChildKey = aRoot + '.' + name.split('.')[0];
164 | if (possibleChildKey && !(possibleChildKey in foundChildren)) {
165 | possibleChildren.push(possibleChildKey);
166 | foundChildren[possibleChildKey] = true;
167 | }
168 | });
169 | return possibleChildren.sort();
170 | },
171 |
172 | addPrefListener : function(aObserver)
173 | {
174 | var domains = ('domains' in aObserver) ? aObserver.domains : [aObserver.domain] ;
175 | try {
176 | for (var domain of domains)
177 | this.Prefs.addObserver(domain, aObserver, false);
178 | }
179 | catch(e) {
180 | }
181 | },
182 |
183 | removePrefListener : function(aObserver)
184 | {
185 | var domains = ('domains' in aObserver) ? aObserver.domains : [aObserver.domain] ;
186 | try {
187 | for (var domain of domains)
188 | this.Prefs.removeObserver(domain, aObserver, false);
189 | }
190 | catch(e) {
191 | }
192 | },
193 |
194 | createStore : function(aDomain)
195 | {
196 | var listener = {
197 | domain : aDomain,
198 | observe : function(aSubject, aTopic, aData) {
199 | if (aTopic != 'nsPref:changed')
200 | return;
201 | var name = keyToName[aData];
202 | store[name] = prefs.getPref(aData);
203 | }
204 | };
205 | this.addPrefListener(listener);
206 | var keyToName = {};
207 | var base = aDomain.replace(/\.$/, '') + '.';
208 | var store = {
209 | define : function(aName, aValue, aKey) {
210 | aKey = base + (aKey || aName);
211 | prefs.setDefaultPref(aKey, aValue);
212 | this[aName] = prefs.getPref(aKey);
213 | keyToName[aKey] = aName;
214 | },
215 | destroy : function() {
216 | prefs.removePrefListener(listener);
217 | aDomain = undefined;
218 | base = undefined;
219 | listener = undefined;
220 | keyToName = undefined;
221 | store = undefined;
222 | }
223 | };
224 | return store;
225 | }
226 | };
227 | })();
228 |
--------------------------------------------------------------------------------
/modules/lib/KeyboardShortcut.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Keyboard shortcut helper module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 2
5 | *
6 | * @license
7 | * The MIT License, Copyright (c) 2011 YUKI "Piro" Hiroshi.
8 | * https://github.com/piroor/restartless/blob/master/license.txt
9 | * @url http://github.com/piroor/restartless
10 | */
11 |
12 | if (typeof window == 'undefined' ||
13 | (window && typeof window.constructor == 'function')) {
14 | this.EXPORTED_SYMBOLS = ['KeyboardShortcut'];
15 |
16 | /** A handler for bootstrap.js */
17 | function shutdown()
18 | {
19 | KeyboardShortcut.instances.slice(0).forEach(function(aKey) {
20 | aKey.destroy();
21 | });
22 | KeyboardShortcut = undefined;
23 | }
24 | }
25 |
26 | /**
27 | * aDefinition = {
28 | * shortcut : String (key combinations, like "ctrl-shift-s"),
29 | * (other properties) : String (attributes for the generated key element)
30 | * }
31 | */
32 | function KeyboardShortcut(aDefinition, aKeySet) {
33 | this.init(aDefinition, aKeySet);
34 | }
35 | KeyboardShortcut.prototype = {
36 | init : function(aDefinition, aKeySet)
37 | {
38 | if (this._definition)
39 | return;
40 |
41 | if (!aKeySet)
42 | throw new Error('no element is specified!');
43 |
44 | aDefinition = this._normalizeDefinition(aDefinition);
45 | this._assertDefinition(aDefinition);
46 | this._definition = aDefinition;
47 |
48 | this._document = aKeySet.ownerDocument || aKeySet;
49 | this._window = this._document.defaultView;
50 | this._window.addEventListener('unload', this, false);
51 | this._createKeyIn(aKeySet);
52 |
53 | KeyboardShortcut.instances.push(this);
54 | },
55 |
56 | destroy : function()
57 | {
58 | if (!this._definition)
59 | return;
60 |
61 | this._window.removeEventListener('unload', this, false);
62 |
63 | if (this._keyset.parentNode)
64 | this._keyset.parentNode.removeChild(this._keyset);
65 |
66 | delete this._definition;
67 | delete this._document;
68 | delete this._window;
69 | delete this._keyset;
70 | delete this.node;
71 |
72 | KeyboardShortcut.instances = KeyboardShortcut.instances.filter(function(aKey) {
73 | return aKey != this;
74 | }, this);
75 | },
76 |
77 | handleEvent : function(aEvent)
78 | {
79 | this.destroy(); // on unload
80 | },
81 |
82 | _normalizeDefinition : function(aDefinition)
83 | {
84 | if (aDefinition.shortcut)
85 | aDefinition.shortcut = this._normalizeShortcut(aDefinition.shortcut);
86 |
87 | return aDefinition;
88 | },
89 |
90 | _assertDefinition : function(aDefinition)
91 | {
92 | if (!aDefinition || !aDefinition.shortcut)
93 | throw new Error('"shortcut", the keyboard shortcut is required!');
94 | },
95 |
96 | _normalizeShortcut : function(aShortcut)
97 | {
98 | return aShortcut
99 | .replace(/^\s+|\s+$/g, '')
100 | .replace(/\s+/g, ' ')
101 | .replace(/accel-/gi, KeyboardShortcut.XULAppInfo.OS == 'Darwin' ? 'meta' : 'ctrl' )
102 | .replace(/option-/gi, 'alt-')
103 | .replace(/control-/gi, 'ctrl-')
104 | .replace(/(command|\u2318)-/gi, 'meta-')
105 | .replace(/(?:(?:alt|ctrl|meta|shift)-)+/g, function(aModifiers) {
106 | return aModifiers.replace(/-$/, '').split('-').sort().join('-')+'-';
107 | })
108 | .toLowerCase();
109 | },
110 |
111 | _createKeyIn : function(aKeySet)
112 | {
113 | var shortcut = this._definition.shortcut;
114 | var key = this._document.createElement('key');
115 |
116 | var modifiers = [];
117 | if (shortcut.indexOf('alt-') > -1) modifiers.push('alt');
118 | if (shortcut.indexOf('ctrl-') > -1) modifiers.push('control');
119 | if (shortcut.indexOf('meta-') > -1) modifiers.push('meta');
120 | if (shortcut.indexOf('shift-') > -1) modifiers.push('shift');
121 | if (modifiers) key.setAttribute('modifiers', modifiers.join(','));
122 |
123 | var keyMatch = shortcut.match(/-(.)$/);
124 | var keyCodeMatch = shortcut.match(/-([^\-]+)$/);
125 | if (keyMatch = (keyMatch && keyMatch[1])) {
126 | key.setAttribute('key', keyMatch);
127 | }
128 | else if (keyCodeMatch = (keyCodeMatch && KeyboardShortcut._keyCodeFromKeyName(keyCodeMatch[1]))) {
129 | key.setAttribute('keycode', keyCodeMatch);
130 | }
131 | else {
132 | this.destroy();
133 | throw new Error(shortcut+' is not a valid shortcut!');
134 | }
135 |
136 | for (let attr in this._definition)
137 | {
138 | if (attr == 'shortcut' || !this._definition.hasOwnProperty(attr))
139 | continue;
140 | key.setAttribute(attr, this._definition[attr]);
141 | }
142 |
143 | /**
144 | * must be inserted with new , because s
145 | * inserted into existing doesn't work due to Gecko's bug.
146 | * http://d.hatena.ne.jp/onozaty/20080204/p1
147 | * https://bugzilla.mozilla.org/show_bug.cgi?id=399604
148 | * https://bugzilla.mozilla.org/show_bug.cgi?id=101116
149 | */
150 | this._keyset = this._document.createElement('keyset');
151 | this._keyset.appendChild(key);
152 |
153 | aKeySet.appendChild(this._keyset);
154 | this.node = key;
155 | }
156 | };
157 |
158 | KeyboardShortcut.instances = [];
159 |
160 | KeyboardShortcut.toKeyboardShortcut = function(aEvent) {
161 | if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_ALT ||
162 | aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL ||
163 | aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_META ||
164 | aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_SHIFT)
165 | return '';
166 |
167 | var shortcut = [];
168 | if (aEvent.altKey) shortcut.push('Alt');
169 | if (aEvent.ctrlKey) shortcut.push(KeyboardShortcut.XULAppInfo.OS == 'Darwin' ? 'Control' : 'Ctrl');
170 | if (aEvent.metaKey) shortcut.push(KeyboardShortcut.XULAppInfo.OS == 'Darwin' ? /* 'Command' */ '\u2318' : 'Meta' );
171 | if (aEvent.shiftKey) shortcut.push('Shift');
172 | if (aEvent.charCode) shortcut.push(String.fromCharCode(aEvent.charCode));
173 | if (aEvent.keyCode) shortcut.push(this._keyNameFromKeyCode(aEvent.keyCode));
174 | return shortcut.join('-');
175 | };
176 | KeyboardShortcut.create = function(aDefinition, aKeySet) {
177 | return new this(aDefinition, aKeySet);
178 | };
179 |
180 | KeyboardShortcut._keyNameFromKeyCode = function(aCode) {
181 | for (let prop in Ci.nsIDOMKeyEvent)
182 | {
183 | if (Ci.nsIDOMKeyEvent[prop] == aCode)
184 | return prop.replace(/^DOM_VK_/, '')
185 | .split('_')
186 | .map(function(aPart) {
187 | return aPart.charAt(0).toUpperCase()+aPart.toLowerCase().substring(1);
188 | })
189 | .join('');
190 | }
191 | return 'unknown';
192 | };
193 | KeyboardShortcut._keyCodeFromKeyName = function(aName) {
194 | var keyCode = 'VK_'+
195 | aName
196 | .replace(/[A-Z][a-z]+/g, function(aPart) {
197 | return '-'+aPart;
198 | })
199 | .replace(/^-/, '')
200 | .toUpperCase();
201 | return ('DOM_'+keyCode in Ci.nsIDOMKeyEvent) ? keyCode : '' ;
202 | };
203 |
204 | KeyboardShortcut.XULAppInfo = Components.classes['@mozilla.org/xre/app-info;1']
205 | .getService(Components.interfaces.nsIXULAppInfo)
206 | .QueryInterface(Components.interfaces.nsIXULRuntime);
207 |
--------------------------------------------------------------------------------
/modules/lib/http.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview XMLHttpRequest wrapper module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 14
5 | * @description
6 | * // get as a text
7 | * http.get('http://.....',
8 | * { 'X-Response-Content-Type': 'text/plain; charset=EUC-JP' })
9 | * .next(function(aResponse) {
10 | * console.log(aResponse.responseText);
11 | * });
12 | *
13 | * // get as a JSON
14 | * http.getAsJSON('http://.....')
15 | * .next(function(aResponse) {
16 | * var body = aResponse.response;
17 | * console.log(function);
18 | * });
19 | *
20 | * // get as a binary
21 | * http.getAsBinary('http://.....')
22 | * .next(function(aResponse) {
23 | * // arraybyffer = aResponse.response;
24 | * var bodyBytesArray = aResponse.responseText;
25 | * var bodyBase64 = btoa(bodyBytesArray);
26 | * console.log(bodyBase64);
27 | * });
28 | * // See also: https://developer.mozilla.org/docs/XMLHttpRequest/Sending_and_Receiving_Binary_Data
29 | *
30 | * http.postAsJSON('http://.....', { a: true, b: 29 })
31 | * .next(function(aResponse) {
32 | * var responseJSON = JSON.parse(aResponse.responseText);
33 | * });
34 | * http.postAsForm('http://.....', { a: "true", b: "29" });
35 | *
36 | * @license
37 | * The MIT License, Copyright (c) 2014 YUKI "Piro" Hiroshi.
38 | * https://github.com/piroor/restartless/blob/master/license.txt
39 | * @url http://github.com/piroor/restartless
40 | */
41 |
42 | var EXPORTED_SYMBOLS = [
43 | 'get',
44 | 'getAsJSON',
45 | 'getAsBinary',
46 | 'post',
47 | 'postAsJSON',
48 | 'postAsForm',
49 | 'RESPONSE_TYPE',
50 | 'RESPONSE_CONTENT_TYPE'
51 | ];
52 |
53 | var RESPONSE_TYPE = 'X-Response-Type';
54 | var RESPONSE_CONTENT_TYPE = 'X-Response-Content-Type';
55 |
56 | var PSEUDO_HEADERS = [
57 | RESPONSE_TYPE,
58 | RESPONSE_CONTENT_TYPE
59 | ];
60 |
61 | var Deferred = require('jsdeferred').Deferred;
62 |
63 | function clone(aObject) {
64 | aObject = aObject || {};
65 | var cloned = {};
66 | Object.keys(aObject).forEach(function(aKey) {
67 | cloned[aKey] = aObject[aKey];
68 | });
69 | return cloned;
70 | }
71 |
72 |
73 | function get(aURI, aHeaders) {
74 | return sendRequest({
75 | method: 'GET',
76 | uri: aURI,
77 | headers: aHeaders
78 | });
79 | }
80 |
81 | function getAsJSON(aURI, aHeaders) {
82 | var headers = clone(aHeaders);
83 | headers[RESPONSE_TYPE] = 'json';
84 | return get(aURI, headers);
85 | }
86 |
87 | function getAsBinary(aURI, aHeaders) {
88 | var headers = clone(aHeaders);
89 | headers[RESPONSE_TYPE] = 'arraybuffer';
90 | return get(aURI, headers);
91 | }
92 |
93 | function post(aURI, aPostData, aHeaders) {
94 | return sendRequest({
95 | method: 'POST',
96 | uri: aURI,
97 | headers: aHeaders,
98 | postData: aPostData
99 | });
100 | }
101 |
102 | function postAsJSON(aURI, aPostData, aHeaders) {
103 | var headers = clone(aHeaders);
104 | var postData = JSON.stringify(aPostData);
105 | headers['Content-Type'] = 'application/json';
106 | return post(aURI, postData, headers);
107 | }
108 |
109 | function postAsForm(aURI, aFormData, aHeaders) {
110 | var headers = clone(aHeaders);
111 | var postData = Cc['@mozilla.org/files/formdata;1']
112 | .createInstance(Ci.nsIDOMFormData);
113 | Object.keys(aFormData).forEach(function(aKey) {
114 | postData.append(aKey, aFormData[aKey]);
115 | });
116 | delete headers['Content-Type']; // it is always multipart/form-data
117 | return post(aURI, postData, headers);
118 | }
119 |
120 |
121 | function ArrayBufferRespone(aResponse) {
122 | this._raw = aResponse;
123 | this._init();
124 | }
125 | ArrayBufferRespone.prototype = {
126 | _init: function() {
127 | for (let key in this._raw) {
128 | if (key in this)
129 | continue;
130 | this._bind(key);
131 | }
132 | },
133 | _bind: function(aName) {
134 | if (typeof this._raw[aName] == 'function') {
135 | this[aName] = this._raw[aName].bind(this._raw);
136 | } else {
137 | this.__defineGetter__(aName, function() {
138 | return this._raw[aName];
139 | });
140 | }
141 | },
142 | get responseText() {
143 | var bytesArray = ''
144 | var bytes = new Uint8Array(this._raw.response);
145 | for (let i = 0, maxi = bytes.byteLength; i < maxi; i++) {
146 | bytesArray += String.fromCharCode(bytes[i]);
147 | }
148 | return bytesArray;
149 | },
150 | get responseXML() {
151 | // accessing to the property raises InvalidStateError, so I have to define this staticly
152 | return this._raw.responseXML;
153 | }
154 | };
155 |
156 | function sendRequest(aParams) {
157 | var deferred = new Deferred();
158 |
159 | var method = (aParams.method || 'GET').toUpperCase();
160 | var uri = aParams.uri || null;
161 | var headers = aParams.headers || {};
162 | var postData = aParams.postData || null;
163 | var responseType = aParams.responseType || '';
164 | var responseContentType = aParams.responseContentType || null;
165 |
166 | if (headers[RESPONSE_TYPE])
167 | responseType = headers[RESPONSE_TYPE];
168 | if (headers[RESPONSE_CONTENT_TYPE])
169 | responseContentType = headers[RESPONSE_CONTENT_TYPE];
170 |
171 | if (!uri)
172 | throw new Error('no URL');
173 |
174 | var cleanup = function() {
175 | if (!request)
176 | return;
177 | request.removeEventListener('load', listener, false);
178 | request.removeEventListener('error', listener, false);
179 | deferred.canceller = function() {};
180 | deferred = listener = request = undefined;
181 | };
182 |
183 | var request;
184 | var listener = function(aEvent) {
185 | if (!request || !listener)
186 | return;
187 |
188 | switch (aEvent.type) {
189 | case 'load':
190 | case 'error':
191 | break;
192 | default:
193 | return;
194 | }
195 |
196 | var response = request;
197 | if (responseType == 'arraybuffer')
198 | response = new ArrayBufferRespone(response)
199 |
200 | deferred.call(response);
201 | cleanup();
202 | response = undefined;
203 | };
204 |
205 | Deferred.next(function() {
206 | request = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
207 | .createInstance(Ci.nsIXMLHttpRequest)
208 | .QueryInterface(Ci.nsIDOMEventTarget);
209 | request.open(method, uri, true);
210 | if (responseType)
211 | request.responseType = responseType;
212 | if (responseContentType)
213 | request.overrideMimeType(responseContentType);
214 | Object.keys(headers).forEach(function(aKey) {
215 | if (PSEUDO_HEADERS.indexOf(aKey) < 0)
216 | request.setRequestHeader(aKey, headers[aKey]);
217 | });
218 | request.addEventListener('load', listener, false);
219 | request.addEventListener('error', listener, false);
220 |
221 | deferred.canceller = function() {
222 | request.abort();
223 | cleanup();
224 | };
225 |
226 | if (postData &&
227 | typeof postData == 'object' &&
228 | !(postData instanceof Ci.nsISupports))
229 | postData = JSON.stringify(postData);
230 |
231 | if (typeof postData == 'string') {
232 | let postDataStream = Cc['@mozilla.org/io/string-input-stream;1']
233 | .createInstance(Ci.nsIStringInputStream);
234 | postDataStream.data = postData;
235 | postData = postDataStream;
236 | }
237 | request.send(postData);
238 | }).error(function(aError) {
239 | deferred.fail(aError);
240 | });
241 |
242 | return deferred;
243 | }
244 |
--------------------------------------------------------------------------------
/modules/lib/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Configuration dialog module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 17
5 | *
6 | * @license
7 | * The MIT License, Copyright (c) 2011-2018 YUKI "Piro" Hiroshi.
8 | * https://github.com/piroor/restartless/blob/master/license.txt
9 | * @url http://github.com/piroor/restartless
10 | */
11 |
12 | var EXPORTED_SYMBOLS = ['config'];
13 |
14 | const XULNS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
15 |
16 | 'open,register,unregister,setDefault'.split(',').forEach(function(aSymbol) {
17 | exports[aSymbol] = function() {
18 | if (!config)
19 | throw new Error('config module was already unloaded!');
20 | return config[aSymbol].apply(config, arguments);
21 | };
22 | });
23 |
24 | /**
25 | * @class
26 | * Provides features to manage custom configuration dialog.
27 | */
28 | var config = {
29 | _configs : {},
30 |
31 | /**
32 | * Opens a registered dialog bound to the given URI as a "non-modal"
33 | * window. If there is existing window, then focus to it.
34 | *
35 | * @param {String} aURI
36 | * A URI which is bould to any configuration dialog.
37 | * @param {nsIDOMWindow} aOwner
38 | * An owner window of the dialog.
39 | *
40 | * @returns {nsIDOMWindow}
41 | * The window object of the configuration dialog.
42 | */
43 | open : function(aURI, aOwner)
44 | {
45 | aURI = this._resolveResURI(aURI);
46 | if (!(aURI in this._configs))
47 | return null;
48 |
49 | var current = this._configs[aURI];
50 |
51 | if (current.openedWindow && !current.openedWindow.closed) {
52 | current.openedWindow.focus();
53 | return current.openedWindow;
54 | }
55 |
56 | var source = Cc['@mozilla.org/variant;1']
57 | .createInstance(Ci.nsIWritableVariant);
58 | source.setFromVariant([this._builder.toSource(), current.source, aURI, current.script, this]);
59 |
60 | if (aOwner) {
61 | let parent = aOwner.top
62 | .QueryInterface(Ci.nsIInterfaceRequestor)
63 | .getInterface(Ci.nsIWebNavigation)
64 | .QueryInterface(Ci.nsIDocShell)
65 | .QueryInterface(Ci.nsIDocShellTreeNode || Ci.nsIDocShellTreeItem) // nsIDocShellTreeNode is merged to nsIDocShellTreeItem by https://bugzilla.mozilla.org/show_bug.cgi?id=331376
66 | .QueryInterface(Ci.nsIDocShellTreeItem)
67 | .parent;
68 | if (parent)
69 | aOwner = parent.QueryInterface(Ci.nsIWebNavigation)
70 | .document
71 | .defaultView;
72 | else
73 | aOwner = null;
74 | }
75 |
76 | var features = 'chrome,titlebar,toolbar,centerscreen' +
77 | (Prefs.getBoolPref('browser.preferences.instantApply') ?
78 | ',dialog=no' :
79 | aOwner ?
80 | ',modal' :
81 | ''
82 | );
83 | var window = Cc['@mozilla.org/embedcomp/window-watcher;1']
84 | .getService(Ci.nsIWindowWatcher)
85 | .openWindow(
86 | aOwner || null,
87 | 'data:application/vnd.mozilla.xul+xml,'+encodeURIComponent(
88 | current.container
89 | ),
90 | '_blank',
91 | features,
92 | source
93 | );
94 | if (features.indexOf('modal') < 0)
95 | return window;
96 | },
97 |
98 | /**
99 | * Registers a source code of a XUL document for a configuration dialog
100 | * to the given URI. It is used by open().
101 | *
102 | * @param {String} aURI
103 | * A URI which is the target URI. When the URI is loaded in a browser
104 | * window, then this system automatically opens a generated XUL window
105 | * from the source.
106 | * @param {Object} aSource
107 | * A source of a XUL document for a configuration dialog defined as a
108 | * or something. Typical headers ( and
109 | * an for the default theme) are automatically added.
110 | * Note: Any elements are ignored or doesn't work as you expected.
111 | * You have to put any script as the third argument.
112 | * @param {String} aScript
113 | * JavaScript codes to be run in the configuration dialog.
114 | */
115 | register : function(aURI, aSource, aScript)
116 | {
117 | if (typeof aScript == 'function')
118 | aScript = aScript.toSource().replace(/^\(?function\s*\(\)\s*\{|\}\)?$/g, '');
119 |
120 | var header = '\n'+
121 | '\n'+
122 | '\n';
123 |
124 | var container;
125 | var source;
126 | if (aSource.toXMLString) { // E4X
127 | let root = aSource.copy();
128 | delete root['*'];
129 | let attributes = root.attributes();
130 | for (let attribute of aAttribute)
131 | {
132 | delete root['@'+attribute.name()];
133 | }
134 | root = root.toXMLString()
135 | .replace(
136 | /<([^ ]+)([^>]+)\/>\s*$/,
137 | '<$1$2>$1>'
138 | );
139 |
140 | let originalSettings = XML.settings();
141 | XML.ignoreWhitespace = true;
142 | XML.prettyPrinting = false;
143 |
144 | container = header+((new XMLList(root)).toXMLString());
145 | source = (new XMLList(aSource.toXMLString())).toXMLString();
146 |
147 | XML.setSettings(originalSettings);
148 | }
149 | else { // string
150 | source = String(aSource);
151 | let root = source
152 | .replace(/^\s+|\s+$/g, '')
153 | .replace(/[\r\n]+/g, ' ')
154 | .replace(/>.+<\/[^>]+/, '/');
155 | let xmlnses = root.match(/xmlns(:[^=]+)\s*=\s*('[^']*'|"[^"]*")/g);
156 | root = root
157 | .replace(/\s+[^ =]+\s*=\s*('[^']*'|"[^"]*")/g, '')
158 | .replace(/(\/>)$/, ' ' + [...xmlnses].join(' ') + '$1')
159 | }
160 |
161 | this._configs[this._resolveResURI(aURI)] = {
162 | container : container,
163 | source : source,
164 | script : aScript || '',
165 | openedWindow : null
166 | };
167 | },
168 | _loader : 'eval(arguments[0][0]+"();"+arguments[0][3]);',
169 | _builder : function()
170 | {
171 | var args = window.arguments[0];
172 | window.config = args[4];
173 | var soruce = args[1];
174 | var sourceURI = args[2];
175 | var root = document.documentElement;
176 | var range = document.createRange();
177 | range.selectNode(root);
178 | var fragment = range.createContextualFragment(soruce);
179 | // clear white-space nodes from XUL tree
180 | (function processNode(aNode) {
181 | [...aNode.childNodes].forEach(processNode);
182 | if (aNode.parentNode &&
183 | aNode.parentNode.namespaceURI == XULNS &&
184 | aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
185 | aNode.nodeValue.replace(/^\s+|\s+$/g, '') == '')
186 | aNode.parentNode.removeChild(aNode);
187 | })(fragment);
188 | document.replaceChild(fragment, root);
189 | range.detach();
190 | window._sourceURI = sourceURI;
191 | },
192 |
193 | /**
194 | * Unregisters a registeed dialog for the given URI.
195 | *
196 | * @param {String} aURI
197 | * A URI which have a registered dialog.
198 | */
199 | unregister : function(aURI)
200 | {
201 | delete this._configs[this._resolveResURI(aURI)];
202 | },
203 |
204 | /**
205 | * Unregisters a default value for the preference.
206 | *
207 | * @param {String} aKey
208 | * A key of preference.
209 | * @param {nsIVariant} aValue
210 | * The default value. This must be a string, integer, or boolean.
211 | */
212 | setDefault : function(aKey, aValue)
213 | {
214 | switch (typeof aValue)
215 | {
216 | case 'string':
217 | return DefaultPrefs.setCharPref(aKey, unescape(encodeURIComponent(aValue)));
218 |
219 | case 'number':
220 | return DefaultPrefs.setIntPref(aKey, parseInt(aValue));
221 |
222 | default:
223 | return DefaultPrefs.setBoolPref(aKey, !!aValue);
224 | }
225 | },
226 |
227 | observe : function(aSubject, aTopic, aData)
228 | {
229 | var uri = aSubject.location.href;
230 | if (
231 | uri == 'about:addons' ||
232 | uri == 'chrome://mozapps/content/extensions/extensions.xul' // Firefox 3.6
233 | ) {
234 | this._onLoadManager(aSubject);
235 | return;
236 | }
237 |
238 | uri = this._resolveResURI(uri);
239 | if (uri in this._configs) {
240 | aSubject.setTimeout('window.close();', 0);
241 | this.open(uri);
242 | }
243 | },
244 |
245 | _resolveResURI : function(aURI)
246 | {
247 | if (aURI.indexOf('resource:') == 0)
248 | return ResProtocolHandler.resolveURI(IOService.newURI(aURI, null, null));
249 | return aURI;
250 | },
251 |
252 | handleEvent : function(aEvent)
253 | {
254 | switch (aEvent.type)
255 | {
256 | case 'unload':
257 | this._onUnloadManager(aEvent.currentTarget);
258 | return;
259 |
260 | case 'command':
261 | let target = aEvent.originalTarget;
262 | let uri;
263 | if (target.getAttribute('anonid') == 'preferences-btn' ||
264 | target.id == 'cmd_showItemPreferences')
265 | uri = target.ownerDocument.defaultView
266 | .gViewController
267 | .currentViewObj
268 | .getSelectedAddon()
269 | .optionsURL;
270 | else if (target.id == 'cmd_options') // Firefox 3.6
271 | uri = target.ownerDocument.defaultView
272 | .gExtensionsView
273 | .currentItem
274 | .getAttribute('optionsURL');
275 | if (uri &&
276 | (uri = this._resolveResURI(uri)) &&
277 | uri in this._configs) {
278 | this.open(uri, target.ownerDocument.defaultView);
279 | aEvent.stopPropagation();
280 | aEvent.preventDefault();
281 | }
282 | return;
283 | }
284 | },
285 | _onLoadManager : function(aWindow)
286 | {
287 | aWindow.addEventListener('command', this, true);
288 | aWindow.addEventListener('unload', this, true);
289 | this._managers.push(aWindow);
290 | },
291 | _onUnloadManager : function(aWindow)
292 | {
293 | aWindow.removeEventListener('command', this, true);
294 | aWindow.removeEventListener('unload', this, true);
295 | this._managers.splice(this._managers.indexOf(aWindow), 1);
296 | },
297 | _managers : []
298 | };
299 |
300 | var Prefs = Cc['@mozilla.org/preferences-service;1']
301 | .getService(Ci.nsIPrefBranch);
302 | var DefaultPrefs = Cc['@mozilla.org/preferences-service;1']
303 | .getService(Ci.nsIPrefService)
304 | .getDefaultBranch(null);
305 |
306 | var IOService = Cc['@mozilla.org/network/io-service;1']
307 | .getService(Ci.nsIIOService);
308 | var ResProtocolHandler = IOService
309 | .getProtocolHandler('resource')
310 | .QueryInterface(Ci.nsIResProtocolHandler);
311 |
312 | var ObserverService = Cc['@mozilla.org/observer-service;1']
313 | .getService(Ci.nsIObserverService);
314 | ObserverService.addObserver(config, 'chrome-document-global-created', false);
315 | ObserverService.addObserver(config, 'content-document-global-created', false);
316 |
317 | var WindowMediator = Cc['@mozilla.org/appshell/window-mediator;1']
318 | .getService(Ci.nsIWindowMediator)
319 | {
320 | let managers = WindowMediator.getEnumerator('Addons:Manager');
321 | while (managers.hasMoreElements())
322 | {
323 | config._onLoadManager(managers.getNext().QueryInterface(Ci.nsIDOMWindow));
324 | }
325 | }
326 | {
327 | let browsers = WindowMediator.getEnumerator('navigator:browser');
328 | while (browsers.hasMoreElements())
329 | {
330 | let browser = browsers.getNext().QueryInterface(Ci.nsIDOMWindow);
331 | if (browser.gBrowser) {
332 | for (let aTab of browser.gBrowser.tabContainer.childNodes)
333 | {
334 | if (aTab.linkedBrowser.currentURI.spec == 'about:addons')
335 | config._onLoadManager(aTab.linkedBrowser.contentWindow);
336 | }
337 | }
338 | }
339 | }
340 | {
341 | let managers = WindowMediator.getEnumerator('Extension:Manager'); // Firefox 3.6
342 | while (managers.hasMoreElements())
343 | {
344 | config._onLoadManager(managers.getNext().QueryInterface(Ci.nsIDOMWindow));
345 | }
346 | }
347 |
348 | function shutdown()
349 | {
350 | var windows = WindowMediator.getEnumerator(null);
351 | while (windows.hasMoreElements())
352 | {
353 | let window = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
354 | if (window._sourceURI && window._sourceURI in config._configs)
355 | window.close();
356 | }
357 |
358 | config._managers.forEach(config._onUnloadManager, config);
359 |
360 | ObserverService.removeObserver(config, 'chrome-document-global-created');
361 | ObserverService.removeObserver(config, 'content-document-global-created');
362 |
363 | Prefs = void(0);
364 | DefaultPrefs = void(0);
365 | IOService = void(0);
366 | ResProtocolHandler = void(0);
367 | ObserverService = void(0);
368 | WindowMediator = void(0);
369 |
370 | config._configs = void(0);
371 | config._managers = void(0);
372 | config = void(0);
373 | }
374 |
--------------------------------------------------------------------------------
/modules/lib/ToolbarItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Toolbar item module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @version 13
5 | *
6 | * @license
7 | * The MIT License, Copyright (c) 2011-2016 YUKI "Piro" Hiroshi.
8 | * https://github.com/piroor/restartless/blob/master/license.txt
9 | * @url http://github.com/piroor/restartless
10 | */
11 |
12 | var EXPORTED_SYMBOLS = ['ToolbarItem'];
13 |
14 | const XULAppInfo = Cc['@mozilla.org/xre/app-info;1']
15 | .getService(Ci.nsIXULAppInfo)
16 | .QueryInterface(Ci.nsIXULRuntime);
17 |
18 | /**
19 | * aDefinition = nsIDOMElement (the toolbar item) ||
20 | * {
21 | * node : nsIDOMElement (the toolbar item),
22 | * toolbar : String (optional: the ID of customizable toolbar),
23 | * onInit : Function (optional: called when the item is inserted to the toolbar),
24 | * onDestroy : Function (optional: called when the item is removed from the toolbar)
25 | * }
26 | */
27 | function ToolbarItem(aDefinition) {
28 | this.init(aDefinition);
29 | }
30 | ToolbarItem.prototype = {
31 | get id()
32 | {
33 | return this.node.id;
34 | },
35 | get inserted()
36 | {
37 | const nsIDOMNode = Ci.nsIDOM3Node || Ci.nsIDOMNode; // on Firefox 7, nsIDOM3Node was merged to nsIDOMNode.
38 | return this._document.compareDocumentPosition(this.node) & nsIDOMNode.DOCUMENT_POSITION_CONTAINED_BY;
39 | },
40 | get defaultToolbar()
41 | {
42 | if (!this._definition.toolbar)
43 | return null;
44 | return typeof this._definition.toolbar === 'string' ?
45 | this._document.getElementById(this._definition.toolbar) :
46 | this._definition.toolbar ;
47 | },
48 | get defaultCustomizableToolbar()
49 | {
50 | return this._getNodeByXPath('/descendant::*[local-name()="toolbar" and @customizable="true"][1]');
51 | },
52 | get toolbox()
53 | {
54 | var toolbar = this.defaultToolbar || this.defaultCustomizableToolbar;
55 | return toolbar.toolbox ||
56 | this._getOwnerToolbox(toolbar); // for Firefox 3.6
57 | },
58 | _getOwnerToolbox : function(aToolbar)
59 | {
60 | return this._getNodeByXPath('ancestor::*[local-name()="toolbox"][1]', aToolbar);
61 | },
62 | get palette()
63 | {
64 | var toolbox = this.toolbox;
65 | return (
66 | (toolbox && toolbox.palette) ||
67 | this._getNodeByXPath('descendant::*[local-name()="toolbarpalette"]', toolbox)
68 | );
69 | },
70 |
71 | init : function(aDefinition)
72 | {
73 | if (this._definition)
74 | return;
75 |
76 | aDefinition = this._normalizeDefinition(aDefinition);
77 | this._assertDefinition(aDefinition);
78 | this._definition = aDefinition;
79 |
80 | this.node = this._definition.node;
81 | this._document = this.node.ownerDocument || this.node;
82 | this._window = this._document.defaultView;
83 |
84 | if (Cc['@mozilla.org/xpcom/version-comparator;1']
85 | .getService(Ci.nsIVersionComparator)
86 | .compare(XULAppInfo.version, '4.0') < 0) { // Firefox 3.6
87 | this.toolbox.addEventListener('DOMAttrModified', this, false);
88 | }
89 | else {
90 | this._window.addEventListener('beforecustomization', this, false);
91 | this._window.addEventListener('aftercustomization', this, false);
92 | }
93 | this._window.addEventListener('unload', this, false);
94 |
95 | ToolbarItem.instances.push(this);
96 |
97 | this._initialInsert();
98 |
99 | this._onAfterCustomization();
100 | },
101 |
102 | destroy : function()
103 | {
104 | if (!this._definition)
105 | return;
106 |
107 | this._onBeforeCustomization();
108 | this._removeFromDefaultSet();
109 |
110 | if (Cc['@mozilla.org/xpcom/version-comparator;1']
111 | .getService(Ci.nsIVersionComparator)
112 | .compare(XULAppInfo.version, '4.0') < 0) { // Firefox 3.6
113 | this.toolbox.removeEventListener('DOMAttrModified', this, false);
114 | }
115 | else {
116 | this._window.removeEventListener('beforecustomization', this, false);
117 | this._window.removeEventListener('aftercustomization', this, false);
118 | }
119 | this._window.removeEventListener('unload', this, false);
120 |
121 | if (this.node.parentNode)
122 | this.node.parentNode.removeChild(this.node);
123 |
124 | delete this._definition;
125 | delete this.node;
126 | delete this._document;
127 | delete this._window;
128 |
129 | ToolbarItem.instances = ToolbarItem.instances.filter(function(aItem) {
130 | return aItem != this;
131 | }, this);
132 | },
133 |
134 | _normalizeDefinition : function(aDefinition)
135 | {
136 | var ns = aDefinition && aDefinition.ownerDocument && aDefinition.ownerDocument.defaultView;
137 | var Element = ns && typeof ns.Element == 'function' ? ns.Element : null ;
138 | if (aDefinition &&
139 | typeof Element == 'function' &&
140 | aDefinition instanceof Element)
141 | aDefinition = { node : aDefinition };
142 | if (aDefinition.element && !aDefinition.node)
143 | aDefinition.node = aDefinition.element;
144 |
145 | aDefinition.node.setAttribute('removable', true);
146 | aDefinition.node.setAttribute('class', aDefinition.node.className+' platform-'+XULAppInfo.OS);
147 |
148 | if (aDefinition.oninit && !aDefinition.onInit)
149 | aDefinition.onInit = aDefinition.oninit;
150 | if (aDefinition.init && !aDefinition.onInit)
151 | aDefinition.onInit = aDefinition.init;
152 |
153 | if (aDefinition.ondestroy && !aDefinition.onDestroy)
154 | aDefinition.onDestroy = aDefinition.ondestroy;
155 | if (aDefinition.destroy && !aDefinition.onDestroy)
156 | aDefinition.onDestroy = aDefinition.destroy;
157 |
158 | if (aDefinition.toolbar &&
159 | typeof Element == 'function' &&
160 | aDefinition.toolbar instanceof Element)
161 | aDefinition.toolbar = aDefinition.toolbar.id;
162 |
163 | return aDefinition;
164 | },
165 |
166 | _assertDefinition : function(aDefinition)
167 | {
168 | if (!aDefinition.node)
169 | throw new Error('"node", the toolbar item DOM element is required!');
170 | if (!aDefinition.node.id)
171 | throw new Error('"node", the toolbar item DOM element must have ID!');
172 | },
173 |
174 |
175 | _initialInsert : function()
176 | {
177 | const Prefs = Cc['@mozilla.org/preferences;1']
178 | .getService(Ci.nsIPrefBranch);
179 | const key = 'extensions.restartless@piro.sakura.ne.jp.toolbaritem.'+this.id+'.initialized';
180 |
181 | var done = false;
182 | try {
183 | done = Prefs.getBoolPref(key);
184 | }
185 | catch(e) {
186 | }
187 |
188 | this._appendToDefaultSet();
189 |
190 | let palette = this.palette;
191 | if (palette)
192 | palette.appendChild(this.node);
193 |
194 | if (!this._checkInsertedInOtherPlace()) {
195 | let toolbar = this.defaultToolbar;
196 | if (!done && toolbar && this.toolbox)
197 | this._insertToDefaultToolbar();
198 | }
199 |
200 | if (!done)
201 | Prefs.setBoolPref(key, true);
202 | },
203 |
204 | _appendToDefaultSet : function()
205 | {
206 | var toolbar = this.defaultToolbar;
207 | if (!toolbar)
208 | return;
209 |
210 | var items = (toolbar.getAttribute('defaultset') || '').split(',');
211 | if (items.indexOf(this.id) > -1)
212 | return;
213 |
214 | var lastItem = this._getLastItemInToolbar(toolbar);
215 | if (lastItem) {
216 | let index = items.indexOf(lastItem.id);
217 | if (index > -1) {
218 | items.splice(index, 0, this.id);
219 | }
220 | else {
221 | items.push(this.id);
222 | }
223 | }
224 | else {
225 | items.push(this.id);
226 | }
227 | toolbar.setAttribute('defaultset', items.join(','));
228 | },
229 |
230 | _removeFromDefaultSet : function()
231 | {
232 | var toolbar = this.defaultToolbar;
233 |
234 | var items = (toolbar.getAttribute('defaultset') || '').split(',');
235 | var index = items.indexOf(this.id);
236 | if (index < 0)
237 | return;
238 |
239 | items.splice(index, 1);
240 | toolbar.setAttribute('defaultset', items.join(','));
241 | },
242 |
243 | _checkInsertedInOtherPlace : function()
244 | {
245 | var toolbar = this._getNodeByXPath('/descendant::*[local-name()="toolbar" and contains(concat(",",@currentset,","), '+JSON.stringify(this.id)+')]');
246 | if (!toolbar)
247 | return false;
248 |
249 | if (!this.inserted) {
250 | let items = (toolbar.getAttribute('currentset') || '').split(',');
251 |
252 | let index = items.indexOf(this.id);
253 | do {
254 | index++;
255 | }
256 | while (index < items.length - 1 && !this._document.getElementById(items[index]));
257 |
258 | toolbar.insertBefore(this.node, this._document.getElementById(items[index]));
259 | }
260 | return true;
261 | },
262 |
263 | _insertToDefaultToolbar : function()
264 | {
265 | var toolbar = this.defaultToolbar;
266 | if (!toolbar)
267 | return;
268 |
269 | var currentset = toolbar.currentSet.replace(/__empty/, '');
270 | currentset = currentset ? currentset.split(',') : [] ;
271 | currentset.push(this.id);
272 | currentset = currentset.join(',');
273 | toolbar.currentSet = currentset;
274 | toolbar.setAttribute('currentset', currentset);
275 | this._document.persist(toolbar.id, 'currentset');
276 |
277 | var owner = toolbar.customizationTarget || target;
278 | owner.insertBefore(this.node, this._getLastItemInToolbar(owner));
279 | },
280 |
281 | _getLastItemInToolbar : function(aToolbar)
282 | {
283 | return this._getNodeByXPath('descendant::*[@id="fullscreenflex" or @id="window-controls"][1]', aToolbar);
284 | },
285 |
286 | _getNodeByXPath : function(aExpression, aContext)
287 | {
288 | return this._document.evaluate(
289 | aExpression,
290 | aContext || this._document,
291 | null,
292 | Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE,
293 | null
294 | ).singleNodeValue;
295 | },
296 |
297 |
298 | handleEvent : function(aEvent)
299 | {
300 | switch (aEvent.type)
301 | {
302 | case 'DOMAttrModified': // Firefox 3.6
303 | if (aEvent.target == aEvent.currentTarget &&
304 | aEvent.attrName == 'customizing') {
305 | if (aEvent.newValue == 'true')
306 | this._onBeforeCustomization();
307 | else
308 | this._onAfterCustomization();
309 | }
310 | return;
311 |
312 | case 'beforecustomization':
313 | return this._onBeforeCustomization();
314 |
315 | case 'aftercustomization':
316 | return this._onAfterCustomization();
317 |
318 | case 'unload':
319 | return this.destroy();
320 | }
321 | },
322 |
323 | _onBeforeCustomization : function()
324 | {
325 | if (this._definition && this._definition.onDestroy && this.inserted)
326 | this._definition.onDestroy.call(this);
327 | },
328 |
329 | _onAfterCustomization : function()
330 | {
331 | if (this._definition && this._definition.onInit && this.inserted)
332 | this._definition.onInit.call(this);
333 | },
334 |
335 |
336 | addEventListener : function(aType, aListener, aUseCapture, aAcceptUnsafeEvents)
337 | {
338 | return this.node.addEventListener(aType, aListener, aUseCapture, aAcceptUnsafeEvents);
339 | },
340 |
341 | removeEventListener : function(aType, aListener, aUseCapture, aAcceptUnsafeEvents)
342 | {
343 | return this.node.removeEventListener(aType, aListener, aUseCapture, aAcceptUnsafeEvents);
344 | }
345 | };
346 |
347 | ToolbarItem.instances = [];
348 |
349 | ToolbarItem.BASIC_ITEM_CLASS = 'toolbarbutton-1 chromeclass-toolbar-additional';
350 | ToolbarItem.XULNS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
351 |
352 | /**
353 | * @param {Object} aSource
354 | * A source of a XUL element for a toolbar item as a string or something.
355 | * @param {nsIDOMNode} aOwner
356 | * A owner document or a toolbar element which becomes to the parent of the created item.
357 | * @param {Object} aOptions
358 | * A options for the ToolbarItem constructor.
359 | */
360 | ToolbarItem.create = function(aSource, aOwner, aOptions) {
361 | aOptions = aOptions || {};
362 | var item = aSource;
363 | var ns = aSource && aSource.ownerDocument && aSource.ownerDocument.defaultView;
364 | if (!ns && aOwner && aOwner.ownerDocument)
365 | ns = aOwner.ownerDocument && aOwner.ownerDocument.defaultView;
366 | var Element = ns && typeof ns.Element == 'function' ? ns.Element : null ;
367 | if (!(Element && aSource instanceof Element)) {
368 | let fragment = this.toDOMDocumentFragment(aSource, aOwner);
369 | aOptions.node = item = fragment.querySelector('*');
370 | }
371 | if (item.parentNode) // remove topmost document fragment
372 | aOptions.node = item.parentNode.removeChild(item);
373 | if (Element && aOwner instanceof Element && aOwner.localName == 'toolbar')
374 | aOptions.toolbar = aOwner;
375 | return new ToolbarItem(aOptions);
376 | };
377 |
378 | /**
379 | * @param {Object} aSource
380 | * A source of a XUL document fragment as a string or something.
381 | * @param {nsIDOMNode} aOwner
382 | * A owner document or a XUL element which becomes to the parent of the created document fragment.
383 | */
384 | ToolbarItem.toDOMDocumentFragment = function(aSource, aOwner) {
385 | try{
386 | var doc = aOwner.ownerDocument || aOwner;
387 | var range = doc.createRange();
388 | // createContextualFragment failes when the range is in an anonymous content.
389 | range.selectNodeContents(doc.getBindingParent(aOwner) || aOwner);
390 |
391 | var fragment;
392 | if (aSource.toXMLString) { // E4X
393 | var originalSettings = XML.settings();
394 | XML.ignoreWhitespace = true;
395 | XML.prettyPrinting = false;
396 | fragment = range.createContextualFragment(aSource.toXMLString());
397 | XML.setSettings(originalSettings);
398 | }
399 | else { // string
400 | fragment = range.createContextualFragment(String(aSource));
401 | // clear white-space nodes from XUL tree
402 | (function processNode(aNode) {
403 | [...aNode.childNodes].forEach(processNode);
404 | if (aNode.parentNode &&
405 | aNode.parentNode.namespaceURI == ToolbarItem.XULNS &&
406 | aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
407 | aNode.nodeValue.replace(/^\s+|\s+$/g, '') == '')
408 | aNode.parentNode.removeChild(aNode);
409 | })(fragment);
410 | }
411 |
412 | range.detach();
413 | }catch(e){dump(e+'\n\n'+(aSource.toXMLString ? aSource.toXMLString() : aSource)+'\n');}
414 | return fragment;
415 | };
416 |
417 | /** A handler for bootstrap.js */
418 | function shutdown()
419 | {
420 | ToolbarItem.instances.slice(0).forEach(function(aItem) {
421 | aItem.destroy();
422 | });
423 | ToolbarItem = undefined;
424 | }
425 |
--------------------------------------------------------------------------------
/components/loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview Loader module for restartless addons
3 | * @author YUKI "Piro" Hiroshi
4 | * @contributor Infocatcher
5 | * @version 17
6 | *
7 | * @license
8 | * The MIT License, Copyright (c) 2010-2017 YUKI "Piro" Hiroshi.
9 | * https://github.com/piroor/restartless/blob/master/license.txt
10 | * @url http://github.com/piroor/restartless
11 | */
12 |
13 | function toPropertyDescriptors(aProperties) {
14 | var descriptors = {};
15 | Object.keys(aProperties).forEach(function(aProperty) {
16 | var description = Object.getOwnPropertyDescriptor(aProperties, aProperty);
17 | descriptors[aProperty] = description;
18 | });
19 | return descriptors;
20 | }
21 |
22 | function inherit(aParent, aExtraProperties) {
23 | var global;
24 | if (Components.utils.getGlobalForObject)
25 | global = Components.utils.getGlobalForObject(aParent);
26 | else
27 | global = aParent.valueOf.call();
28 | global = global || this;
29 |
30 | var ObjectClass = global.Object || Object;
31 | if (aExtraProperties)
32 | return ObjectClass.create(aParent, toPropertyDescriptors(aExtraProperties));
33 | else
34 | return ObjectClass.create(aParent);
35 | }
36 |
37 | // import base64 utilities from the js code module namespace
38 | try {
39 | var { atob, btoa } = Components.utils.import('resource://gre/modules/Services.jsm', {});
40 | } catch(e) {
41 | Components.utils.reportError(new Error('failed to load Services.jsm'));
42 | }
43 | try {
44 | var { console } = Components.utils.import('resource://gre/modules/devtools/Console.jsm', {});
45 | } catch(e) {
46 | Components.utils.reportError(new Error('failed to load Console.jsm'));
47 | }
48 |
49 | var { Promise } = Components.utils.import('resource://gre/modules/Promise.jsm', {});
50 |
51 | var _namespacePrototype = {
52 | Cc : Components.classes,
53 | Ci : Components.interfaces,
54 | Cu : Components.utils,
55 | Cr : Components.results,
56 | console : this.console,
57 | btoa : function(aInput) {
58 | return btoa(aInput);
59 | },
60 | atob : function(aInput) {
61 | return atob(aInput);
62 | },
63 | inherit : function(aParent, aExtraProperties) {
64 | return inherit(aParent, aExtraProperties);
65 | },
66 | Promise : Promise,
67 | };
68 | var _namespaces;
69 |
70 | /**
71 | * This functiom loads specified script into a unique namespace for the URL.
72 | * Namespaces for loaded scripts have a wrapped version of this function.
73 | * Both this and wrapped work like as Components.utils.import().
74 | * Due to the reserved symbol "import", we have to use another name "load"
75 | * instead it.
76 | *
77 | * @param {String} aScriptURL
78 | * URL of a script. Wrapped version of load() can handle related path.
79 | * Related path will be resolved based on the location of the caller script.
80 | * @param {Object=} aExportTargetForImport
81 | * EXPORTED_SYMBOLS in the loaded script will be exported to the object.
82 | * If no object is specified, symbols will be exported to the global object
83 | * of the caller.
84 | * @param {Object=} aExportTargetForRequire
85 | * Properties of "exports" in the loaded script will be exported to the object.
86 | *
87 | * @returns {Object}
88 | * The global object for the loaded script.
89 | */
90 | function load(aURISpec, aExportTargetForImport, aExportTargetForRequire, aRoot)
91 | {
92 | if (!_namespaces)
93 | _namespaces = {};
94 | var ns;
95 | if (aURISpec in _namespaces) {
96 | ns = _namespaces[aURISpec];
97 | _exportForImport(ns, aExportTargetForImport);
98 | _exportForRequire(ns, aExportTargetForRequire);
99 | return ns;
100 | }
101 | ns = _createNamespace(aURISpec, aRoot || aURISpec);
102 | try {
103 | Components.classes['@mozilla.org/moz/jssubscript-loader;1']
104 | .getService(Components.interfaces.mozIJSSubScriptLoader)
105 | .loadSubScript(aURISpec, ns);
106 | }
107 | catch(e) {
108 | let message = 'Loader::load('+aURISpec+') failed!\n'+e+'\n';
109 | dump(message);
110 | Components.utils.reportError(message + e.stack.replace(/( -> )/g, '\n$1'));
111 | throw e;
112 | }
113 | _exportForImport(ns, aExportTargetForImport);
114 | _exportForRequire(ns, aExportTargetForRequire);
115 | return _namespaces[aURISpec] = ns;
116 | }
117 |
118 | // JavaScript code module style
119 | function _exportForImport(aSource, aTarget)
120 | {
121 | if (
122 | !aTarget ||
123 | !('EXPORTED_SYMBOLS' in aSource) ||
124 | !aSource.EXPORTED_SYMBOLS ||
125 | !aSource.EXPORTED_SYMBOLS.forEach
126 | )
127 | return;
128 | for (let symbol of aSource.EXPORTED_SYMBOLS)
129 | {
130 | aTarget[symbol] = aSource[symbol];
131 | }
132 | }
133 |
134 | // CommonJS style
135 | function _exportForRequire(aSource, aTarget)
136 | {
137 | if (
138 | !aTarget ||
139 | !('exports' in aSource) ||
140 | !aSource.exports ||
141 | typeof aSource.exports != 'object'
142 | )
143 | return;
144 | for (var symbol in aSource.exports)
145 | {
146 | aTarget[symbol] = aSource.exports[symbol];
147 | }
148 | }
149 |
150 | var IOService = Components.classes['@mozilla.org/network/io-service;1']
151 | .getService(Components.interfaces.nsIIOService);
152 | var FileHandler = IOService.getProtocolHandler('file')
153 | .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
154 |
155 | /**
156 | * Checks existence of the file specified by the given relative path and the base URI.
157 | *
158 | * @param {String} aPath
159 | * A relative path to a file or directory, from the aBaseURI.
160 | * @param {String} aBaseURI
161 | * An absolute URI (with scheme) for relative paths.
162 | *
163 | * @returns {String}
164 | * If the file (or directory) exists, returns the absolute URI. Otherwise null.
165 | */
166 | function exists(aPath, aBaseURI)
167 | {
168 | if (/^\w+:/.test(aPath)) {
169 | let leafName = aPath.match(/([^\/]+)$/);
170 | leafName = leafName ? leafName[1] : '' ;
171 | aBaseURI = aPath.replace(/(?:[^\/]+)$/, '');
172 | aPath = leafName;
173 | }
174 | var baseURI = aBaseURI.indexOf('file:') == 0 ?
175 | IOService.newFileURI(FileHandler.getFileFromURLSpec(aBaseURI)) :
176 | IOService.newURI(aBaseURI, null, null);
177 | if (aBaseURI.indexOf('jar:') == 0) {
178 | baseURI = baseURI.QueryInterface(Components.interfaces.nsIJARURI);
179 | var reader = Components.classes['@mozilla.org/libjar/zip-reader;1']
180 | .createInstance(Components.interfaces.nsIZipReader);
181 | reader.open(baseURI.JARFile.QueryInterface(Components.interfaces.nsIFileURL).file);
182 | try {
183 | let baseEntry = baseURI.JAREntry.replace(/[^\/]+$/, '');
184 | let entries = reader.findEntries(baseEntry + aPath + '$');
185 | let found = entries.hasMore();
186 | return found ? baseURI.resolve(aPath) : null ;
187 | }
188 | finally {
189 | reader.close();
190 | }
191 | }
192 | else {
193 | let resolved = baseURI.resolve(aPath);
194 | return FileHandler.getFileFromURLSpec(resolved).exists() ? resolved : null ;
195 | }
196 | }
197 |
198 | function _readFrom(aURISpec, aEncoding)
199 | {
200 | const Cc = Components.classes;
201 | const Ci = Components.interfaces;
202 |
203 | var uri = aURISpec.indexOf('file:') == 0 ?
204 | IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
205 | IOService.newURI(aURISpec, null, null) ;
206 | var channel = IOService.newChannelFromURI(uri.QueryInterface(Ci.nsIURI));
207 | var stream = channel.open();
208 |
209 | var fileContents = null;
210 | try {
211 | if (aEncoding) {
212 | var converterStream = Cc['@mozilla.org/intl/converter-input-stream;1']
213 | .createInstance(Ci.nsIConverterInputStream);
214 | var buffer = stream.available();
215 | converterStream.init(stream, aEncoding, buffer,
216 | converterStream.DEFAULT_REPLACEMENT_CHARACTER);
217 | var out = { value : null };
218 | converterStream.readString(stream.available(), out);
219 | converterStream.close();
220 | fileContents = out.value;
221 | }
222 | else {
223 | var scriptableStream = Cc['@mozilla.org/scriptableinputstream;1']
224 | .createInstance(Ci.nsIScriptableInputStream);
225 | scriptableStream.init(stream);
226 | fileContents = scriptableStream.read(scriptableStream.available());
227 | scriptableStream.close();
228 | }
229 | }
230 | finally {
231 | stream.close();
232 | }
233 | return fileContents;
234 | }
235 |
236 | function _createNamespace(aURISpec, aRoot)
237 | {
238 | var baseURI = aURISpec.indexOf('file:') == 0 ?
239 | IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
240 | IOService.newURI(aURISpec, null, null);
241 | var rootURI = typeof aRoot == 'string' ?
242 | (aRoot.indexOf('file:') == 0 ?
243 | IOService.newFileURI(FileHandler.getFileFromURLSpec(aRoot)) :
244 | IOService.newURI(aRoot, null, null)
245 | ) :
246 | aRoot ;
247 | var ns = inherit(_namespacePrototype, {
248 | location : _createFakeLocation(baseURI),
249 | exists : function(aPath, aBase) {
250 | return exists(aPath, aBase || baseURI.spec);
251 | },
252 | exist : function(aPath, aBase) { // alias
253 | return exists(aPath, aBase || baseURI.spec);
254 | },
255 | /** JavaScript code module style */
256 | load : function(aURISpec, aExportTarget) {
257 | if (!/\.jsm?$/.test(aURISpec)) {
258 | if (exists(aURISpec+'.js', baseURI.spec))
259 | aURISpec += '.js'
260 | else if (exists(aURISpec+'.jsm', baseURI.spec))
261 | aURISpec += '.jsm'
262 | }
263 | var resolved = baseURI.resolve(aURISpec);
264 | if (resolved == aURISpec)
265 | throw new Error('Recursive load!');
266 | return load(resolved, aExportTarget || ns, aExportTarget, rootURI);
267 | },
268 | 'import' : function() { // alias
269 | return this.load.apply(this, arguments);
270 | },
271 | /**
272 | * CommonJS style
273 | * @url http://www.commonjs.org/specs/
274 | */
275 | require : function(aURISpec) {
276 | if (!/\.jsm?$/.test(aURISpec)) {
277 | if (exists(aURISpec+'.js', baseURI.spec))
278 | aURISpec += '.js'
279 | else if (exists(aURISpec+'.jsm', baseURI.spec))
280 | aURISpec += '.jsm'
281 | }
282 | var resolved = (aURISpec.charAt(0) == '.' ? rootURI : baseURI ).resolve(aURISpec);
283 | if (resolved == aURISpec)
284 | throw new Error('Recursive load!');
285 | var exported = {};
286 | load(resolved, exported, exported, rootURI);
287 | return exported;
288 | },
289 | /* utility to resolve relative path from the file */
290 | resolve : function(aURISpec, aBaseURI) {
291 | var base = !aBaseURI ?
292 | baseURI :
293 | aBaseURI.indexOf('file:') == 0 ?
294 | IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
295 | IOService.newURI(aURISpec, null, null) ;
296 | return base.resolve(aURISpec);
297 | },
298 | /* utility to read contents of a text file */
299 | read : function(aURISpec, aEncoding, aBaseURI) {
300 | return _readFrom(this.resolve(aURISpec, aBaseURI), aEncoding);
301 | },
302 | exports : {}
303 | });
304 | return ns;
305 | }
306 |
307 | function _createFakeLocation(aURI)
308 | {
309 | aURI = aURI.QueryInterface(Components.interfaces.nsIURL)
310 | .QueryInterface(Components.interfaces.nsIURI);
311 | return {
312 | href : aURI.spec,
313 | search : aURI.query ? '?'+aURI.query : '' ,
314 | hash : aURI.ref ? '#'+aURI.ref : '' ,
315 | host : aURI.scheme == 'jar' ? '' : aURI.hostPort,
316 | hostname : aURI.scheme == 'jar' ? '' : aURI.host,
317 | port : aURI.scheme == 'jar' ? -1 : aURI.port,
318 | pathname : aURI.path,
319 | protocol : aURI.scheme+':',
320 | reload : function() {},
321 | replace : function() {},
322 | toString : function() {
323 | return this.href;
324 | }
325 | };
326 | }
327 |
328 | function _callHandler(aHandler, aReason)
329 | {
330 | var handlers = [];
331 | for (var i in _namespaces)
332 | {
333 | if (_namespaces[i][aHandler] &&
334 | typeof _namespaces[i][aHandler] == 'function')
335 | handlers.push({
336 | key : i,
337 | namespace : _namespaces[i],
338 | handler : _namespaces[i][aHandler]
339 | });
340 | }
341 |
342 | return new Promise(function(aResolve, aReject) {
343 | var processHandler = function() {
344 | var handler = handlers.shift();
345 | if (!handler)
346 | return aResolve();
347 |
348 | try {
349 | var result = handler.handler.call(handler.namespace, aReason);
350 | }
351 | catch(e) {
352 | let message = i+'('+aHandler+', '+aReason+')\n'+e+'\n';
353 | dump(message);
354 | Components.utils.reportError(message + e.stack.replace(/( -> )/g, '\n$1'));
355 | }
356 |
357 | if (result && typeof result.then == 'function') {
358 | result.then(processHandler);
359 | }
360 | else {
361 | processHandler();
362 | }
363 | };
364 | processHandler();
365 | });
366 | }
367 |
368 | function registerResource(aName, aRoot)
369 | {
370 | IOService.getProtocolHandler('resource')
371 | .QueryInterface(Components.interfaces.nsIResProtocolHandler)
372 | .setSubstitution(aName, aRoot);
373 | }
374 |
375 | function unregisterResource(aName)
376 | {
377 | IOService.getProtocolHandler('resource')
378 | .QueryInterface(Components.interfaces.nsIResProtocolHandler)
379 | .setSubstitution(aName, null);
380 | }
381 |
382 | /** Handler for "install" of the bootstrap.js */
383 | function install(aReason)
384 | {
385 | _callHandler('install', aReason);
386 | }
387 |
388 | /** Handler for "uninstall" of the bootstrap.js */
389 | function uninstall(aReason)
390 | {
391 | _callHandler('uninstall', aReason);
392 | }
393 |
394 | /** Handler for "shutdown" of the bootstrap.js */
395 | function shutdown(aReason)
396 | {
397 | _callHandler('shutdown', aReason)
398 | .then(function() {
399 | for (let ns of _namespaces)
400 | {
401 | for (let i in ns.exports)
402 | {
403 | if (ns.exports.hasOwnProperty(i))
404 | delete ns.exports[i];
405 | }
406 | }
407 | _namespaces = void(0);
408 | _namespacePrototype = void(0);
409 |
410 | IOService = void(0);
411 | FileHandler = void(0);
412 | Promise = void(0);
413 |
414 | load = void(0);
415 | _exportSymbols = void(0);
416 | exists = void(0);
417 | _createNamespace = void(0);
418 | _callHandler = void(0);
419 | registerResource = void(0);
420 | unregisterResource = void(0);
421 | install = void(0);
422 | uninstall = void(0);
423 | shutdown = void(0);
424 | });
425 | }
426 |
--------------------------------------------------------------------------------