Bug 1667999 - [devtools] Land in-tree devtools-modules package. r=jdescottes

/!\ in order to take the new package in packages/ folder in account,
you have to run `yarn` before `node bin/bundle.js`

Differential Revision: https://phabricator.services.mozilla.com/D91732
This commit is contained in:
Alexandre Poirot 2020-09-30 17:38:50 +00:00
Родитель d7ed96dfed
Коммит eb3020eed5
31 изменённых файлов: 5703 добавлений и 1737 удалений

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

@ -6,5 +6,6 @@ src/**/fixtures/**
src/test/mochitest/**
bin/
packages/**/fixtures/**
packages/devtools-modules/**
node_modules
out

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

@ -684,7 +684,23 @@
"external \"\\n(() => {\\n let factory;\\n function define(...args) {\\n if (factory) {\\n throw new Error(\\\"expected a single define call\\\");\\n }\\n\\n if (\\n args.length !== 2 ||\\n !Array.isArray(args[0]) ||\\n args[0].length !== 0 ||\\n typeof args[1] !== \\\"function\\\"\\n ) {\\n throw new Error(\\\"whatwg-url had unexpected factory arguments.\\\");\\n }\\n\\n factory = args[1];\\n }\\n define.amd = true;\\n\\n const existingDefine = Object.getOwnPropertyDescriptor(globalThis, \\\"define\\\");\\n globalThis.define = define;\\n let err;\\n try {\\n importScripts(\\\"resource://devtools/client/shared/vendor/whatwg-url.js\\\");\\n\\n if (!factory) {\\n throw new Error(\\\"Failed to load whatwg-url factory\\\");\\n }\\n } finally {\\n if (existingDefine) {\\n Object.defineProperty(globalThis, \\\"define\\\", existingDefine);\\n } else {\\n delete globalThis.define;\\n }\\n\\n }\\n\\n return factory();\\n})()\\n\"": 533,
"../../@babel/types/lib/builders/flow/createFlowUnionType.js": 534,
"../../@babel/types/lib/builders/typescript/createTSUnionType.js": 535,
"../../@babel/types/lib/modifications/typescript/removeTypeDuplicates.js": 536
"../../@babel/types/lib/modifications/typescript/removeTypeDuplicates.js": 536,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/utils/event-emitter.js": 537,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/index.js": 538,
"../../../packages/devtools-modules/src/prefs.js": 539,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/utils/promise.js": 540,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/key-shortcuts.js": 541,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/zoom-keys.js": 542,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/async-storage.js": 543,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/source-utils.js": 544,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/utils/telemetry.js": 545,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/unicode-url.js": 546,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/plural-form.js": 547,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/saveAs.js": 548,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-modules/src/async-store-helper.js": 549,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-config/index.js": 550,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-config/src/feature.js": 551,
"../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-environment/index.js": 552
},
"usedIds": {
"0": 0,
@ -1223,7 +1239,23 @@
"533": 533,
"534": 534,
"535": 535,
"536": 536
"536": 536,
"537": 537,
"538": 538,
"539": 539,
"540": 540,
"541": 541,
"542": 542,
"543": 543,
"544": 544,
"545": 545,
"546": 546,
"547": 547,
"548": 548,
"549": 549,
"550": 550,
"551": 551,
"552": 552
}
},
"chunks": {

3449
devtools/client/debugger/dist/vendors.js поставляемый

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

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

@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

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

@ -0,0 +1,8 @@
## Devtools Modules
[![Npm version](https://img.shields.io/npm/v/devtools-modules.svg)](https://npmjs.org/package/devtools-modules)
* *KeyShortcuts* - keyboard shortcuts library
* *Menu* - Context Menu library
* *Services* - A de-privilidged shim for prefs and appInfo
* *PrefsHelper* - A Prefs convenience Wrapper

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

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { PrefsHelper } = require("./src/prefs");
const KeyShortcuts = require("./src/key-shortcuts");
const { ZoomKeys } = require("./src/zoom-keys");
const EventEmitter = require("./src/utils/event-emitter");
const asyncStorage = require("./src/async-storage");
const asyncStoreHelper = require("./src/async-store-helper");
const SourceUtils = require("./src/source-utils");
const Telemetry = require("./src/utils/telemetry");
const { getUnicodeHostname, getUnicodeUrlPath, getUnicodeUrl } =
require("./src/unicode-url");
const PluralForm = require("./src/plural-form");
const saveAs = require("./src/saveAs")
module.exports = {
KeyShortcuts,
PrefsHelper,
ZoomKeys,
asyncStorage,
asyncStoreHelper,
EventEmitter,
SourceUtils,
Telemetry,
getUnicodeHostname,
getUnicodeUrlPath,
getUnicodeUrl,
PluralForm,
saveAs
};

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

@ -0,0 +1,37 @@
{
"name": "devtools-modules",
"version": "1.1.9",
"description": "DevTools Modules from M-C",
"main": "index.js",
"scripts": {
"license-check": "devtools-license-check",
"test": "jest"
},
"author": "",
"license": "MPL-2.0",
"dependencies": {
"punycode": "^2.1.0",
"devtools-services": "0.0.1"
},
"devDependencies": {
"jest": "^23.0.0"
},
"files": [
"src"
],
"jest": {
"rootDir": "src",
"testMatch": [
"**/tests/**/*.js"
],
"testPathIgnorePatterns": [
"<rootDir>/tests/helpers/local-storage-mock.js"
],
"transformIgnorePatterns": [],
"setupFiles": [
"<rootDir>/tests/helpers/local-storage-mock.js"
],
"moduleNameMapper": {},
"testURL": "http://localhost/"
}
}

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

@ -0,0 +1,214 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/**
*
* Adapted from https://github.com/mozilla-b2g/gaia/blob/f09993563fb5fec4393eb71816ce76cb00463190/shared/js/async_storage.js
* (converted to use Promises instead of callbacks).
*
* This file defines an asynchronous version of the localStorage API, backed by
* an IndexedDB database. It creates a global asyncStorage object that has
* methods like the localStorage object.
*
* To store a value use setItem:
*
* asyncStorage.setItem("key", "value");
*
* This returns a promise in case you want confirmation that the value has been stored.
*
* asyncStorage.setItem("key", "newvalue").then(function() {
* console.log("new value stored");
* });
*
* To read a value, call getItem(), but note that you must wait for a promise
* resolution for the value to be retrieved.
*
* asyncStorage.getItem("key").then(function(value) {
* console.log("The value of key is:", value);
* });
*
* Note that unlike localStorage, asyncStorage does not allow you to store and
* retrieve values by setting and querying properties directly. You cannot just
* write asyncStorage.key; you have to explicitly call setItem() or getItem().
*
* removeItem(), clear(), length(), and key() are like the same-named methods of
* localStorage, and all return a promise.
*
* The asynchronous nature of getItem() makes it tricky to retrieve multiple
* values. But unlike localStorage, asyncStorage does not require the values you
* store to be strings. So if you need to save multiple values and want to
* retrieve them together, in a single asynchronous operation, just group the
* values into a single object. The properties of this object may not include
* DOM elements, but they may include things like Blobs and typed arrays.
*
*/
"use strict";
const DBNAME = "devtools-async-storage";
const DBVERSION = 1;
const STORENAME = "keyvaluepairs";
var db = null;
function withStore(type, onsuccess, onerror) {
if (db) {
const transaction = db.transaction(STORENAME, type);
const store = transaction.objectStore(STORENAME);
onsuccess(store);
} else {
const openreq = indexedDB.open(DBNAME, DBVERSION);
openreq.onerror = function withStoreOnError() {
onerror();
};
openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() {
// First time setup: create an empty object store
openreq.result.createObjectStore(STORENAME);
};
openreq.onsuccess = function withStoreOnSuccess() {
db = openreq.result;
const transaction = db.transaction(STORENAME, type);
const store = transaction.objectStore(STORENAME);
onsuccess(store);
};
}
}
function getItem(itemKey) {
return new Promise((resolve, reject) => {
let req;
withStore(
"readonly",
store => {
store.transaction.oncomplete = function onComplete() {
let value = req.result;
if (value === undefined) {
value = null;
}
resolve(value);
};
req = store.get(itemKey);
req.onerror = function getItemOnError() {
reject("Error in asyncStorage.getItem(): ", req.error.name);
};
},
reject
);
});
}
function setItem(itemKey, value) {
return new Promise((resolve, reject) => {
withStore(
"readwrite",
store => {
store.transaction.oncomplete = resolve;
const req = store.put(value, itemKey);
req.onerror = function setItemOnError() {
reject("Error in asyncStorage.setItem(): ", req.error.name);
};
},
reject
);
});
}
function removeItem(itemKey) {
return new Promise((resolve, reject) => {
withStore(
"readwrite",
store => {
store.transaction.oncomplete = resolve;
const req = store.delete(itemKey);
req.onerror = function removeItemOnError() {
reject("Error in asyncStorage.removeItem(): ", req.error.name);
};
},
reject
);
});
}
function clear() {
return new Promise((resolve, reject) => {
withStore(
"readwrite",
store => {
store.transaction.oncomplete = resolve;
const req = store.clear();
req.onerror = function clearOnError() {
reject("Error in asyncStorage.clear(): ", req.error.name);
};
},
reject
);
});
}
function length() {
return new Promise((resolve, reject) => {
let req;
withStore(
"readonly",
store => {
store.transaction.oncomplete = function onComplete() {
resolve(req.result);
};
req = store.count();
req.onerror = function lengthOnError() {
reject("Error in asyncStorage.length(): ", req.error.name);
};
},
reject
);
});
}
function key(n) {
return new Promise((resolve, reject) => {
if (n < 0) {
resolve(null);
return;
}
let req;
withStore(
"readonly",
store => {
store.transaction.oncomplete = function onComplete() {
const cursor = req.result;
resolve(cursor ? cursor.key : null);
};
let advanced = false;
req = store.openCursor();
req.onsuccess = function keyOnSuccess() {
const cursor = req.result;
if (!cursor) {
// this means there weren"t enough keys
return;
}
if (n === 0 || advanced) {
// Either 1) we have the first key, return it if that's what they
// wanted, or 2) we"ve got the nth key.
return;
}
// Otherwise, ask the cursor to skip ahead n records
advanced = true;
cursor.advance(n);
};
req.onerror = function keyOnError() {
reject("Error in asyncStorage.key(): ", req.error.name);
};
},
reject
);
});
}
exports.getItem = getItem;
exports.setItem = setItem;
exports.removeItem = removeItem;
exports.clear = clear;
exports.length = length;
exports.key = key;

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

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
"use strict";
const asyncStorage = require("./async-storage");
/*
* asyncStoreHelper wraps asyncStorage so that it is easy to define project
* specific properties. It is similar to PrefsHelper.
*
* e.g.
* const asyncStore = asyncStoreHelper("r", {a: "_a"})
* asyncStore.a // => asyncStorage.getItem("r._a")
* asyncStore.a = 2 // => asyncStorage.setItem("r._a", 2)
*/
function asyncStoreHelper(root, mappings) {
let store = {};
function getMappingKey(key) {
return Array.isArray(mappings[key]) ? mappings[key][0] : mappings[key];
}
function getMappingDefaultValue(key) {
return Array.isArray(mappings[key]) ? mappings[key][1] : null;
}
Object.keys(mappings).map(key =>
Object.defineProperty(store, key, {
async get() {
const value = await asyncStorage.getItem(
`${root}.${getMappingKey(key)}`
);
return value || getMappingDefaultValue(key);
},
set(value) {
return asyncStorage.setItem(`${root}.${getMappingKey(key)}`, value);
},
})
);
store = new Proxy(store, {
set: function(target, property, value, receiver) {
if (!mappings.hasOwnProperty(property)) {
throw new Error(`AsyncStore: ${property} is not defined in mappings`);
}
Reflect.set(...arguments);
return true;
},
});
return store;
}
module.exports = asyncStoreHelper;

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

@ -0,0 +1,243 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { appinfo } = require("devtools-services");
const EventEmitter = require("./utils/event-emitter");
const isOSX = appinfo.OS === "Darwin";
// List of electron keys mapped to DOM API (DOM_VK_*) key code
const ElectronKeysMapping = {
"F1": "DOM_VK_F1",
"F2": "DOM_VK_F2",
"F3": "DOM_VK_F3",
"F4": "DOM_VK_F4",
"F5": "DOM_VK_F5",
"F6": "DOM_VK_F6",
"F7": "DOM_VK_F7",
"F8": "DOM_VK_F8",
"F9": "DOM_VK_F9",
"F10": "DOM_VK_F10",
"F11": "DOM_VK_F11",
"F12": "DOM_VK_F12",
"F13": "DOM_VK_F13",
"F14": "DOM_VK_F14",
"F15": "DOM_VK_F15",
"F16": "DOM_VK_F16",
"F17": "DOM_VK_F17",
"F18": "DOM_VK_F18",
"F19": "DOM_VK_F19",
"F20": "DOM_VK_F20",
"F21": "DOM_VK_F21",
"F22": "DOM_VK_F22",
"F23": "DOM_VK_F23",
"F24": "DOM_VK_F24",
"Space": "DOM_VK_SPACE",
"Backspace": "DOM_VK_BACK_SPACE",
"Delete": "DOM_VK_DELETE",
"Insert": "DOM_VK_INSERT",
"Return": "DOM_VK_RETURN",
"Enter": "DOM_VK_RETURN",
"Up": "DOM_VK_UP",
"Down": "DOM_VK_DOWN",
"Left": "DOM_VK_LEFT",
"Right": "DOM_VK_RIGHT",
"Home": "DOM_VK_HOME",
"End": "DOM_VK_END",
"PageUp": "DOM_VK_PAGE_UP",
"PageDown": "DOM_VK_PAGE_DOWN",
"Escape": "DOM_VK_ESCAPE",
"Esc": "DOM_VK_ESCAPE",
"Tab": "DOM_VK_TAB",
"VolumeUp": "DOM_VK_VOLUME_UP",
"VolumeDown": "DOM_VK_VOLUME_DOWN",
"VolumeMute": "DOM_VK_VOLUME_MUTE",
"PrintScreen": "DOM_VK_PRINTSCREEN",
};
/**
* Helper to listen for keyboard events decribed in .properties file.
*
* let shortcuts = new KeyShortcuts({
* window
* });
* shortcuts.on("Ctrl+F", event => {
* // `event` is the KeyboardEvent which relates to the key shortcuts
* });
*
* @param DOMWindow window
* The window object of the document to listen events from.
* @param DOMElement target
* Optional DOM Element on which we should listen events from.
* If omitted, we listen for all events fired on `window`.
*/
function KeyShortcuts({ window, target }) {
this.window = window;
this.target = target || window;
this.keys = new Map();
this.eventEmitter = new EventEmitter();
this.target.addEventListener("keydown", this);
}
/*
* Parse an electron-like key string and return a normalized object which
* allow efficient match on DOM key event. The normalized object matches DOM
* API.
*
* @param DOMWindow window
* Any DOM Window object, just to fetch its `KeyboardEvent` object
* @param String str
* The shortcut string to parse, following this document:
* https://github.com/electron/electron/blob/master/docs/api/accelerator.md
*/
KeyShortcuts.parseElectronKey = function(window, str) {
let modifiers = str.split("+");
let key = modifiers.pop();
let shortcut = {
ctrl: false,
meta: false,
alt: false,
shift: false,
// Set for character keys
key: undefined,
// Set for non-character keys
keyCode: undefined,
};
for (let mod of modifiers) {
if (mod === "Alt") {
shortcut.alt = true;
} else if (["Command", "Cmd"].includes(mod)) {
shortcut.meta = true;
} else if (["CommandOrControl", "CmdOrCtrl"].includes(mod)) {
if (isOSX) {
shortcut.meta = true;
} else {
shortcut.ctrl = true;
}
} else if (["Control", "Ctrl"].includes(mod)) {
shortcut.ctrl = true;
} else if (mod === "Shift") {
shortcut.shift = true;
} else {
console.error("Unsupported modifier:", mod, "from key:", str);
return null;
}
}
// Plus is a special case. It's a character key and shouldn't be matched
// against a keycode as it is only accessible via Shift/Capslock
if (key === "Plus") {
key = "+";
}
if (typeof key === "string" && key.length === 1) {
// Match any single character
shortcut.key = key.toLowerCase();
} else if (key in ElectronKeysMapping) {
// Maps the others manually to DOM API DOM_VK_*
key = ElectronKeysMapping[key];
shortcut.keyCode = window.KeyboardEvent[key];
// Used only to stringify the shortcut
shortcut.keyCodeString = key;
shortcut.key = key;
} else {
console.error("Unsupported key:", key);
return null;
}
return shortcut;
};
KeyShortcuts.stringify = function(shortcut) {
let list = [];
if (shortcut.alt) {
list.push("Alt");
}
if (shortcut.ctrl) {
list.push("Ctrl");
}
if (shortcut.meta) {
list.push("Cmd");
}
if (shortcut.shift) {
list.push("Shift");
}
let key;
if (shortcut.key) {
key = shortcut.key.toUpperCase();
} else {
key = shortcut.keyCodeString;
}
list.push(key);
return list.join("+");
};
KeyShortcuts.prototype = {
destroy() {
this.target.removeEventListener("keydown", this);
this.keys.clear();
},
doesEventMatchShortcut(event, shortcut) {
if (shortcut.meta != event.metaKey) {
return false;
}
if (shortcut.ctrl != event.ctrlKey) {
return false;
}
if (shortcut.alt != event.altKey) {
return false;
}
// Shift is a special modifier, it may implicitely be required if the
// expected key is a special character accessible via shift.
if (shortcut.shift != event.shiftKey && event.key &&
event.key.match(/[a-zA-Z]/)) {
return false;
}
if (shortcut.keyCode) {
return event.keyCode == shortcut.keyCode;
} else if (event.key in ElectronKeysMapping) {
return ElectronKeysMapping[event.key] === shortcut.key;
}
// get the key from the keyCode if key is not provided.
let key = event.key || String.fromCharCode(event.keyCode);
// For character keys, we match if the final character is the expected one.
// But for digits we also accept indirect match to please azerty keyboard,
// which requires Shift to be pressed to get digits.
return key.toLowerCase() == shortcut.key ||
(shortcut.key.match(/^[0-9]$/) &&
event.keyCode == shortcut.key.charCodeAt(0));
},
handleEvent(event) {
for (let [key, shortcut] of this.keys) {
if (this.doesEventMatchShortcut(event, shortcut)) {
this.eventEmitter.emit(key, event);
}
}
},
on(key, listener) {
if (typeof listener !== "function") {
throw new Error("KeyShortcuts.on() expects a function as " +
"second argument");
}
if (!this.keys.has(key)) {
let shortcut = KeyShortcuts.parseElectronKey(this.window, key);
// The key string is wrong and we were unable to compute the key shortcut
if (!shortcut) {
return;
}
this.keys.set(key, shortcut);
}
this.eventEmitter.on(key, listener);
},
off(key, listener) {
this.eventEmitter.off(key, listener);
},
};
module.exports = KeyShortcuts;

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

@ -0,0 +1,247 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Services from "devtools-services";
const { appinfo } = Services;
const isMacOS = appinfo.OS === "Darwin";
const EventEmitter = require("../utils/event-emitter");
/**
* Formats key for use in tooltips
* For macOS we use the following unicode
*
* cmd = \u2318
* shift \u21E7
* option (alt) \u2325
*
* For Win/Lin this replaces CommandOrControl or CmdOrCtrl with Ctrl
*
* @static
*/
function formatKeyShortcut(shortcut) {
if (isMacOS) {
return shortcut
.replace(/Shift\+/g, "\u21E7")
.replace(/Command\+|Cmd\+/g, "\u2318")
.replace(/CommandOrControl\+|CmdOrCtrl\+/g, "\u2318")
.replace(/Alt\+/g, "\u2325");
}
return shortcut
.replace(/CommandOrControl\+|CmdOrCtrl\+/g, `${L10N.getStr("ctrl")}+`)
.replace(/Shift\+/g, "Shift+");
}
function inToolbox() {
try {
return window.parent.document.documentURI.startsWith("about:devtools-toolbox");
} catch (e) {
// If `window` is not available, it's very likely that we are in the toolbox.
return true;
}
}
// Copied from m-c DevToolsUtils.
function getTopWindow(win) {
return win.windowRoot ? win.windowRoot.ownerGlobal : win.top;
}
/**
* A partial implementation of the Menu API provided by electron:
* https://github.com/electron/electron/blob/master/docs/api/menu.md.
*
* Extra features:
* - Emits an 'open' and 'close' event when the menu is opened/closed
* @param String id (non standard)
* Needed so tests can confirm the XUL implementation is working
*/
function Menu({ id = null } = {}) {
this.menuitems = [];
this.id = id;
Object.defineProperty(this, "items", {
get() {
return this.menuitems;
}
});
EventEmitter.decorate(this);
}
/**
* Add an item to the end of the Menu
*
* @param {MenuItem} menuItem
*/
Menu.prototype.append = function (menuItem) {
this.menuitems.push(menuItem);
};
/**
* Add an item to a specified position in the menu
*
* @param {int} pos
* @param {MenuItem} menuItem
*/
Menu.prototype.insert = function (pos, menuItem) {
throw Error("Not implemented");
};
/**
* Show the Menu at a specified location on the screen
*
* Missing features:
* - browserWindow - BrowserWindow (optional) - Default is null.
* - positioningItem Number - (optional) OS X
*
* @param {int} screenX
* @param {int} screenY
* @param {Document} doc
* The document that should own the context menu.
*/
Menu.prototype.popup = function (screenX, screenY, doc) {
// The context-menu will be created in the topmost window to preserve keyboard
// navigation. See Bug 1543940. Keep a reference on the window owning the menu to hide
// the popup on unload.
const win = doc.defaultView;
doc = getTopWindow(doc.defaultView).document;
let popupset = doc.querySelector("popupset");
if (!popupset) {
popupset = doc.createXULElement("popupset");
doc.documentElement.appendChild(popupset);
}
// See bug 1285229, on Windows, opening the same popup multiple times in a
// row ends up duplicating the popup. The newly inserted popup doesn't
// dismiss the old one. So remove any previously displayed popup before
// opening a new one.
let popup = popupset.querySelector("menupopup[menu-api=\"true\"]");
if (popup) {
popup.hidePopup();
}
popup = this.createPopup(doc);
popup.setAttribute("menu-api", "true");
if (this.id) {
popup.id = this.id;
}
this._createMenuItems(popup);
// The context menu will be created in the topmost chrome window. Hide it manually when
// the owner document is unloaded.
const onWindowUnload = () => popup.hidePopup();
win.addEventListener("unload", onWindowUnload);
// Remove the menu from the DOM once it's hidden.
popup.addEventListener("popuphidden", (e) => {
if (e.target === popup) {
win.removeEventListener("unload", onWindowUnload);
popup.remove();
this.emit("close", popup);
}
});
popup.addEventListener("popupshown", (e) => {
if (e.target === popup) {
this.emit("open", popup);
}
});
popupset.appendChild(popup);
popup.openPopupAtScreen(screenX, screenY, true);
};
Menu.prototype.createPopup = function(doc) {
return doc.createElement("menupopup");
}
Menu.prototype._createMenuItems = function(parent) {
let doc = parent.ownerDocument;
this.menuitems.forEach(item => {
if (!item.visible) {
return;
}
if (item.submenu) {
let menupopup = doc.createElement("menupopup");
item.submenu._createMenuItems(menupopup);
let menuitem = doc.createElement("menuitem");
menuitem.setAttribute("label", item.label);
if (!inToolbox()) {
menuitem.textContent = item.label;
}
let menu = doc.createElement("menu");
menu.appendChild(menuitem);
menu.appendChild(menupopup);
if (item.disabled) {
menu.setAttribute("disabled", "true");
}
if (item.accesskey) {
menu.setAttribute("accesskey", item.accesskey);
}
if (item.id) {
menu.id = item.id;
}
if (item.accelerator) {
menuitem.setAttribute("acceltext", formatKeyShortcut(item.accelerator));
}
parent.appendChild(menu);
} else if (item.type === "separator") {
let menusep = doc.createElement("menuseparator");
parent.appendChild(menusep);
} else {
let menuitem = doc.createElement("menuitem");
menuitem.setAttribute("label", item.label);
if (!inToolbox()) {
menuitem.textContent = item.label;
}
menuitem.addEventListener("command", () => item.click());
if (item.type === "checkbox") {
menuitem.setAttribute("type", "checkbox");
}
if (item.type === "radio") {
menuitem.setAttribute("type", "radio");
}
if (item.disabled) {
menuitem.setAttribute("disabled", "true");
}
if (item.checked) {
menuitem.setAttribute("checked", "true");
}
if (item.accesskey) {
menuitem.setAttribute("accesskey", item.accesskey);
}
if (item.id) {
menuitem.id = item.id;
}
if (item.accelerator) {
menuitem.setAttribute("acceltext", formatKeyShortcut(item.accelerator));
}
parent.appendChild(menuitem);
}
});
};
Menu.setApplicationMenu = () => {
throw Error("Not implemented");
};
Menu.sendActionToFirstResponder = () => {
throw Error("Not implemented");
};
Menu.buildFromTemplate = () => {
throw Error("Not implemented");
};
module.exports = Menu;

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

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* A partial implementation of the MenuItem API provided by electron:
* https://github.com/electron/electron/blob/master/docs/api/menu-item.md.
*
* Missing features:
* - id String - Unique within a single menu. If defined then it can be used
* as a reference to this item by the position attribute.
* - role String - Define the action of the menu item; when specified the
* click property will be ignored
* - sublabel String
* - icon NativeImage
* - position String - This field allows fine-grained definition of the
* specific location within a given menu.
*
* Implemented features:
* @param Object options
* Function click
* Will be called with click(menuItem, browserWindow) when the menu item
* is clicked
* String type
* Can be normal, separator, submenu, checkbox or radio
* String label
* Boolean enabled
* If false, the menu item will be greyed out and unclickable.
* Boolean checked
* Should only be specified for checkbox or radio type menu items.
* Menu submenu
* Should be specified for submenu type menu items. If submenu is specified,
* the type: 'submenu' can be omitted. If the value is not a Menu then it
* will be automatically converted to one using Menu.buildFromTemplate.
* Boolean visible
* If false, the menu item will be entirely hidden.
* String accelerator
* If specified, will be used as accelerator text for MenuItem
*/
function MenuItem({
accesskey = null,
checked = false,
click = () => {},
disabled = false,
label = "",
id = null,
submenu = null,
type = "normal",
visible = true,
accelerator = "",
} = { }) {
this.accesskey = accesskey;
this.checked = checked;
this.click = click;
this.disabled = disabled;
this.id = id;
this.label = label;
this.submenu = submenu;
this.type = type;
this.visible = visible;
this.accelerator = accelerator;
}
module.exports = MenuItem;

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

@ -0,0 +1,160 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// These are the available plural functions that give the appropriate index
// based on the plural rule number specified. The first element is the number
// of plural forms and the second is the function to figure out the index.
const gFunctions = [
// 0: Chinese
[1, (n) => 0],
// 1: English
[2, (n) => n!=1?1:0],
// 2: French
[2, (n) => n>1?1:0],
// 3: Latvian
[3, (n) => n%10==1&&n%100!=11?1:n%10==0?0:2],
// 4: Scottish Gaelic
[4, (n) => n==1||n==11?0:n==2||n==12?1:n>0&&n<20?2:3],
// 5: Romanian
[3, (n) => n==1?0:n==0||n%100>0&&n%100<20?1:2],
// 6: Lithuanian
[3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?2:1],
// 7: Russian
[3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
// 8: Slovak
[3, (n) => n==1?0:n>=2&&n<=4?1:2],
// 9: Polish
[3, (n) => n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
// 10: Slovenian
[4, (n) => n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3],
// 11: Irish Gaeilge
[5, (n) => n==1?0:n==2?1:n>=3&&n<=6?2:n>=7&&n<=10?3:4],
// 12: Arabic
[6, (n) => n==0?5:n==1?0:n==2?1:n%100>=3&&n%100<=10?2:n%100>=11&&n%100<=99?3:4],
// 13: Maltese
[4, (n) => n==1?0:n==0||n%100>0&&n%100<=10?1:n%100>10&&n%100<20?2:3],
// 14: Unused
[3, (n) => n%10==1?0:n%10==2?1:2],
// 15: Icelandic, Macedonian
[2, (n) => n%10==1&&n%100!=11?0:1],
// 16: Breton
[5, (n) => n%10==1&&n%100!=11&&n%100!=71&&n%100!=91?0:n%10==2&&n%100!=12&&n%100!=72&&n%100!=92?1:(n%10==3||n%10==4||n%10==9)&&n%100!=13&&n%100!=14&&n%100!=19&&n%100!=73&&n%100!=74&&n%100!=79&&n%100!=93&&n%100!=94&&n%100!=99?2:n%1000000==0&&n!=0?3:4],
// 17: Shuar
[2, (n) => n!=0?1:0],
// 18: Welsh
[6, (n) => n==0?0:n==1?1:n==2?2:n==3?3:n==6?4:5],
// 19: Bosnian, Croatian, Serbian
[3, (n) => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2],
];
const PluralForm = {
/**
* Get the correct plural form of a word based on the number
*
* @param aNum
* The number to decide which plural form to use
* @param aWords
* A semi-colon (;) separated string of words to pick the plural form
* @return The appropriate plural form of the word
*/
get get()
{
// This method will lazily load to avoid perf when it is first needed and
// creates getPluralForm function. The function it creates is based on the
// value of pluralRule specified in the intl stringbundle.
// See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
// Delete the getters to be overwritten
delete this.numForms;
delete this.get;
// Make the plural form get function and set it as the default get
[this.get, this.numForms] = this.makeGetter(this.ruleNum);
return this.get;
},
/**
* Create a pair of plural form functions for the given plural rule number.
*
* @param aRuleNum
* The plural rule number to create functions
* @return A pair: [function that gets the right plural form,
* function that returns the number of plural forms]
*/
makeGetter: function(aRuleNum)
{
// Default to "all plural" if the value is out of bounds or invalid
if (aRuleNum < 0 || aRuleNum >= gFunctions.length || isNaN(aRuleNum)) {
log(["Invalid rule number: ", aRuleNum, " -- defaulting to 0"]);
aRuleNum = 0;
}
// Get the desired pluralRule function
let [numForms, pluralFunc] = gFunctions[aRuleNum];
// Return functions that give 1) the number of forms and 2) gets the right
// plural form
return [function(aNum, aWords) {
// Figure out which index to use for the semi-colon separated words
let index = pluralFunc(aNum ? Number(aNum) : 0);
let words = aWords ? aWords.split(/;/) : [""];
// Explicitly check bounds to avoid strict warnings
let ret = index < words.length ? words[index] : undefined;
// Check for array out of bounds or empty strings
if ((ret == undefined) || (ret == "")) {
// Display a message in the error console
log(["Index #", index, " of '", aWords, "' for value ", aNum,
" is invalid -- plural rule #", aRuleNum, ";"]);
// Default to the first entry (which might be empty, but not undefined)
ret = words[0];
}
return ret;
}, () => numForms];
},
/**
* Get the number of forms for the current plural rule
*
* @return The number of forms
*/
get numForms()
{
// We lazily load numForms, so trigger the init logic with get()
this.get();
return this.numForms;
},
/**
* Get the plural rule number from the intl stringbundle
*
* @return The plural rule number
*/
get ruleNum()
{
try {
return parseInt(L10N.getStr("pluralRule"), 10);
} catch (e) {
// Fallback to English if the pluralRule property is not available.
return 1;
}
}
};
/**
* Private helper function to log errors to the error console and command line
*
* @param aMsg
* Error message to log or an array of strings to concat
*/
function log(aMsg)
{
let msg = "plural-form.js: " + (aMsg.join ? aMsg.join("") : aMsg);
console.log(msg + "\n");
}
module.exports = PluralForm;

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

@ -0,0 +1,187 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Services = require("devtools-services");
const EventEmitter = require("./utils/event-emitter");
/**
* Shortcuts for lazily accessing and setting various preferences.
* Usage:
* let prefs = new Prefs("root.path.to.branch", {
* myIntPref: ["Int", "leaf.path.to.my-int-pref"],
* myCharPref: ["Char", "leaf.path.to.my-char-pref"],
* myJsonPref: ["Json", "leaf.path.to.my-json-pref"],
* myFloatPref: ["Float", "leaf.path.to.my-float-pref"]
* ...
* });
*
* Get/set:
* prefs.myCharPref = "foo";
* let aux = prefs.myCharPref;
*
* Observe:
* prefs.registerObserver();
* prefs.on("pref-changed", (prefName, prefValue) => {
* ...
* });
*
* @param string prefsRoot
* The root path to the required preferences branch.
* @param object prefsBlueprint
* An object containing { accessorName: [prefType, prefName, prefDefault] } keys.
*/
function PrefsHelper(prefsRoot = "", prefsBlueprint = {}) {
EventEmitter.decorate(this);
let cache = new Map();
for (let accessorName in prefsBlueprint) {
let [prefType, prefName, prefDefault] = prefsBlueprint[accessorName];
map(this, cache, accessorName, prefType, prefsRoot, prefName, prefDefault);
}
let observer = makeObserver(this, cache, prefsRoot, prefsBlueprint);
this.registerObserver = () => observer.register();
this.unregisterObserver = () => observer.unregister();
}
/**
* Helper method for getting a pref value.
*
* @param Map cache
* @param string prefType
* @param string prefsRoot
* @param string prefName
* @return any
*/
function get(cache, prefType, prefsRoot, prefName) {
let cachedPref = cache.get(prefName);
if (cachedPref !== undefined) {
return cachedPref;
}
let value = Services.prefs["get" + prefType + "Pref"](
[prefsRoot, prefName].join(".")
);
cache.set(prefName, value);
return value;
}
/**
* Helper method for setting a pref value.
*
* @param Map cache
* @param string prefType
* @param string prefsRoot
* @param string prefName
* @param any value
*/
function set(cache, prefType, prefsRoot, prefName, value) {
Services.prefs["set" + prefType + "Pref"](
[prefsRoot, prefName].join("."),
value
);
cache.set(prefName, value);
}
/**
* Maps a property name to a pref, defining lazy getters and setters.
* Supported types are "Bool", "Char", "Int", "Float" (sugar around "Char"
* type and casting), and "Json" (which is basically just sugar for "Char"
* using the standard JSON serializer).
*
* @param PrefsHelper self
* @param Map cache
* @param string accessorName
* @param string prefType
* @param string prefsRoot
* @param string prefName
* @param string prefDefault
* @param array serializer [optional]
*/
function map(self, cache, accessorName, prefType, prefsRoot, prefName, prefDefault,
serializer = { in: e => e, out: e => e }) {
if (prefName in self) {
throw new Error(`Can't use ${prefName} because it overrides a property` +
"on the instance.");
}
if (prefType == "Json") {
map(self, cache, accessorName, "String", prefsRoot, prefName, prefDefault, {
in: JSON.parse,
out: JSON.stringify
});
return;
}
if (prefType == "Float") {
map(self, cache, accessorName, "Char", prefsRoot, prefName, prefDefault, {
in: Number.parseFloat,
out: (n) => n + ""
});
return;
}
Object.defineProperty(self, accessorName, {
get: () => {
try {
return serializer.in(get(cache, prefType, prefsRoot, prefName));
} catch (e) {
if (typeof prefDefault !== 'undefined') {
return prefDefault;
}
throw e;
}
},
set: (e) => set(cache, prefType, prefsRoot, prefName, serializer.out(e))
});
}
/**
* Finds the accessor for the provided pref, based on the blueprint object
* used in the constructor.
*
* @param PrefsHelper self
* @param object prefsBlueprint
* @return string
*/
function accessorNameForPref(somePrefName, prefsBlueprint) {
for (let accessorName in prefsBlueprint) {
let [, prefName] = prefsBlueprint[accessorName];
if (somePrefName == prefName) {
return accessorName;
}
}
return "";
}
/**
* Creates a pref observer for `self`.
*
* @param PrefsHelper self
* @param Map cache
* @param string prefsRoot
* @param object prefsBlueprint
* @return object
*/
function makeObserver(self, cache, prefsRoot, prefsBlueprint) {
return {
register: function() {
this._branch = Services.prefs.getBranch(prefsRoot + ".");
this._branch.addObserver("", this);
},
unregister: function() {
this._branch.removeObserver("", this);
},
observe: function(subject, topic, prefName) {
// If this particular pref isn't handled by the blueprint object,
// even though it's in the specified branch, ignore it.
let accessorName = accessorNameForPref(prefName, prefsBlueprint);
if (!(accessorName in self)) {
return;
}
cache.delete(prefName);
self.emit("pref-changed", accessorName, self[accessorName]);
}
};
}
exports.PrefsHelper = PrefsHelper;

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

@ -0,0 +1,5 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module.exports = () => {}

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

@ -0,0 +1,339 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// TODO : Localize this (was l10n.getStr("frame.unknownSource"))
const UNKNOWN_SOURCE_STRING = "(unknown)";
// Character codes used in various parsing helper functions.
const CHAR_CODE_A = "a".charCodeAt(0);
const CHAR_CODE_B = "b".charCodeAt(0);
const CHAR_CODE_C = "c".charCodeAt(0);
const CHAR_CODE_D = "d".charCodeAt(0);
const CHAR_CODE_E = "e".charCodeAt(0);
const CHAR_CODE_F = "f".charCodeAt(0);
const CHAR_CODE_H = "h".charCodeAt(0);
const CHAR_CODE_I = "i".charCodeAt(0);
const CHAR_CODE_J = "j".charCodeAt(0);
const CHAR_CODE_L = "l".charCodeAt(0);
const CHAR_CODE_M = "m".charCodeAt(0);
const CHAR_CODE_N = "n".charCodeAt(0);
const CHAR_CODE_O = "o".charCodeAt(0);
const CHAR_CODE_P = "p".charCodeAt(0);
const CHAR_CODE_R = "r".charCodeAt(0);
const CHAR_CODE_S = "s".charCodeAt(0);
const CHAR_CODE_T = "t".charCodeAt(0);
const CHAR_CODE_U = "u".charCodeAt(0);
const CHAR_CODE_W = "w".charCodeAt(0);
const CHAR_CODE_COLON = ":".charCodeAt(0);
const CHAR_CODE_DASH = "-".charCodeAt(0);
const CHAR_CODE_L_SQUARE_BRACKET = "[".charCodeAt(0);
const CHAR_CODE_SLASH = "/".charCodeAt(0);
const CHAR_CODE_CAP_S = "S".charCodeAt(0);
// The cache used in the `parseURL` function.
const gURLStore = new Map();
// The cache used in the `getSourceNames` function.
const gSourceNamesStore = new Map();
/**
* Takes a string and returns an object containing all the properties
* available on an URL instance, with additional properties (fileName),
* Leverages caching.
*
* @param {String} location
* @return {Object?} An object containing most properties available
* in https://developer.mozilla.org/en-US/docs/Web/API/URL
*/
function parseURL(location) {
let url = gURLStore.get(location);
if (url !== void 0) {
return url;
}
try {
url = new URL(location);
// The callers were generally written to expect a URL from
// sdk/url, which is subtly different. So, work around some
// important differences here.
url = {
href: url.href,
protocol: url.protocol,
host: url.host,
hostname: url.hostname,
port: url.port || null,
pathname: url.pathname,
search: url.search,
hash: url.hash,
username: url.username,
password: url.password,
origin: url.origin,
};
// Definitions:
// Example: https://foo.com:8888/file.js
// `hostname`: "foo.com"
// `host`: "foo.com:8888"
let isChrome = isChromeScheme(location);
url.fileName = url.pathname ?
(url.pathname.slice(url.pathname.lastIndexOf("/") + 1) || "/") :
"/";
if (isChrome) {
url.hostname = null;
url.host = null;
}
gURLStore.set(location, url);
return url;
} catch (e) {
gURLStore.set(location, null);
return null;
}
}
/**
* Parse a source into a short and long name as well as a host name.
*
* @param {String} source
* The source to parse. Can be a URI or names like "(eval)" or
* "self-hosted".
* @return {Object}
* An object with the following properties:
* - {String} short: A short name for the source.
* - "http://page.com/test.js#go?q=query" -> "test.js"
* - {String} long: The full, long name for the source, with
hash/query stripped.
* - "http://page.com/test.js#go?q=query" -> "http://page.com/test.js"
* - {String?} host: If available, the host name for the source.
* - "http://page.com/test.js#go?q=query" -> "page.com"
*/
function getSourceNames(source) {
let data = gSourceNamesStore.get(source);
if (data) {
return data;
}
let short, long, host;
const sourceStr = source ? String(source) : "";
// If `data:...` uri
if (isDataScheme(sourceStr)) {
let commaIndex = sourceStr.indexOf(",");
if (commaIndex > -1) {
// The `short` name for a data URI becomes `data:` followed by the actual
// encoded content, omitting the MIME type, and charset.
short = `data:${sourceStr.substring(commaIndex + 1)}`.slice(0, 100);
let result = { short, long: sourceStr };
gSourceNamesStore.set(source, result);
return result;
}
}
const parsedUrl = parseURL(sourceStr);
if (!parsedUrl) {
// Malformed URI.
long = sourceStr;
short = sourceStr.slice(0, 100);
} else {
host = parsedUrl.host;
long = parsedUrl.href;
if (parsedUrl.hash) {
long = long.replace(parsedUrl.hash, "");
}
if (parsedUrl.search) {
long = long.replace(parsedUrl.search, "");
}
short = parsedUrl.fileName;
// If `short` is just a slash, and we actually have a path,
// strip the slash and parse again to get a more useful short name.
// e.g. "http://foo.com/bar/" -> "bar", rather than "/"
if (short === "/" && parsedUrl.pathname !== "/") {
short = parseURL(long.replace(/\/$/, "")).fileName;
}
}
if (!short) {
if (!long) {
long = UNKNOWN_SOURCE_STRING;
}
short = long.slice(0, 100);
}
let result = { short, long, host };
gSourceNamesStore.set(source, result);
return result;
}
// For the functions below, we assume that we will never access the location
// argument out of bounds, which is indeed the vast majority of cases.
//
// They are written this way because they are hot. Each frame is checked for
// being content or chrome when processing the profile.
function isColonSlashSlash(location, i = 0) {
return location.charCodeAt(++i) === CHAR_CODE_COLON &&
location.charCodeAt(++i) === CHAR_CODE_SLASH &&
location.charCodeAt(++i) === CHAR_CODE_SLASH;
}
function isDataScheme(location, i = 0) {
return location.charCodeAt(i) === CHAR_CODE_D &&
location.charCodeAt(++i) === CHAR_CODE_A &&
location.charCodeAt(++i) === CHAR_CODE_T &&
location.charCodeAt(++i) === CHAR_CODE_A &&
location.charCodeAt(++i) === CHAR_CODE_COLON;
}
function isContentScheme(location, i = 0) {
let firstChar = location.charCodeAt(i);
switch (firstChar) {
// "http://" or "https://"
case CHAR_CODE_H:
if (location.charCodeAt(++i) === CHAR_CODE_T &&
location.charCodeAt(++i) === CHAR_CODE_T &&
location.charCodeAt(++i) === CHAR_CODE_P) {
if (location.charCodeAt(i + 1) === CHAR_CODE_S) {
++i;
}
return isColonSlashSlash(location, i);
}
return false;
// "file://"
case CHAR_CODE_F:
if (location.charCodeAt(++i) === CHAR_CODE_I &&
location.charCodeAt(++i) === CHAR_CODE_L &&
location.charCodeAt(++i) === CHAR_CODE_E) {
return isColonSlashSlash(location, i);
}
return false;
// "app://"
case CHAR_CODE_A:
if (location.charCodeAt(++i) == CHAR_CODE_P &&
location.charCodeAt(++i) == CHAR_CODE_P) {
return isColonSlashSlash(location, i);
}
return false;
// "blob:"
case CHAR_CODE_B:
if (
location.charCodeAt(++i) == CHAR_CODE_L &&
location.charCodeAt(++i) == CHAR_CODE_O &&
location.charCodeAt(++i) == CHAR_CODE_B &&
location.charCodeAt(++i) == CHAR_CODE_COLON
) {
return isContentScheme(location, i + 1);
}
return false;
default:
return false;
}
}
function isChromeScheme(location, i = 0) {
let firstChar = location.charCodeAt(i);
switch (firstChar) {
// "chrome://"
case CHAR_CODE_C:
if (location.charCodeAt(++i) === CHAR_CODE_H &&
location.charCodeAt(++i) === CHAR_CODE_R &&
location.charCodeAt(++i) === CHAR_CODE_O &&
location.charCodeAt(++i) === CHAR_CODE_M &&
location.charCodeAt(++i) === CHAR_CODE_E) {
return isColonSlashSlash(location, i);
}
return false;
// "resource://"
case CHAR_CODE_R:
if (location.charCodeAt(++i) === CHAR_CODE_E &&
location.charCodeAt(++i) === CHAR_CODE_S &&
location.charCodeAt(++i) === CHAR_CODE_O &&
location.charCodeAt(++i) === CHAR_CODE_U &&
location.charCodeAt(++i) === CHAR_CODE_R &&
location.charCodeAt(++i) === CHAR_CODE_C &&
location.charCodeAt(++i) === CHAR_CODE_E) {
return isColonSlashSlash(location, i);
}
return false;
// "jar:file://"
case CHAR_CODE_J:
if (location.charCodeAt(++i) === CHAR_CODE_A &&
location.charCodeAt(++i) === CHAR_CODE_R &&
location.charCodeAt(++i) === CHAR_CODE_COLON &&
location.charCodeAt(++i) === CHAR_CODE_F &&
location.charCodeAt(++i) === CHAR_CODE_I &&
location.charCodeAt(++i) === CHAR_CODE_L &&
location.charCodeAt(++i) === CHAR_CODE_E) {
return isColonSlashSlash(location, i);
}
return false;
default:
return false;
}
}
function isWASM(location, i = 0) {
return (
// "wasm-function["
location.charCodeAt(i) === CHAR_CODE_W &&
location.charCodeAt(++i) === CHAR_CODE_A &&
location.charCodeAt(++i) === CHAR_CODE_S &&
location.charCodeAt(++i) === CHAR_CODE_M &&
location.charCodeAt(++i) === CHAR_CODE_DASH &&
location.charCodeAt(++i) === CHAR_CODE_F &&
location.charCodeAt(++i) === CHAR_CODE_U &&
location.charCodeAt(++i) === CHAR_CODE_N &&
location.charCodeAt(++i) === CHAR_CODE_C &&
location.charCodeAt(++i) === CHAR_CODE_T &&
location.charCodeAt(++i) === CHAR_CODE_I &&
location.charCodeAt(++i) === CHAR_CODE_O &&
location.charCodeAt(++i) === CHAR_CODE_N &&
location.charCodeAt(++i) === CHAR_CODE_L_SQUARE_BRACKET
);
}
/**
* A utility method to get the file name from a sourcemapped location
* The sourcemap location can be in any form. This method returns a
* formatted file name for different cases like Windows or OSX.
* @param source
* @returns String
*/
function getSourceMappedFile(source) {
// If sourcemapped source is a OSX path, return
// the characters after last "/".
// If sourcemapped source is a Windowss path, return
// the characters after last "\\".
if (source.lastIndexOf("/") >= 0) {
source = source.slice(source.lastIndexOf("/") + 1);
} else if (source.lastIndexOf("\\") >= 0) {
source = source.slice(source.lastIndexOf("\\") + 1);
}
return source;
}
module.exports = {
parseURL,
getSourceNames,
isChromeScheme,
isContentScheme,
isWASM,
isDataScheme,
getSourceMappedFile,
};

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

@ -0,0 +1,30 @@
var localStorageMock = (() => {
let store = {};
return {
getItem: (key) => {
if (!store.hasOwnProperty(key)) {
return "";
}
return store[key];
},
setItem: (key, value) => {
store[key] = value.toString();
},
clear: () => {
store = {};
},
removeItem: (key) => {
delete store[key];
},
key: (index) => {
return Object.keys(store)[index];
},
get length() {
return Object.keys(store).length;
}
};
})();
Object.defineProperty(window, "localStorage", {
value: localStorageMock
});

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

@ -0,0 +1,37 @@
const { PrefsHelper } = require("../prefs");
const Services = require("devtools-services");
const pref = Services.pref;
describe("prefs helper", () => {
beforeEach(() => {});
afterEach(() => {
localStorage.clear();
});
it("supports fallback values", () => {
pref("devtools.valid", "{\"valid\": true}");
pref("devtools.invalid", "{not valid at all]");
pref("devtools.nodefault", "{not valid at all]");
pref("devtools.validnodefault", "{\"nodefault\": true}");
const prefs = new PrefsHelper("devtools", {
valid: ["Json", "valid", {valid: true}],
invalid: ["Json", "invalid", {}],
nodefault: ["Json", "nodefault"],
validnodefault: ["Json", "validnodefault"],
});
// Valid Json pref should return the actual value
expect(prefs.valid).toEqual({valid: true});
// Invalid Json pref with a fallback shoud return the fallback value
expect(prefs.invalid).toEqual({});
// Invalid Json pref with no fallback should throw
expect(() => prefs.nodefault).toThrow();
// Valid Json pref with no fallback should return the value
expect(prefs.validnodefault).toEqual({nodefault: true});
});
});

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

@ -0,0 +1,200 @@
const Services = require("devtools-services");
const pref = Services.pref;
describe("services prefs shim", () => {
beforeEach(() => {
// Add some starter prefs.
localStorage.setItem("Services.prefs:devtools.branch1.somebool", JSON.stringify({
// bool
type: 128,
defaultValue: false,
hasUserValue: false,
userValue: false
}));
localStorage.setItem("Services.prefs:devtools.branch1.somestring", JSON.stringify({
// string
type: 32,
defaultValue: "dinosaurs",
hasUserValue: true,
userValue: "elephants"
}));
localStorage.setItem("Services.prefs:devtools.branch2.someint", JSON.stringify({
// int
type: 64,
defaultValue: -16,
hasUserValue: false,
userValue: null
}));
});
afterEach(() => {
localStorage.clear();
});
it("can get and set preferences", () => {
expect(Services.prefs.getBoolPref("devtools.branch1.somebool")).toBe(false);
Services.prefs.setBoolPref("devtools.branch1.somebool", true);
expect(Services.prefs.getBoolPref("devtools.branch1.somebool")).toBe(true);
Services.prefs.clearUserPref("devtools.branch1.somestring");
expect(Services.prefs.getCharPref("devtools.branch1.somestring")).toBe("dinosaurs");
expect(Services.prefs.prefHasUserValue("devtools.branch1.somebool")).toBe(true,
"bool pref has user value");
expect(Services.prefs.prefHasUserValue("devtools.branch1.somestring")).toBe(false,
"string pref does not have user value");
// String prefs actually differ from Char prefs in the real implementation of
// Services.prefs, but for this shim, both are using the same implementation.
Services.prefs.setStringPref("devtools.branch1.somerealstring", "abcdef");
expect(Services.prefs.getStringPref("devtools.branch1.somerealstring")).toBe("abcdef");
});
it("can call savePrefFile without crashing", () => {
Services.prefs.savePrefFile(null);
});
it("can use branches", () => {
let branch0 = Services.prefs.getBranch(null);
let branch1 = Services.prefs.getBranch("devtools.branch1.");
branch1.setCharPref("somestring", "octopus");
Services.prefs.setCharPref("devtools.branch1.somestring", "octopus");
expect(Services.prefs.getCharPref("devtools.branch1.somestring")).toEqual("octopus");
expect(branch0.getCharPref("devtools.branch1.somestring")).toEqual("octopus");
expect(branch1.getCharPref("somestring")).toEqual("octopus");
});
it("throws exceptions when expected", () => {
expect(() => {
Services.prefs.setIntPref("devtools.branch1.somebool", 27);
}).toThrow();
expect(() => {
Services.prefs.setBoolPref("devtools.branch1.somebool", 27);
}).toThrow();
expect(() => {
Services.prefs.getCharPref("devtools.branch2.someint");
}).toThrow();
expect(() => {
Services.prefs.setCharPref("devtools.branch2.someint", "whatever");
}).toThrow();
expect(() => {
Services.prefs.setIntPref("devtools.branch2.someint", "whatever");
}).toThrow();
expect(() => {
Services.prefs.getBoolPref("devtools.branch1.somestring");
}).toThrow();
expect(() => {
Services.prefs.setBoolPref("devtools.branch1.somestring", true);
}).toThrow();
expect(() => {
Services.prefs.setCharPref("devtools.branch1.somestring", true);
}).toThrow();
});
it("returns correct pref types", () => {
expect(Services.prefs.getPrefType("devtools.branch1.somebool"))
.toEqual(Services.prefs.PREF_BOOL);
expect(Services.prefs.getPrefType("devtools.branch2.someint"))
.toEqual(Services.prefs.PREF_INT);
expect(Services.prefs.getPrefType("devtools.branch1.somestring"))
.toEqual(Services.prefs.PREF_STRING);
});
it("supports observers", () => {
let notifications = {};
let clearNotificationList = () => {
notifications = {};
};
let observer = {
observe: function (subject, topic, data) {
notifications[data] = true;
}
};
let branch0 = Services.prefs.getBranch(null);
let branch1 = Services.prefs.getBranch("devtools.branch1.");
branch0.addObserver("devtools.branch1", null, null);
branch0.addObserver("devtools.branch1.", observer);
branch1.addObserver("", observer);
Services.prefs.setCharPref("devtools.branch1.somestring", "elf owl");
expect(notifications).toEqual({
"devtools.branch1.somestring": true,
"somestring": true
}, "notifications sent to two listeners");
clearNotificationList();
Services.prefs.setIntPref("devtools.branch2.someint", 1729);
expect(notifications).toEqual({}, "no notifications sent");
clearNotificationList();
branch0.removeObserver("devtools.branch1.", observer);
Services.prefs.setCharPref("devtools.branch1.somestring", "tapir");
expect(notifications).toEqual({
"somestring": true
}, "removeObserver worked");
clearNotificationList();
branch0.addObserver("devtools.branch1.somestring", observer);
Services.prefs.setCharPref("devtools.branch1.somestring", "northern shoveler");
expect(notifications).toEqual({
"devtools.branch1.somestring": true,
"somestring": true
}, "notifications sent to two listeners");
branch0.removeObserver("devtools.branch1.somestring", observer);
// Make sure we update if the pref change comes from somewhere else.
clearNotificationList();
pref("devtools.branch1.someotherstring", "lazuli bunting");
expect(notifications).toEqual({
"someotherstring": true
}, "pref worked");
});
it("does not crash when setting prefs (bug 1296427)", () => {
// Regression test for bug 1296427.
pref("devtools.hud.loglimit", 1000);
pref("devtools.hud.loglimit.network", 1000);
});
it("fixes observer bug (bug 1319150)", () => {
// Regression test for bug 1319150.
let seen = false;
let fnObserver = () => {
seen = true;
};
let branch0 = Services.prefs.getBranch(null);
branch0.addObserver("devtools.branch1.somestring", fnObserver);
Services.prefs.setCharPref("devtools.branch1.somestring", "common merganser");
expect(seen).toBe(true);
branch0.removeObserver("devtools.branch1.somestring", fnObserver);
});
it("supports default value argument", () => {
// Check support for default values
let intPrefWithDefault =
Services.prefs.getIntPref("devtools.branch1.missing", 1);
expect(intPrefWithDefault).toEqual(1);
let charPrefWithDefault =
Services.prefs.getCharPref("devtools.branch1.missing", "test");
expect(charPrefWithDefault).toEqual("test");
let boolPrefWithDefault =
Services.prefs.getBoolPref("devtools.branch1.missing", true);
expect(boolPrefWithDefault).toBe(true);
});
});

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

@ -0,0 +1,41 @@
const Services = require("devtools-services");
// const pref = Services;
describe("services telemetry shim", () => {
beforeEach(() => {
telemetry.scalars = {};
telemetry.histograms = {};
});
it("getHistogramById", () => {
const hist = Services.telemetry.getHistogramById("foo");
hist.add(3);
expect(telemetry.histograms.foo).toEqual([3]);
hist.add(3);
expect(telemetry.histograms.foo).toEqual([3, 3]);
});
it("getKeyedHistogramById", () => {
const hist = Services.telemetry.getKeyedHistogramById("foo");
hist.add("a", 3);
expect(telemetry.histograms.foo.a).toEqual([3]);
hist.add("a", 3);
expect(telemetry.histograms.foo.a).toEqual([3, 3]);
});
it("scalarSet", () => {
Services.telemetry.scalarSet("foo", 3);
expect(telemetry.scalars.foo).toEqual(3);
});
it("scalarAdd", () => {
Services.telemetry.scalarAdd("foo", 3);
expect(telemetry.scalars.foo).toEqual(3);
Services.telemetry.scalarAdd("foo", 3);
expect(telemetry.scalars.foo).toEqual(6);
});
});

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

@ -0,0 +1,179 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {
getSourceMappedFile,
getSourceNames,
isChromeScheme,
isContentScheme,
isDataScheme,
isWASM,
parseURL,
} = require("../source-utils");
describe("source-utils", () => {
const CHROME_URLS = [
"chrome://foo",
"resource://baz",
"jar:file:///Users/root"
];
const CONTENT_URLS = [
"http://mozilla.org",
"https://mozilla.org",
"file:///Users/root",
"app://fxosapp",
"blob:http://mozilla.org",
"blob:https://mozilla.org"
];
it("parse URLs", () => {
let parsed = parseURL("https://foo.com:8888/boo/bar.js?q=query");
expect(parsed.fileName).toBe("bar.js");
expect(parsed.host).toBe("foo.com:8888");
expect(parsed.hostname).toBe("foo.com");
expect(parsed.port).toBe("8888");
expect(parsed.href).toBe("https://foo.com:8888/boo/bar.js?q=query");
parsed = parseURL("https://foo.com");
expect(parsed.host).toBe("foo.com");
expect(parsed.hostname).toBe("foo.com");
expect(parseURL("self-hosted")).toBe(null);
});
it("isContentScheme", () => {
for (let url of CHROME_URLS) {
expect(isContentScheme(url)).toBe(false);
}
for (let url of CONTENT_URLS) {
expect(isContentScheme(url)).toBe(true);
}
});
it("isChromeScheme", () => {
for (let url of CHROME_URLS) {
expect(isChromeScheme(url)).toBe(true);
}
for (let url of CONTENT_URLS) {
expect(isChromeScheme(url)).toBe(false);
}
});
it("isWASM", () => {
expect(isWASM("wasm-function[66240] (?:13870536)")).toBe(true);
expect(isWASM(CHROME_URLS[0])).toBe(false);
});
it("isDataScheme", () => {
const dataURI = "data:text/html;charset=utf-8,<!DOCTYPE html></html>";
expect(isDataScheme(dataURI)).toBe(true);
for (let url of CHROME_URLS) {
expect(isDataScheme(url)).toBe(false);
}
for (let url of CONTENT_URLS) {
expect(isDataScheme(url)).toBe(false);
}
});
it("getSourceMappedFile", () => {
expect(getSourceMappedFile("baz.js")).toBe("baz.js");
expect(getSourceMappedFile("/foo/bar/baz.js")).toBe("baz.js");
expect(getSourceMappedFile("Z:\\foo\\bar\\baz.js")).toBe("baz.js");
});
it("getSourceNames", () => {
// Check length
const longMalformedURL = `example.com${"/a".repeat(100)}/file.js`;
expect(getSourceNames(longMalformedURL).short.length).toBeLessThanOrEqual(100);
expect(getSourceNames("http://example.com/foo/bar/baz/boo.js")).toEqual({
short: "boo.js",
long: "http://example.com/foo/bar/baz/boo.js",
host: "example.com"
});
expect(getSourceNames("self-hosted")).toEqual({
short: "self-hosted",
long: "self-hosted",
host: undefined
});
expect(getSourceNames("")).toEqual({
short: "(unknown)",
long: "(unknown)",
host: undefined
});
// Test shortening data URIs, stripping mime/charset
const dataURI = "data:text/html;charset=utf-8,<!DOCTYPE html></html>";
expect(getSourceNames(dataURI)).toEqual({
short: "data:<!DOCTYPE html></html>",
long: "data:text/html;charset=utf-8,<!DOCTYPE html></html>",
host: undefined
});
// Test shortening data URIs and that the `short` result is capped
let longDataURI = `data:image/png;base64,${"a".repeat(100)}`;
let longDataURIShort = getSourceNames(longDataURI).short;
expect(longDataURIShort.length).toBeLessThanOrEqual(100);
expect(longDataURIShort.substr(0, 10)).toBe("data:aaaaa");
// Test simple URL and cache retrieval by calling the same input multiple times.
let testUrl = "http://example.com/foo/bar/baz/boo.js";
expect(getSourceNames(testUrl)).toEqual({
short: "boo.js",
long: testUrl,
host: "example.com"
});
expect(getSourceNames(testUrl)).toEqual({
short: "boo.js",
long: testUrl,
host: "example.com"
});
// Check query and hash and port
expect(getSourceNames("http://example.com:8888/foo/bar/baz.js?q=query#go")).toEqual({
short: "baz.js",
long: "http://example.com:8888/foo/bar/baz.js",
host: "example.com:8888"
});
// Trailing "/" with nothing beyond host
expect(getSourceNames("http://example.com/")).toEqual({
short: "/",
long: "http://example.com/",
host: "example.com"
});
// Trailing "/"
expect(getSourceNames("http://example.com/foo/bar/")).toEqual({
short: "bar",
long: "http://example.com/foo/bar/",
host: "example.com"
});
// Non-extension ending
expect(getSourceNames("http://example.com/bar")).toEqual({
short: "bar",
long: "http://example.com/bar",
host: "example.com"
});
// Check query
expect(getSourceNames("http://example.com/foo.js?bar=1&baz=2")).toEqual({
short: "foo.js",
long: "http://example.com/foo.js",
host: "example.com"
});
// Check query with trailing slash
expect(getSourceNames("http://example.com/foo/?bar=1&baz=2")).toEqual({
short: "foo",
long: "http://example.com/foo/",
host: "example.com"
});
});
});

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

@ -0,0 +1,120 @@
const Telemetry = require("../utils/telemetry");
const telemetry = new Telemetry();
describe("telemetry shim", () => {
it("msSystemNow", () => {
expect(() => {
telemetry.msSystemNow;
}).not.toThrow();
});
it("start", () => {
expect(() => {
telemetry.start("foo", this);
}).not.toThrow();
});
it("startKeyed", () => {
expect(() => {
telemetry.startKeyed("foo", "bar", this);
}).not.toThrow();
});
it("finish", () => {
expect(() => {
telemetry.finish("foo", this);
}).not.toThrow();
});
it("finishKeyed", () => {
expect(() => {
telemetry.finishKeyed("foo", "bar", this);
}).not.toThrow();
});
it("getHistogramById", () => {
expect(() => {
telemetry.getHistogramById("foo").add(3);
}).not.toThrow();
});
it("getKeyedHistogramById", () => {
expect(() => {
telemetry.getKeyedHistogramById("foo").add("foo", 3);
}).not.toThrow();
});
it("scalarSet", () => {
expect(() => {
telemetry.scalarSet("foo", 3);
}).not.toThrow();
});
it("scalarAdd", () => {
expect(() => {
telemetry.scalarAdd("foo", 3);
}).not.toThrow();
});
it("keyedScalarAdd", () => {
expect(() => {
telemetry.keyedScalarAdd("foo", "bar", 3);
}).not.toThrow();
});
it("setEventRecordingEnabled", () => {
expect(() => {
telemetry.setEventRecordingEnabled("foo", true);
}).not.toThrow();
});
it("preparePendingEvent", () => {
expect(() => {
telemetry.preparePendingEvent(
"devtools.main", "open", "inspector", null, ["foo", "bar"]);
}).not.toThrow();
});
it("addEventProperty", () => {
expect(() => {
telemetry.addEventProperty(
"devtools.main", "open", "inspector", "foo", "1");
}).not.toThrow();
});
it("addEventProperties", () => {
expect(() => {
telemetry.addEventProperties("devtools.main", "open", "inspector", {
"foo": "1",
"bar": "2"
});
}).not.toThrow();
});
it("_sendPendingEvent", () => {
expect(() => {
telemetry._sendPendingEvent("devtools.main", "open", "inspector", null);
}).not.toThrow();
});
it("recordEvent", () => {
expect(() => {
telemetry.recordEvent("devtools.main", "open", "inspector", null, {
"foo": "1",
"bar": "2"
});
}).not.toThrow();
});
it("toolOpened", () => {
expect(() => {
telemetry.toolOpened("foo");
}).not.toThrow();
});
it("toolClosed", () => {
expect(() => {
telemetry.toolClosed("foo");
}).not.toThrow();
});
});

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

@ -0,0 +1,228 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { getUnicodeUrl, getUnicodeUrlPath, getUnicodeHostname } =
require("../unicode-url");
describe("unicode-url", () => {
// List of URLs used to test Unicode URL conversion
const TEST_URLS = [
// Type: Readable ASCII URLs
// Expected: All of Unicode versions should equal to the raw.
{
raw: "https://example.org",
expectedUnicode: "https://example.org",
},
{
raw: "http://example.org",
expectedUnicode: "http://example.org",
},
{
raw: "ftp://example.org",
expectedUnicode: "ftp://example.org",
},
{
raw: "https://example.org.",
expectedUnicode: "https://example.org.",
},
{
raw: "https://example.org/",
expectedUnicode: "https://example.org/",
},
{
raw: "https://example.org/test",
expectedUnicode: "https://example.org/test",
},
{
raw: "https://example.org/test.html",
expectedUnicode: "https://example.org/test.html",
},
{
raw: "https://example.org/test.html?one=1&two=2",
expectedUnicode: "https://example.org/test.html?one=1&two=2",
},
{
raw: "https://example.org/test.html#here",
expectedUnicode: "https://example.org/test.html#here",
},
{
raw: "https://example.org/test.html?one=1&two=2#here",
expectedUnicode: "https://example.org/test.html?one=1&two=2#here",
},
// Type: Unreadable URLs with either Punycode domain names or URI-encoded
// paths
// Expected: Unreadable domain names and URI-encoded paths should be converted
// to readable Unicode.
{
raw: "https://xn--g6w.xn--8pv/test.html",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "https://\u6e2c.\u672c/test.html",
},
{
raw: "https://example.org/%E6%B8%AC%E8%A9%A6.html",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "https://example.org/\u6e2c\u8a66.html",
},
{
raw: "https://example.org/test.html?One=%E4%B8%80",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "https://example.org/test.html?One=\u4e00",
},
{
raw: "https://example.org/test.html?%E4%B8%80=1",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "https://example.org/test.html?\u4e00=1",
},
{
raw: "https://xn--g6w.xn--8pv/%E6%B8%AC%E8%A9%A6.html" +
"?%E4%B8%80=%E4%B8%80" +
"#%E6%AD%A4",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "https://\u6e2c.\u672c/\u6e2c\u8a66.html" +
"?\u4e00=\u4e00" +
"#\u6b64",
},
// Type: data: URIs
// Expected: All should not be converted.
{
raw: "data:text/plain;charset=UTF-8;Hello%20world",
expectedUnicode: "data:text/plain;charset=UTF-8;Hello%20world",
},
{
raw: "data:text/plain;charset=UTF-8;%E6%B8%AC%20%E8%A9%A6",
expectedUnicode: "data:text/plain;charset=UTF-8;%E6%B8%AC%20%E8%A9%A6",
},
{
raw: "data:image/png;base64,iVBORw0KGgoAAA" +
"ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4" +
"//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU" +
"5ErkJggg==",
expectedUnicode: "data:image/png;base64,iVBORw0KGgoAAA" +
"ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4" +
"//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU" +
"5ErkJggg==",
},
// Type: Malformed URLs
// Expected: All should not be converted.
{
raw: "://example.org/test",
expectedUnicode: "://example.org/test",
},
{
raw: "://xn--g6w.xn--8pv/%E6%B8%AC%E8%A9%A6.html" +
"?%E4%B8%80=%E4%B8%80",
expectedUnicode: "://xn--g6w.xn--8pv/%E6%B8%AC%E8%A9%A6.html" +
"?%E4%B8%80=%E4%B8%80",
},
{
// %E8%A9 isn't a valid UTF-8 code, so this URL is malformed.
raw: "https://xn--g6w.xn--8pv/%E6%B8%AC%E8%A9",
expectedUnicode: "https://xn--g6w.xn--8pv/%E6%B8%AC%E8%A9",
},
];
// List of hostanmes used to test Unicode hostname conversion
const TEST_HOSTNAMES = [
// Type: Readable ASCII hostnames
// Expected: All of Unicode versions should equal to the raw.
{
raw: "example",
expectedUnicode: "example",
},
{
raw: "example.org",
expectedUnicode: "example.org",
},
// Type: Unreadable Punycode hostnames
// Expected: Punycode should be converted to readable Unicode.
{
raw: "xn--g6w",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "\u6e2c",
},
{
raw: "xn--g6w.xn--8pv",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "\u6e2c.\u672c",
},
];
// List of URL paths used to test Unicode URL path conversion
const TEST_URL_PATHS = [
// Type: Readable ASCII URL paths
// Expected: All of Unicode versions should equal to the raw.
{
raw: "test",
expectedUnicode: "test",
},
{
raw: "/",
expectedUnicode: "/",
},
{
raw: "/test",
expectedUnicode: "/test",
},
{
raw: "/test.html?one=1&two=2#here",
expectedUnicode: "/test.html?one=1&two=2#here",
},
// Type: Unreadable URI-encoded URL paths
// Expected: URL paths should be converted to readable Unicode.
{
raw: "/%E6%B8%AC%E8%A9%A6",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "/\u6e2c\u8a66",
},
{
raw: "/%E6%B8%AC%E8%A9%A6.html",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "/\u6e2c\u8a66.html",
},
{
raw: "/%E6%B8%AC%E8%A9%A6.html" +
"?%E4%B8%80=%E4%B8%80&%E4%BA%8C=%E4%BA%8C" +
"#%E6%AD%A4",
// Do not type Unicode characters directly, because this test file isn't
// specified with a known encoding.
expectedUnicode: "/\u6e2c\u8a66.html" +
"?\u4e00=\u4e00&\u4e8c=\u4e8c" +
"#\u6b64",
},
// Type: Malformed URL paths
// Expected: All should not be converted.
{
// %E8%A9 isn't a valid UTF-8 code, so this URL is malformed.
raw: "/%E6%B8%AC%E8%A9",
expectedUnicode: "/%E6%B8%AC%E8%A9",
},
];
it("Get Unicode URLs", () => {
for (let url of TEST_URLS) {
expect(getUnicodeUrl(url.raw)).toBe(url.expectedUnicode);
}
});
it("Get Unicode hostnames", () => {
for (let hostname of TEST_HOSTNAMES) {
expect(getUnicodeHostname(hostname.raw)).toBe(hostname.expectedUnicode);
}
});
it("Get Unicode URL paths", () => {
for (let urlPath of TEST_URL_PATHS) {
expect(getUnicodeUrlPath(urlPath.raw)).toBe(urlPath.expectedUnicode);
}
});
});

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

@ -0,0 +1,115 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This file is a chrome-API-free version of the module
// devtools/client/shared/unicode-url.js in the mozilla-central repository, so
// that it can be used in Chrome-API-free applications, such as the Launchpad.
// But because of this, it cannot take advantage of utilizing chrome APIs and
// should implement the similar functionalities on its own.
//
// Please keep in mind that if the feature in this file has changed, don't
// forget to also change that accordingly in
// devtools/client/shared/unicode-url.js in the mozilla-central repository.
"use strict";
const punycode = require("punycode");
/**
* Gets a readble Unicode hostname from a hostname.
*
* If the `hostname` is a readable ASCII hostname, such as example.org, then
* this function will simply return the original `hostname`.
*
* If the `hostname` is a Punycode hostname representing a Unicode domain name,
* such as xn--g6w.xn--8pv, then this function will return the readable Unicode
* domain name by decoding the Punycode hostname.
*
* @param {string} hostname
* the hostname from which the Unicode hostname will be
* parsed, such as example.org, xn--g6w.xn--8pv.
* @return {string} The Unicode hostname. It may be the same as the `hostname`
* passed to this function if the `hostname` itself is
* a readable ASCII hostname or a Unicode hostname.
*/
function getUnicodeHostname(hostname) {
try {
return punycode.toUnicode(hostname);
} catch (err) {
}
return hostname;
}
/**
* Gets a readble Unicode URL pathname from a URL pathname.
*
* If the `urlPath` is a readable ASCII URL pathname, such as /a/b/c.js, then
* this function will simply return the original `urlPath`.
*
* If the `urlPath` is a URI-encoded pathname, such as %E8%A9%A6/%E6%B8%AC.js,
* then this function will return the readable Unicode pathname.
*
* If the `urlPath` is a malformed URL pathname, then this function will simply
* return the original `urlPath`.
*
* @param {string} urlPath
* the URL path from which the Unicode URL path will be parsed,
* such as /a/b/c.js, %E8%A9%A6/%E6%B8%AC.js.
* @return {string} The Unicode URL Path. It may be the same as the `urlPath`
* passed to this function if the `urlPath` itself is a readable
* ASCII url or a Unicode url.
*/
function getUnicodeUrlPath(urlPath) {
try {
return decodeURIComponent(urlPath);
} catch (err) {
}
return urlPath;
}
/**
* Gets a readable Unicode URL from a URL.
*
* If the `url` is a readable ASCII URL, such as http://example.org/a/b/c.js,
* then this function will simply return the original `url`.
*
* If the `url` includes either an unreadable Punycode domain name or an
* unreadable URI-encoded pathname, such as
* http://xn--g6w.xn--8pv/%E8%A9%A6/%E6%B8%AC.js, then this function will return
* the readable URL by decoding all its unreadable URL components to Unicode
* characters.
*
* If the `url` is a malformed URL, then this function will return the original
* `url`.
*
* If the `url` is a data: URI, then this function will return the original
* `url`.
*
* @param {string} url
* the full URL, or a data: URI. from which the readable URL
* will be parsed, such as, http://example.org/a/b/c.js,
* http://xn--g6w.xn--8pv/%E8%A9%A6/%E6%B8%AC.js
* @return {string} The readable URL. It may be the same as the `url` passed to
* this function if the `url` itself is readable.
*/
function getUnicodeUrl(url) {
try {
const { protocol, hostname } = new URL(url);
if (protocol === "data:") {
// Never convert a data: URI.
return url;
}
const readableHostname = getUnicodeHostname(hostname);
url = decodeURIComponent(url);
return url.replace(hostname, readableHostname);
} catch (err) {
}
return url;
}
module.exports = {
getUnicodeHostname,
getUnicodeUrlPath,
getUnicodeUrl,
};

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

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// See bug 1273941 to understand this choice of promise.
const Promise = require("../sham/promise");
/**
* Returns a deferred object, with a resolve and reject property.
* https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred
*/
module.exports = function defer() {
let resolve, reject;
let promise = new Promise(function () {
resolve = arguments[0];
reject = arguments[1];
});
return {
resolve: resolve,
reject: reject,
promise: promise
};
};

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

@ -0,0 +1,127 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var EventEmitter = function EventEmitter() {};
module.exports = EventEmitter;
const promise = require("./promise");
/**
* Decorate an object with event emitter functionality.
*
* @param Object aObjectToDecorate
* Bind all public methods of EventEmitter to
* the aObjectToDecorate object.
*/
EventEmitter.decorate = function EventEmitter_decorate (aObjectToDecorate) {
let emitter = new EventEmitter();
aObjectToDecorate.on = emitter.on.bind(emitter);
aObjectToDecorate.off = emitter.off.bind(emitter);
aObjectToDecorate.once = emitter.once.bind(emitter);
aObjectToDecorate.emit = emitter.emit.bind(emitter);
};
EventEmitter.prototype = {
/**
* Connect a listener.
*
* @param string aEvent
* The event name to which we're connecting.
* @param function aListener
* Called when the event is fired.
*/
on: function EventEmitter_on(aEvent, aListener) {
if (!this._eventEmitterListeners)
this._eventEmitterListeners = new Map();
if (!this._eventEmitterListeners.has(aEvent)) {
this._eventEmitterListeners.set(aEvent, []);
}
this._eventEmitterListeners.get(aEvent).push(aListener);
},
/**
* Listen for the next time an event is fired.
*
* @param string aEvent
* The event name to which we're connecting.
* @param function aListener
* (Optional) Called when the event is fired. Will be called at most
* one time.
* @return promise
* A promise which is resolved when the event next happens. The
* resolution value of the promise is the first event argument. If
* you need access to second or subsequent event arguments (it's rare
* that this is needed) then use aListener
*/
once: function EventEmitter_once(aEvent, aListener) {
let deferred = promise.defer();
let handler = (aEvent, aFirstArg, ...aRest) => {
this.off(aEvent, handler);
if (aListener) {
aListener.apply(null, [aEvent, aFirstArg, ...aRest]);
}
deferred.resolve(aFirstArg);
};
handler._originalListener = aListener;
this.on(aEvent, handler);
return deferred.promise;
},
/**
* Remove a previously-registered event listener. Works for events
* registered with either on or once.
*
* @param string aEvent
* The event name whose listener we're disconnecting.
* @param function aListener
* The listener to remove.
*/
off: function EventEmitter_off(aEvent, aListener) {
if (!this._eventEmitterListeners)
return;
let listeners = this._eventEmitterListeners.get(aEvent);
if (listeners) {
this._eventEmitterListeners.set(aEvent, listeners.filter(l => {
return l !== aListener && l._originalListener !== aListener;
}));
}
},
/**
* Emit an event. All arguments to this method will
* be sent to listener functions.
*/
emit: function EventEmitter_emit(aEvent) {
if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(aEvent)) {
return;
}
let originalListeners = this._eventEmitterListeners.get(aEvent);
for (let listener of this._eventEmitterListeners.get(aEvent)) {
// If the object was destroyed during event emission, stop
// emitting.
if (!this._eventEmitterListeners) {
break;
}
// If listeners were removed during emission, make sure the
// event handler we're going to fire wasn't removed.
if (originalListeners === this._eventEmitterListeners.get(aEvent) ||
this._eventEmitterListeners.get(aEvent).some(l => l === listener)) {
try {
listener.apply(null, arguments);
}
catch (ex) {
// Prevent a bad listener from interfering with the others.
let msg = ex + ": " + ex.stack;
//console.error(msg);
console.log(msg);
}
}
}
},
};

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

@ -0,0 +1,29 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* A sham for https://dxr.mozilla.org/mozilla-central/source/toolkit/modules/Promise.jsm
*/
/**
* Promise.jsm is mostly the Promise web API with a `defer` method. Just drop this in here,
* and use the native web API (although building with webpack/babel, it may replace this
* with it's own version if we want to target environments that do not have `Promise`.
*/
let p = typeof window != "undefined" ? window.Promise : Promise;
p.defer = function defer() {
var resolve, reject;
var promise = new Promise(function() {
resolve = arguments[0];
reject = arguments[1];
});
return {
resolve: resolve,
reject: reject,
promise: promise,
};
};
module.exports = p;

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

@ -0,0 +1,515 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* eslint-disable spaced-comment */
/* globals StopIteration */
/**
* This module implements a subset of "Task.js" <http://taskjs.org/>.
* It is a copy of toolkit/modules/Task.jsm. Please try not to
* diverge the API here.
*
* Paraphrasing from the Task.js site, tasks make sequential, asynchronous
* operations simple, using the power of JavaScript's "yield" operator.
*
* Tasks are built upon generator functions and promises, documented here:
*
* <https://developer.mozilla.org/en/JavaScript/Guide/Iterators_and_Generators>
* <http://wiki.commonjs.org/wiki/Promises/A>
*
* The "Task.spawn" function takes a generator function and starts running it as
* a task. Every time the task yields a promise, it waits until the promise is
* fulfilled. "Task.spawn" returns a promise that is resolved when the task
* completes successfully, or is rejected if an exception occurs.
*
* -----------------------------------------------------------------------------
*
* const {Task} = require("devtools/shared/task");
*
* Task.spawn(function* () {
*
* // This is our task. Let's create a promise object, wait on it and capture
* // its resolution value.
* let myPromise = getPromiseResolvedOnTimeoutWithValue(1000, "Value");
* let result = yield myPromise;
*
* // This part is executed only after the promise above is fulfilled (after
* // one second, in this imaginary example). We can easily loop while
* // calling asynchronous functions, and wait multiple times.
* for (let i = 0; i < 3; i++) {
* result += yield getPromiseResolvedOnTimeoutWithValue(50, "!");
* }
*
* return "Resolution result for the task: " + result;
* }).then(function (result) {
*
* // result == "Resolution result for the task: Value!!!"
*
* // The result is undefined if no value was returned.
*
* }, function (exception) {
*
* // Failure! We can inspect or report the exception.
*
* });
*
* -----------------------------------------------------------------------------
*
* This module implements only the "Task.js" interfaces described above, with no
* additional features to control the task externally, or do custom scheduling.
* It also provides the following extensions that simplify task usage in the
* most common cases:
*
* - The "Task.spawn" function also accepts an iterator returned by a generator
* function, in addition to a generator function. This way, you can call into
* the generator function with the parameters you want, and with "this" bound
* to the correct value. Also, "this" is never bound to the task object when
* "Task.spawn" calls the generator function.
*
* - In addition to a promise object, a task can yield the iterator returned by
* a generator function. The iterator is turned into a task automatically.
* This reduces the syntax overhead of calling "Task.spawn" explicitly when
* you want to recurse into other task functions.
*
* - The "Task.spawn" function also accepts a primitive value, or a function
* returning a primitive value, and treats the value as the result of the
* task. This makes it possible to call an externally provided function and
* spawn a task from it, regardless of whether it is an asynchronous generator
* or a synchronous function. This comes in handy when iterating over
* function lists where some items have been converted to tasks and some not.
*/
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Promise = require("../sham/promise");
const defer = require("./defer");
// The following error types are considered programmer errors, which should be
// reported (possibly redundantly) so as to let programmers fix their code.
const ERRORS_TO_REPORT = ["EvalError", "RangeError", "ReferenceError",
"TypeError"];
/**
* The Task currently being executed
*/
var gCurrentTask = null;
/**
* If `true`, capture stacks whenever entering a Task and rewrite the
* stack any exception thrown through a Task.
*/
var gMaintainStack = false;
/**
* Iterate through the lines of a string.
*
* @return Iterator<string>
*/
function* linesOf(string) {
let reLine = /([^\r\n])+/g;
let match;
while ((match = reLine.exec(string))) {
yield [match[0], match.index];
}
}
/**
* Detect whether a value is a generator.
*
* @param aValue
* The value to identify.
* @return A boolean indicating whether the value is a generator.
*/
function isGenerator(value) {
return Object.prototype.toString.call(value) == "[object Generator]";
}
////////////////////////////////////////////////////////////////////////////////
//// Task
/**
* This object provides the public module functions.
*/
var Task = {
/**
* Creates and starts a new task.
*
* @param task
* - If you specify a generator function, it is called with no
* arguments to retrieve the associated iterator. The generator
* function is a task, that is can yield promise objects to wait
* upon.
* - If you specify the iterator returned by a generator function you
* called, the generator function is also executed as a task. This
* allows you to call the function with arguments.
* - If you specify a function that is not a generator, it is called
* with no arguments, and its return value is used to resolve the
* returned promise.
* - If you specify anything else, you get a promise that is already
* resolved with the specified value.
*
* @return A promise object where you can register completion callbacks to be
* called when the task terminates.
*/
spawn: function (task) {
return createAsyncFunction(task)();
},
/**
* Create and return an 'async function' that starts a new task.
*
* This is similar to 'spawn' except that it doesn't immediately start
* the task, it binds the task to the async function's 'this' object and
* arguments, and it requires the task to be a function.
*
* It simplifies the common pattern of implementing a method via a task,
* like this simple object with a 'greet' method that has a 'name' parameter
* and spawns a task to send a greeting and return its reply:
*
* let greeter = {
* message: "Hello, NAME!",
* greet: function(name) {
* return Task.spawn((function* () {
* return yield sendGreeting(this.message.replace(/NAME/, name));
* }).bind(this);
* })
* };
*
* With Task.async, the method can be declared succinctly:
*
* let greeter = {
* message: "Hello, NAME!",
* greet: Task.async(function* (name) {
* return yield sendGreeting(this.message.replace(/NAME/, name));
* })
* };
*
* While maintaining identical semantics:
*
* greeter.greet("Mitchell").then((reply) => { ... }); // behaves the same
*
* @param task
* The task function to start.
*
* @return A function that starts the task function and returns its promise.
*/
async: function (task) {
if (typeof (task) != "function") {
throw new TypeError("task argument must be a function");
}
return createAsyncFunction(task);
},
/**
* Constructs a special exception that, when thrown inside a legacy generator
* function (non-star generator), allows the associated task to be resolved
* with a specific value.
*
* Example: throw new Task.Result("Value");
*/
Result: function (value) {
this.value = value;
}
};
function createAsyncFunction(task) {
let asyncFunction = function () {
let result = task;
if (task && typeof (task) == "function") {
if (task.isAsyncFunction) {
throw new TypeError(
"Cannot use an async function in place of a promise. " +
"You should either invoke the async function first " +
"or use 'Task.spawn' instead of 'Task.async' to start " +
"the Task and return its promise.");
}
try {
// Let's call into the function ourselves.
result = task.apply(this, arguments);
} catch (ex) {
if (ex instanceof Task.Result) {
return Promise.resolve(ex.value);
}
return Promise.reject(ex);
}
}
if (isGenerator(result)) {
// This is an iterator resulting from calling a generator function.
return new TaskImpl(result).deferred.promise;
}
// Just propagate the given value to the caller as a resolved promise.
return Promise.resolve(result);
};
asyncFunction.isAsyncFunction = true;
return asyncFunction;
}
////////////////////////////////////////////////////////////////////////////////
//// TaskImpl
/**
* Executes the specified iterator as a task, and gives access to the promise
* that is fulfilled when the task terminates.
*/
function TaskImpl(iterator) {
if (gMaintainStack) {
this._stack = (new Error()).stack;
}
this.deferred = defer();
this._iterator = iterator;
this._isStarGenerator = !("send" in iterator);
this._run(true);
}
TaskImpl.prototype = {
/**
* Includes the promise object where task completion callbacks are registered,
* and methods to resolve or reject the promise at task completion.
*/
deferred: null,
/**
* The iterator returned by the generator function associated with this task.
*/
_iterator: null,
/**
* Whether this Task is using a star generator.
*/
_isStarGenerator: false,
/**
* Main execution routine, that calls into the generator function.
*
* @param sendResolved
* If true, indicates that we should continue into the generator
* function regularly (if we were waiting on a promise, it was
* resolved). If true, indicates that we should cause an exception to
* be thrown into the generator function (if we were waiting on a
* promise, it was rejected).
* @param sendValue
* Resolution result or rejection exception, if any.
*/
_run: function (sendResolved, sendValue) {
try {
gCurrentTask = this;
if (this._isStarGenerator) {
try {
let result = sendResolved ? this._iterator.next(sendValue)
: this._iterator.throw(sendValue);
if (result.done) {
// The generator function returned.
this.deferred.resolve(result.value);
} else {
// The generator function yielded.
this._handleResultValue(result.value);
}
} catch (ex) {
// The generator function failed with an uncaught exception.
this._handleException(ex);
}
} else {
try {
let yielded = sendResolved ? this._iterator.send(sendValue)
: this._iterator.throw(sendValue);
this._handleResultValue(yielded);
} catch (ex) {
if (ex instanceof Task.Result) {
// The generator function threw the special exception that
// allows it to return a specific value on resolution.
this.deferred.resolve(ex.value);
} else if (ex instanceof StopIteration) {
// The generator function terminated with no specific result.
this.deferred.resolve(undefined);
} else {
// The generator function failed with an uncaught exception.
this._handleException(ex);
}
}
}
} finally {
//
// At this stage, the Task may have finished executing, or have
// walked through a `yield` or passed control to a sub-Task.
// Regardless, if we still own `gCurrentTask`, reset it. If we
// have not finished execution of this Task, re-entering `_run`
// will set `gCurrentTask` to `this` as needed.
//
// We just need to be careful here in case we hit the following
// pattern:
//
// Task.spawn(foo);
// Task.spawn(bar);
//
// Here, `foo` and `bar` may be interleaved, so when we finish
// executing `foo`, `gCurrentTask` may actually either `foo` or
// `bar`. If `gCurrentTask` has already been set to `bar`, leave
// it be and it will be reset to `null` once `bar` is complete.
//
if (gCurrentTask == this) {
gCurrentTask = null;
}
}
},
/**
* Handle a value yielded by a generator.
*
* @param value
* The yielded value to handle.
*/
_handleResultValue: function (value) {
// If our task yielded an iterator resulting from calling another
// generator function, automatically spawn a task from it, effectively
// turning it into a promise that is fulfilled on task completion.
if (isGenerator(value)) {
value = Task.spawn(value);
}
if (value && typeof (value.then) == "function") {
// We have a promise object now. When fulfilled, call again into this
// function to continue the task, with either a resolution or rejection
// condition.
value.then(this._run.bind(this, true),
this._run.bind(this, false));
} else {
// If our task yielded a value that is not a promise, just continue and
// pass it directly as the result of the yield statement.
this._run(true, value);
}
},
/**
* Handle an uncaught exception thrown from a generator.
*
* @param exception
* The uncaught exception to handle.
*/
_handleException: function (exception) {
gCurrentTask = this;
if (exception && typeof exception == "object" && "stack" in exception) {
let stack = exception.stack;
if (gMaintainStack &&
exception._capturedTaskStack != this._stack &&
typeof stack == "string") {
// Rewrite the stack for more readability.
let bottomStack = this._stack;
stack = Task.Debugging.generateReadableStack(stack);
exception.stack = stack;
// If exception is reinjected in the same task and rethrown,
// we don't want to perform the rewrite again.
exception._capturedTaskStack = bottomStack;
} else if (!stack) {
stack = "Not available";
}
if ("name" in exception &&
ERRORS_TO_REPORT.indexOf(exception.name) != -1) {
// We suspect that the exception is a programmer error, so we now
// display it using dump(). Note that we do not use Cu.reportError as
// we assume that this is a programming error, so we do not want end
// users to see it. Also, if the programmer handles errors correctly,
// they will either treat the error or log them somewhere.
dump("*************************\n");
dump("A coding exception was thrown and uncaught in a Task.\n\n");
dump("Full message: " + exception + "\n");
dump("Full stack: " + exception.stack + "\n");
dump("*************************\n");
}
}
this.deferred.reject(exception);
},
get callerStack() {
// Cut `this._stack` at the last line of the first block that
// contains task.js, keep the tail.
for (let [line, index] of linesOf(this._stack || "")) {
if (line.indexOf("/task.js:") == -1) {
return this._stack.substring(index);
}
}
return "";
}
};
Task.Debugging = {
/**
* Control stack rewriting.
*
* If `true`, any exception thrown from a Task will be rewritten to
* provide a human-readable stack trace. Otherwise, stack traces will
* be left unchanged.
*
* There is a (small but existing) runtime cost associated to stack
* rewriting, so you should probably not activate this in production
* code.
*
* @type {bool}
*/
get maintainStack() {
return gMaintainStack;
},
set maintainStack(x) {
if (!x) {
gCurrentTask = null;
}
gMaintainStack = x;
return x;
},
/**
* Generate a human-readable stack for an error raised in
* a Task.
*
* @param {string} topStack The stack provided by the error.
* @param {string=} prefix Optionally, a prefix for each line.
*/
generateReadableStack: function (topStack, prefix = "") {
if (!gCurrentTask) {
return topStack;
}
// Cut `topStack` at the first line that contains task.js, keep the head.
let lines = [];
for (let [line] of linesOf(topStack)) {
if (line.indexOf("/task.js:") != -1) {
break;
}
lines.push(prefix + line);
}
if (!prefix) {
lines.push(gCurrentTask.callerStack);
} else {
for (let [line] of linesOf(gCurrentTask.callerStack)) {
lines.push(prefix + line);
}
}
return lines.join("\n");
}
};
exports.Task = Task;

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

@ -0,0 +1,331 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/**
* This is a stub of the DevTools telemetry module and will be replaced by the
* full version of the file by Webpack for running inside Firefox.
*/
class Telemetry {
/**
* Time since the system wide epoch. This is not a monotonic timer but
* can be used across process boundaries.
*/
get msSystemNow() {
return 0;
}
/**
* Starts a timer associated with a telemetry histogram. The timer can be
* directly associated with a histogram, or with a pair of a histogram and
* an object.
*
* @param {String} histogramId
* A string which must be a valid histogram name.
* @param {Object} obj
* Optional parameter. If specified, the timer is associated with this
* object, meaning that multiple timers for the same histogram may be
* run concurrently, as long as they are associated with different
* objects.
*
* @returns {Boolean}
* True if the timer was successfully started, false otherwise. If a
* timer already exists, it can't be started again, and the existing
* one will be cleared in order to avoid measurements errors.
*/
start(histogramId, obj) {
return true;
}
/**
* Starts a timer associated with a keyed telemetry histogram. The timer can
* be directly associated with a histogram and its key. Similarly to
* TelemetryStopwatch.start the histogram and its key can be associated
* with an object. Each key may have multiple associated objects and each
* object can be associated with multiple keys.
*
* @param {String} histogramId
* A string which must be a valid histogram name.
* @param {String} key
* A string which must be a valid histgram key.
* @param {Object} obj
* Optional parameter. If specified, the timer is associated with this
* object, meaning that multiple timers for the same histogram may be
* run concurrently,as long as they are associated with different
* objects.
*
* @returns {Boolean}
* True if the timer was successfully started, false otherwise. If a
* timer already exists, it can't be started again, and the existing
* one will be cleared in order to avoid measurements errors.
*/
startKeyed(histogramId, key, obj) {
return true;
}
/**
* Stops the timer associated with the given histogram (and object),
* calculates the time delta between start and finish, and adds the value
* to the histogram.
*
* @param {String} histogramId
* A string which must be a valid histogram name.
* @param {Object} obj
* Optional parameter which associates the histogram timer with the
* given object.
* @param {Boolean} canceledOkay
* Optional parameter which will suppress any warnings that normally
* fire when a stopwatch is finished after being cancelled.
* Defaults to false.
*
* @returns {Boolean}
* True if the timer was succesfully stopped and the data was added
* to the histogram, False otherwise.
*/
finish(histogramId, obj, canceledOkay) {
return true;
}
/**
* Stops the timer associated with the given keyed histogram (and object),
* calculates the time delta between start and finish, and adds the value
* to the keyed histogram.
*
* @param {String} histogramId
* A string which must be a valid histogram name.
* @param {String} key
* A string which must be a valid histogram key.
* @param {Object} obj
* Optional parameter which associates the histogram timer with the
* given object.
* @param {Boolean} canceledOkay
* Optional parameter which will suppress any warnings that normally
* fire when a stopwatch is finished after being cancelled.
* Defaults to false.
*
* @returns {Boolean}
* True if the timer was succesfully stopped and the data was added
* to the histogram, False otherwise.
*/
finishKeyed(histogramId, key, obj, cancelledOkay) {
return true;
}
/**
* Log a value to a histogram.
*
* @param {String} histogramId
* Histogram in which the data is to be stored.
*/
getHistogramById(histogramId) {
return {
add: () => {}
};
}
/**
* Get a keyed histogram.
*
* @param {String} histogramId
* Histogram in which the data is to be stored.
*/
getKeyedHistogramById(histogramId) {
return {
add: () => {}
};
}
/**
* Log a value to a scalar.
*
* @param {String} scalarId
* Scalar in which the data is to be stored.
* @param value
* Value to store.
*/
scalarSet(scalarId, value) {}
/**
* Log a value to a count scalar.
*
* @param {String} scalarId
* Scalar in which the data is to be stored.
* @param value
* Value to store.
*/
scalarAdd(scalarId, value) {}
/**
* Log a value to a keyed count scalar.
*
* @param {String} scalarId
* Scalar in which the data is to be stored.
* @param {String} key
* The key within the scalar.
* @param value
* Value to store.
*/
keyedScalarAdd(scalarId, key, value) {}
/**
* Event telemetry is disabled by default. Use this method to enable it for
* a particular category.
*
* @param {Boolean} enabled
* Enabled: true or false.
*/
setEventRecordingEnabled(enabled) {
return enabled;
}
/**
* Telemetry events often need to make use of a number of properties from
* completely different codepaths. To make this possible we create a
* "pending event" along with an array of property names that we need to wait
* for before sending the event.
*
* As each property is received via addEventProperty() we check if all
* properties have been received. Once they have all been received we send the
* telemetry event.
*
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
* @param {String} object
* The telemetry event object name (the name of the object the event
* occurred on) e.g. "tools" or "setting"
* @param {String|null} value
* The telemetry event value (a user defined value, providing context
* for the event) e.g. "console"
* @param {Array} expected
* An array of the properties needed before sending the telemetry
* event e.g.
* [
* "host",
* "width"
* ]
*/
preparePendingEvent(obj, method, object, value, expected = []) {}
/**
* Adds an expected property for either a current or future pending event.
* This means that if preparePendingEvent() is called before or after sending
* the event properties they will automatically added to the event.
*
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
* @param {String} object
* The telemetry event object name (the name of the object the event
* occurred on) e.g. "tools" or "setting"
* @param {String|null} value
* The telemetry event value (a user defined value, providing context
* for the event) e.g. "console"
* @param {String} pendingPropName
* The pending property name
* @param {String} pendingPropValue
* The pending property value
*/
addEventProperty(obj, method, object, value, pendingPropName, pendingPropValue) {}
/**
* Adds expected properties for either a current or future pending event.
* This means that if preparePendingEvent() is called before or after sending
* the event properties they will automatically added to the event.
*
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
* @param {String} object
* The telemetry event object name (the name of the object the event
* occurred on) e.g. "tools" or "setting"
* @param {String|null} value
* The telemetry event value (a user defined value, providing context
* for the event) e.g. "console"
* @param {String} pendingObject
* An object containing key, value pairs that should be added to the
* event as properties.
*/
addEventProperties(obj, method, object, value, pendingObject) {}
/**
* A private method that is not to be used externally. This method is used to
* prepare a pending telemetry event for sending and then send it via
* recordEvent().
*
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
* @param {String} object
* The telemetry event object name (the name of the object the event
* occurred on) e.g. "tools" or "setting"
* @param {String|null} value
* The telemetry event value (a user defined value, providing context
* for the event) e.g. "console"
*/
_sendPendingEvent(obj, method, object, value) {}
/**
* Send a telemetry event.
*
* @param {String} method
* The telemetry event method (describes the type of event that
* occurred e.g. "open")
* @param {String} object
* The telemetry event object name (the name of the object the event
* occurred on) e.g. "tools" or "setting"
* @param {String|null} value
* The telemetry event value (a user defined value, providing context
* for the event) e.g. "console"
* @param {Object} extra
* The telemetry event extra object containing the properties that will
* be sent with the event e.g.
* {
* host: "bottom",
* width: "1024"
* }
*/
recordEvent(method, object, value, extra) {}
/**
* Sends telemetry pings to indicate that a tool has been opened.
*
* @param {String} id
* The ID of the tool opened.
* @param {String} sessionId
* Toolbox session id used when we need to ensure a tool really has a
* timer before calculating a delta.
* @param {Object} obj
* The telemetry event or ping is associated with this object, meaning
* that multiple events or pings for the same histogram may be run
* concurrently, as long as they are associated with different objects.
*/
toolOpened(id, sessionId, obj) {}
/**
* Sends telemetry pings to indicate that a tool has been closed.
*
* @param {String} id
* The ID of the tool opened.
*/
toolClosed(id, sessionId, obj) {}
}
module.exports = Telemetry;

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

@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Empty shim for "devtools/client/shared/zoom-keys" module
*
* Based on nsIMarkupDocumentViewer.fullZoom API
* https://developer.mozilla.org/en-US/Firefox/Releases/3/Full_page_zoom
*/
exports.register = function (window) {
};

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

@ -4673,14 +4673,6 @@ devtools-mc-assets@^0.0.8:
resolved "https://registry.yarnpkg.com/devtools-mc-assets/-/devtools-mc-assets-0.0.8.tgz#47fc23240a916ed2c7d7bc6cc859882d49527e84"
integrity sha512-+nvHMtga7sji1nCL+RaGyqZTfrmJtsdcsQIkI3VQuxvnRMcih59XcxzTrlEn9tQtQFLL0rNk5oqqbPfrKrCstA==
devtools-modules@~1.1.6, devtools-modules@~1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/devtools-modules/-/devtools-modules-1.1.9.tgz#b095ff634229ae7f27fc1883757dbb5ca8b830db"
integrity sha512-Gpwene1bA1vuLi4lu/1jqXZDjLijFCR/O08JXMcxls30dabctaav7XiU3mrg/bro/LrJoPxFro0OM1M7DRY0Vg==
dependencies:
devtools-services "0.0.1"
punycode "^2.1.0"
devtools-services@0.0.1, devtools-services@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/devtools-services/-/devtools-services-0.0.1.tgz#9042600c11d1f4d45cc6ca299588a86fac1fbdd5"