import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
189
browser/extensions/aushelper/bootstrap.js
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
const APP_UPDATE_URL_PREF = "app.update.url";
|
||||
const REPLACE_KEY = "%OS_VERSION%";
|
||||
|
||||
const AUSHELPER_CPU_RESULT_CODE_HISTOGRAM_ID = "AUSHELPER_CPU_RESULT_CODE";
|
||||
// The system is not vulnerable to Bug 1296630.
|
||||
const CPU_NO_BUG1296630 = 1;
|
||||
// The system is vulnerable to Bug 1296630.
|
||||
const CPU_YES_BUG1296630 = 2;
|
||||
// An error occured when checking if the system is vulnerable to Bug 1296630.
|
||||
const CPU_ERR_BUG1296630 = 3;
|
||||
// It is unknown whether the system is vulnerable to Bug 1296630 (should never happen).
|
||||
const CPU_UNKNOWN_BUG1296630 = 4;
|
||||
|
||||
const AUSHELPER_CPU_ERROR_CODE_HISTOGRAM_ID = "AUSHELPER_CPU_ERROR_CODE";
|
||||
const CPU_SUCCESS = 0;
|
||||
const CPU_REG_OPEN_ERROR = 1;
|
||||
const CPU_VENDOR_ID_ERROR = 2;
|
||||
const CPU_ID_ERROR = 4;
|
||||
const CPU_REV_ERROR = 8;
|
||||
|
||||
const AUSHELPER_WEBSENSE_REG_VERSION_SCALAR_NAME = "aushelper.websense_reg_version";
|
||||
const AUSHELPER_WEBSENSE_REG_EXISTS_HISTOGRAM_ID = "AUSHELPER_WEBSENSE_REG_EXISTS";
|
||||
|
||||
const AUSHELPER_WEBSENSE_ERROR_CODE_HISTOGRAM_ID = "AUSHELPER_WEBSENSE_ERROR_CODE";
|
||||
const WEBSENSE_SUCCESS = 0;
|
||||
const WEBSENSE_REG_OPEN_ERROR = 1;
|
||||
const WEBSENSE_REG_READ_ERROR = 2;
|
||||
const WEBSENSE_ALREADY_MODIFIED = 4;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function startup() {
|
||||
if (Services.appinfo.OS != "WINNT") {
|
||||
return;
|
||||
}
|
||||
|
||||
const regCPUPath = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
|
||||
let wrk;
|
||||
let cpuErrorCode = CPU_SUCCESS;
|
||||
try {
|
||||
wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey);
|
||||
wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, regCPUPath, wrk.ACCESS_READ);
|
||||
} catch (e) {
|
||||
Cu.reportError("AUSHelper - unable to open registry. Exception: " + e);
|
||||
cpuErrorCode |= CPU_REG_OPEN_ERROR;
|
||||
}
|
||||
|
||||
// If any of the following values are successfully retrieved and they don't
|
||||
// match the condition for that value then it is safe to update. Hence why the
|
||||
// following checks are somewhat convoluted. The possible values for the
|
||||
// variable set by each check is as follows:
|
||||
//
|
||||
// | Match | No Match | Error |
|
||||
// variable | true | false | null |
|
||||
|
||||
let cpuVendorIDMatch = false;
|
||||
try {
|
||||
let cpuVendorID = wrk.readStringValue("VendorIdentifier");
|
||||
if (cpuVendorID.toLowerCase() == "genuineintel") {
|
||||
cpuVendorIDMatch = true;
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError("AUSHelper - error getting CPU vendor indentifier. Exception: " + e);
|
||||
cpuVendorIDMatch = null;
|
||||
cpuErrorCode |= CPU_VENDOR_ID_ERROR;
|
||||
}
|
||||
|
||||
let cpuIDMatch = false;
|
||||
try {
|
||||
let cpuID = wrk.readStringValue("Identifier");
|
||||
if (cpuID.toLowerCase().indexOf("family 6 model 61 stepping 4") != -1) {
|
||||
cpuIDMatch = true;
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError("AUSHelper - error getting CPU indentifier. Exception: " + e);
|
||||
cpuIDMatch = null;
|
||||
cpuErrorCode |= CPU_ID_ERROR;
|
||||
}
|
||||
|
||||
let microCodeVersions = [0xe, 0x11, 0x12, 0x13, 0x16, 0x18, 0x19];
|
||||
let cpuRevMatch = null;
|
||||
try {
|
||||
let keyNames = ["Update Revision", "Update Signature"];
|
||||
for (let i = 0; i < keyNames.length; ++i) {
|
||||
try {
|
||||
let regVal = wrk.readBinaryValue(keyNames[i]);
|
||||
if (regVal.length == 8) {
|
||||
let hexVal = [];
|
||||
// We are only inyterested in the upper 4 bytes and the little endian
|
||||
// value for it.
|
||||
for (let j = 4; j < 8; j++) {
|
||||
let c = regVal.charCodeAt(j).toString(16);
|
||||
if (c.length == 1) {
|
||||
c = "0" + c;
|
||||
}
|
||||
hexVal.unshift(c);
|
||||
}
|
||||
cpuRevMatch = false;
|
||||
if (microCodeVersions.indexOf(parseInt(hexVal.join(''))) != -1) {
|
||||
cpuRevMatch = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
if (i == keyNames.length - 1) {
|
||||
// The registry key name's value was not successfully queried.
|
||||
cpuRevMatch = null;
|
||||
cpuErrorCode |= CPU_REV_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
wrk.close();
|
||||
} catch (ex) {
|
||||
Cu.reportError("AUSHelper - error getting CPU revision. Exception: " + ex);
|
||||
cpuRevMatch = null;
|
||||
cpuErrorCode |= CPU_REV_ERROR;
|
||||
}
|
||||
|
||||
let cpuResult = CPU_UNKNOWN_BUG1296630;
|
||||
let cpuValue = "(unkBug1296630v1)";
|
||||
// The following uses strict equality checks since the values can be true,
|
||||
// false, or null.
|
||||
if (cpuVendorIDMatch === false || cpuIDMatch === false || cpuRevMatch === false) {
|
||||
// Since one of the values is false then the system won't be affected by
|
||||
// bug 1296630 according to the conditions set out in bug 1311515.
|
||||
cpuValue = "(noBug1296630v1)";
|
||||
cpuResult = CPU_NO_BUG1296630;
|
||||
} else if (cpuVendorIDMatch === null || cpuIDMatch === null || cpuRevMatch === null) {
|
||||
// Since one of the values is null we can't say for sure if the system will
|
||||
// be affected by bug 1296630.
|
||||
cpuValue = "(errBug1296630v1)";
|
||||
cpuResult = CPU_ERR_BUG1296630;
|
||||
} else if (cpuVendorIDMatch === true && cpuIDMatch === true && cpuRevMatch === true) {
|
||||
// Since all of the values are true we can say that the system will be
|
||||
// affected by bug 1296630.
|
||||
cpuValue = "(yesBug1296630v1)";
|
||||
cpuResult = CPU_YES_BUG1296630;
|
||||
}
|
||||
|
||||
Services.telemetry.getHistogramById(AUSHELPER_CPU_RESULT_CODE_HISTOGRAM_ID).add(cpuResult);
|
||||
Services.telemetry.getHistogramById(AUSHELPER_CPU_ERROR_CODE_HISTOGRAM_ID).add(cpuErrorCode);
|
||||
|
||||
const regWebsensePath = "Websense\\Agent";
|
||||
let websenseErrorCode = WEBSENSE_SUCCESS;
|
||||
let websenseVersion = "";
|
||||
try {
|
||||
let regModes = [wrk.ACCESS_READ, wrk.ACCESS_READ | wrk.WOW64_64];
|
||||
for (let i = 0; i < regModes.length; ++i) {
|
||||
wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, "SOFTWARE", regModes[i]);
|
||||
try {
|
||||
if (wrk.hasChild(regWebsensePath)) {
|
||||
let childKey = wrk.openChild(regWebsensePath, wrk.ACCESS_READ);
|
||||
websenseVersion = childKey.readStringValue("InstallVersion");
|
||||
Services.telemetry.scalarSet(AUSHELPER_WEBSENSE_REG_VERSION_SCALAR_NAME, websenseVersion);
|
||||
}
|
||||
wrk.close();
|
||||
} catch (e) {
|
||||
Cu.reportError("AUSHelper - unable to read registry. Exception: " + e);
|
||||
websenseErrorCode |= WEBSENSE_REG_READ_ERROR;
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError("AUSHelper - unable to open registry. Exception: " + ex);
|
||||
websenseErrorCode |= WEBSENSE_REG_OPEN_ERROR;
|
||||
}
|
||||
|
||||
Services.telemetry.getHistogramById(AUSHELPER_WEBSENSE_REG_EXISTS_HISTOGRAM_ID).add(!!websenseVersion);
|
||||
let websenseValue = "(" + (websenseVersion ? "websense-" + websenseVersion : "nowebsense") + ")";
|
||||
|
||||
let branch = Services.prefs.getDefaultBranch("");
|
||||
let curValue = branch.getCharPref(APP_UPDATE_URL_PREF);
|
||||
if (curValue.indexOf(REPLACE_KEY + "/") > -1) {
|
||||
let newValue = curValue.replace(REPLACE_KEY + "/", REPLACE_KEY + cpuValue + websenseValue + "/");
|
||||
branch.setCharPref(APP_UPDATE_URL_PREF, newValue);
|
||||
} else {
|
||||
websenseErrorCode |= WEBSENSE_ALREADY_MODIFIED;
|
||||
}
|
||||
Services.telemetry.getHistogramById(AUSHELPER_WEBSENSE_ERROR_CODE_HISTOGRAM_ID).add(websenseErrorCode);
|
||||
}
|
||||
function shutdown() {}
|
||||
function install() {}
|
||||
function uninstall() {}
|
||||
32
browser/extensions/aushelper/install.rdf.in
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
#filter substitution
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>aushelper@mozilla.org</em:id>
|
||||
<em:version>2.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
||||
<!-- Target Application this extension can install into,
|
||||
with minimum and maximum supported versions. -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
|
||||
<em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Application Update Service Helper</em:name>
|
||||
<em:description>Sets value(s) in the update url based on custom checks.</em:description>
|
||||
</Description>
|
||||
</RDF>
|
||||
16
browser/extensions/aushelper/moz.build
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
||||
|
||||
FINAL_TARGET_FILES.features['aushelper@mozilla.org'] += [
|
||||
'bootstrap.js'
|
||||
]
|
||||
|
||||
FINAL_TARGET_PP_FILES.features['aushelper@mozilla.org'] += [
|
||||
'install.rdf.in'
|
||||
]
|
||||
197
browser/extensions/e10srollout/bootstrap.js
vendored
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/UpdateUtils.jsm");
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
// The amount of people to be part of e10s
|
||||
const TEST_THRESHOLD = {
|
||||
"beta" : 0.5, // 50%
|
||||
"release" : 1.0, // 100%
|
||||
"esr" : 1.0, // 100%
|
||||
};
|
||||
|
||||
const ADDON_ROLLOUT_POLICY = {
|
||||
"beta" : "51alladdons", // Any WebExtension or addon except with mpc = false
|
||||
"release" : "51set1",
|
||||
"esr" : "esrA", // WebExtensions and Addons with mpc=true
|
||||
};
|
||||
|
||||
if (AppConstants.RELEASE_OR_BETA) {
|
||||
// Bug 1348576 - e10s is never enabled for non-official release builds
|
||||
// This is hacky, but the problem it solves is the following:
|
||||
// the e10s rollout is controlled by the channel name, which
|
||||
// is the only way to distinguish between Beta and Release.
|
||||
// However, non-official release builds (like the ones done by distros
|
||||
// to ship Firefox on their package managers) do not set a value
|
||||
// for the release channel, which gets them to the default value
|
||||
// of.. (drumroll) "default".
|
||||
// But we can't just always configure the same settings for the
|
||||
// "default" channel because that's also the name that a locally
|
||||
// built Firefox gets, and e10s is managed in a different way
|
||||
// there (directly by prefs, on Nightly and Aurora).
|
||||
TEST_THRESHOLD.default = TEST_THRESHOLD.release;
|
||||
ADDON_ROLLOUT_POLICY.default = ADDON_ROLLOUT_POLICY.release;
|
||||
}
|
||||
|
||||
|
||||
const PREF_COHORT_SAMPLE = "e10s.rollout.cohortSample";
|
||||
const PREF_COHORT_NAME = "e10s.rollout.cohort";
|
||||
const PREF_E10S_OPTED_IN = "browser.tabs.remote.autostart";
|
||||
const PREF_E10S_FORCE_ENABLED = "browser.tabs.remote.force-enable";
|
||||
const PREF_E10S_FORCE_DISABLED = "browser.tabs.remote.force-disable";
|
||||
const PREF_TOGGLE_E10S = "browser.tabs.remote.autostart.2";
|
||||
const PREF_E10S_ADDON_POLICY = "extensions.e10s.rollout.policy";
|
||||
const PREF_E10S_ADDON_BLOCKLIST = "extensions.e10s.rollout.blocklist";
|
||||
const PREF_E10S_HAS_NONEXEMPT_ADDON = "extensions.e10s.rollout.hasAddon";
|
||||
|
||||
function startup() {
|
||||
// In theory we only need to run this once (on install()), but
|
||||
// it's better to also run it on every startup. If the user has
|
||||
// made manual changes to the prefs, this will keep the data
|
||||
// reported more accurate.
|
||||
// It's also fine (and preferred) to just do it here on startup
|
||||
// (instead of observing prefs), because e10s takes a restart
|
||||
// to take effect, so we keep the data based on how it was when
|
||||
// the session started.
|
||||
defineCohort();
|
||||
}
|
||||
|
||||
function install() {
|
||||
defineCohort();
|
||||
}
|
||||
|
||||
let cohortDefinedOnThisSession = false;
|
||||
|
||||
function defineCohort() {
|
||||
// Avoid running twice when it was called by install() first
|
||||
if (cohortDefinedOnThisSession) {
|
||||
return;
|
||||
}
|
||||
cohortDefinedOnThisSession = true;
|
||||
|
||||
let updateChannel = UpdateUtils.getUpdateChannel(false);
|
||||
if (!(updateChannel in TEST_THRESHOLD)) {
|
||||
setCohort("unsupportedChannel");
|
||||
return;
|
||||
}
|
||||
|
||||
let addonPolicy = "unknown";
|
||||
if (updateChannel in ADDON_ROLLOUT_POLICY) {
|
||||
addonPolicy = ADDON_ROLLOUT_POLICY[updateChannel];
|
||||
Preferences.set(PREF_E10S_ADDON_POLICY, addonPolicy);
|
||||
// This is also the proper place to set the blocklist pref
|
||||
// in case it is necessary.
|
||||
|
||||
Preferences.set(PREF_E10S_ADDON_BLOCKLIST,
|
||||
// bug 1185672 - Tab Mix Plus
|
||||
"{dc572301-7619-498c-a57d-39143191b318};" +
|
||||
// bug 1332692 - LastPass
|
||||
"support@lastpass.com;");
|
||||
} else {
|
||||
Preferences.reset(PREF_E10S_ADDON_POLICY);
|
||||
}
|
||||
|
||||
let userOptedOut = optedOut();
|
||||
let userOptedIn = optedIn();
|
||||
let disqualified = (Services.appinfo.multiprocessBlockPolicy != 0);
|
||||
let testGroup = (getUserSample() < TEST_THRESHOLD[updateChannel]);
|
||||
let hasNonExemptAddon = Preferences.get(PREF_E10S_HAS_NONEXEMPT_ADDON, false);
|
||||
let temporaryDisqualification = getTemporaryDisqualification();
|
||||
|
||||
let cohortPrefix = "";
|
||||
if (disqualified) {
|
||||
cohortPrefix = "disqualified-";
|
||||
} else if (hasNonExemptAddon) {
|
||||
cohortPrefix = `addons-set${addonPolicy}-`;
|
||||
}
|
||||
|
||||
if (userOptedOut) {
|
||||
setCohort("optedOut");
|
||||
} else if (userOptedIn) {
|
||||
setCohort("optedIn");
|
||||
} else if (temporaryDisqualification != "") {
|
||||
// Users who are disqualified by the backend (from multiprocessBlockPolicy)
|
||||
// can be put into either the test or control groups, because e10s will
|
||||
// still be denied by the backend, which is useful so that the E10S_STATUS
|
||||
// telemetry probe can be correctly set.
|
||||
|
||||
// For these volatile disqualification reasons, however, we must not try
|
||||
// to activate e10s because the backend doesn't know about it. E10S_STATUS
|
||||
// here will be accumulated as "2 - Disabled", which is fine too.
|
||||
setCohort(`temp-disqualified-${temporaryDisqualification}`);
|
||||
Preferences.reset(PREF_TOGGLE_E10S);
|
||||
} else if (testGroup) {
|
||||
setCohort(`${cohortPrefix}test`);
|
||||
Preferences.set(PREF_TOGGLE_E10S, true);
|
||||
} else {
|
||||
setCohort(`${cohortPrefix}control`);
|
||||
Preferences.reset(PREF_TOGGLE_E10S);
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
}
|
||||
|
||||
function uninstall() {
|
||||
}
|
||||
|
||||
function getUserSample() {
|
||||
let prefValue = Preferences.get(PREF_COHORT_SAMPLE, undefined);
|
||||
let value = 0.0;
|
||||
|
||||
if (typeof(prefValue) == "string") {
|
||||
value = parseFloat(prefValue, 10);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof(prefValue) == "number") {
|
||||
// convert old integer value
|
||||
value = prefValue / 100;
|
||||
} else {
|
||||
value = Math.random();
|
||||
}
|
||||
|
||||
Preferences.set(PREF_COHORT_SAMPLE, value.toString().substr(0, 8));
|
||||
return value;
|
||||
}
|
||||
|
||||
function setCohort(cohortName) {
|
||||
Preferences.set(PREF_COHORT_NAME, cohortName);
|
||||
try {
|
||||
if (Ci.nsICrashReporter) {
|
||||
Services.appinfo.QueryInterface(Ci.nsICrashReporter).annotateCrashReport("E10SCohort", cohortName);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function optedIn() {
|
||||
return Preferences.get(PREF_E10S_OPTED_IN, false) ||
|
||||
Preferences.get(PREF_E10S_FORCE_ENABLED, false);
|
||||
}
|
||||
|
||||
function optedOut() {
|
||||
// Users can also opt-out by toggling back the pref to false.
|
||||
// If they reset the pref instead they might be re-enabled if
|
||||
// they are still part of the threshold.
|
||||
return Preferences.get(PREF_E10S_FORCE_DISABLED, false) ||
|
||||
(Preferences.isSet(PREF_TOGGLE_E10S) &&
|
||||
Preferences.get(PREF_TOGGLE_E10S) == false);
|
||||
}
|
||||
|
||||
/* If this function returns a non-empty string, it
|
||||
* means that this particular user should be temporarily
|
||||
* disqualified due to some particular reason.
|
||||
* If a user shouldn't be disqualified, then an empty
|
||||
* string must be returned.
|
||||
*/
|
||||
function getTemporaryDisqualification() {
|
||||
return "";
|
||||
}
|
||||
32
browser/extensions/e10srollout/install.rdf.in
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
#filter substitution
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>e10srollout@mozilla.org</em:id>
|
||||
<em:version>1.10</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
||||
<!-- Target Application this theme can install into,
|
||||
with minimum and maximum supported versions. -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
|
||||
<em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Multi-process staged rollout</em:name>
|
||||
<em:description>Staged rollout of Firefox multi-process feature.</em:description>
|
||||
</Description>
|
||||
</RDF>
|
||||
16
browser/extensions/e10srollout/moz.build
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
||||
|
||||
FINAL_TARGET_FILES.features['e10srollout@mozilla.org'] += [
|
||||
'bootstrap.js'
|
||||
]
|
||||
|
||||
FINAL_TARGET_PP_FILES.features['e10srollout@mozilla.org'] += [
|
||||
'install.rdf.in'
|
||||
]
|
||||
297
browser/extensions/flyweb/bootstrap.js
vendored
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
||||
"resource:///modules/CustomizableUI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Console",
|
||||
"resource://gre/modules/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Integration",
|
||||
"resource://gre/modules/Integration.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PermissionUI",
|
||||
"resource:///modules/PermissionUI.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gFlyWebBundle", function() {
|
||||
const tns = {
|
||||
"flyweb-button.label": "FlyWeb",
|
||||
"flyweb-button.tooltiptext": "Discover nearby FlyWeb services",
|
||||
"flyweb-items-empty": "There are no FlyWeb services currently nearby"
|
||||
};
|
||||
return {
|
||||
GetStringFromName(name) {
|
||||
return tns[name];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const FLYWEB_ENABLED_PREF = "dom.flyweb.enabled";
|
||||
|
||||
function install(aData, aReason) {}
|
||||
|
||||
function uninstall(aData, aReason) {}
|
||||
|
||||
function startup(aData, aReason) {
|
||||
// Observe pref changes and enable/disable as necessary.
|
||||
Services.prefs.addObserver(FLYWEB_ENABLED_PREF, prefObserver, false);
|
||||
|
||||
// Only initialize if pref is enabled.
|
||||
let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
|
||||
if (enabled) {
|
||||
FlyWebView.init();
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown(aData, aReason) {
|
||||
Services.prefs.removeObserver(FLYWEB_ENABLED_PREF, prefObserver);
|
||||
|
||||
let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
|
||||
if (enabled) {
|
||||
FlyWebView.uninit();
|
||||
}
|
||||
}
|
||||
|
||||
// use enabled pref as a way for tests (e.g. test_contextmenu.html) to disable
|
||||
// the addon when running.
|
||||
function prefObserver(aSubject, aTopic, aData) {
|
||||
let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
|
||||
if (enabled) {
|
||||
FlyWebView.init();
|
||||
} else {
|
||||
FlyWebView.uninit();
|
||||
}
|
||||
}
|
||||
|
||||
let gDiscoveryManagerInstance;
|
||||
|
||||
class DiscoveryManager {
|
||||
constructor(aWindow) {
|
||||
this._discoveryManager = new aWindow.FlyWebDiscoveryManager();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._id) {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
this._discoveryManager = null;
|
||||
}
|
||||
|
||||
start(callback) {
|
||||
if (!this._id) {
|
||||
this._id = this._discoveryManager.startDiscovery(this);
|
||||
}
|
||||
|
||||
this._callback = callback;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this._discoveryManager.stopDiscovery(this._id);
|
||||
|
||||
this._id = null;
|
||||
}
|
||||
|
||||
pairWith(serviceId, callback) {
|
||||
this._discoveryManager.pairWithService(serviceId, {
|
||||
pairingSucceeded(service) {
|
||||
callback(service);
|
||||
},
|
||||
|
||||
pairingFailed(error) {
|
||||
console.error("FlyWeb failed to pair with service " + serviceId, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDiscoveredServicesChanged(services) {
|
||||
if (!this._id || !this._callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._callback(services);
|
||||
}
|
||||
}
|
||||
|
||||
const FlyWebPermissionPromptIntegration = (base) => ({
|
||||
__proto__: base,
|
||||
createPermissionPrompt(type, request) {
|
||||
if (type != "flyweb-publish-server") {
|
||||
return super.createPermissionPrompt(...arguments);
|
||||
}
|
||||
|
||||
return {
|
||||
__proto__: PermissionUI.PermissionPromptForRequestPrototype,
|
||||
get request() {
|
||||
return request;
|
||||
},
|
||||
get permissionKey() {
|
||||
return "flyweb-publish-server";
|
||||
},
|
||||
get popupOptions() {
|
||||
return {
|
||||
learnMoreURL: "https://flyweb.github.io",
|
||||
popupIconURL: "chrome://flyweb/skin/icon-64.png",
|
||||
};
|
||||
},
|
||||
get notificationID() {
|
||||
return "flyweb-publish-server";
|
||||
},
|
||||
get anchorID() {
|
||||
const kAnchorID = "flyweb-publish-server-notification-icon";
|
||||
let chromeDoc = this.browser.ownerDocument;
|
||||
let anchor = chromeDoc.getElementById(kAnchorID);
|
||||
if (!anchor) {
|
||||
let notificationPopupBox =
|
||||
chromeDoc.getElementById("notification-popup-box");
|
||||
let notificationIcon = chromeDoc.createElement("image");
|
||||
notificationIcon.id = kAnchorID;
|
||||
notificationIcon.setAttribute("src",
|
||||
"chrome://flyweb/skin/icon-64.png");
|
||||
notificationIcon.classList.add("notification-anchor-icon");
|
||||
notificationIcon.setAttribute("role", "button");
|
||||
notificationIcon.setAttribute("aria-label",
|
||||
"View the publish-server request");
|
||||
notificationIcon.style.filter =
|
||||
"url('chrome://browser/skin/filters.svg#fill')";
|
||||
notificationIcon.style.fill = "currentcolor";
|
||||
notificationIcon.style.opacity = "0.4";
|
||||
notificationPopupBox.appendChild(notificationIcon);
|
||||
}
|
||||
|
||||
return kAnchorID;
|
||||
},
|
||||
get message() {
|
||||
return "Would you like to let this site start a server accessible " +
|
||||
"to nearby devices and people?";
|
||||
},
|
||||
get promptActions() {
|
||||
return [{
|
||||
label: "Allow Server",
|
||||
accessKey: "A",
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
|
||||
}, {
|
||||
label: "Block Server",
|
||||
accessKey: "B",
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
|
||||
}];
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
let FlyWebView = {
|
||||
init() {
|
||||
// Create widget and add it to the menu panel.
|
||||
CustomizableUI.createWidget({
|
||||
id: "flyweb-button",
|
||||
type: "view",
|
||||
viewId: "flyweb-panel",
|
||||
label: gFlyWebBundle.GetStringFromName("flyweb-button.label"),
|
||||
tooltiptext: gFlyWebBundle.GetStringFromName("flyweb-button.tooltiptext"),
|
||||
|
||||
onBeforeCreated(aDocument) {
|
||||
let panel = aDocument.createElement("panelview");
|
||||
panel.id = "flyweb-panel";
|
||||
panel.setAttribute("class", "PanelUI-subView");
|
||||
panel.setAttribute("flex", "1");
|
||||
|
||||
let label = aDocument.createElement("label");
|
||||
label.setAttribute("class", "panel-subview-header");
|
||||
label.setAttribute("value", gFlyWebBundle.GetStringFromName("flyweb-button.label"));
|
||||
|
||||
let empty = aDocument.createElement("description");
|
||||
empty.id = "flyweb-items-empty";
|
||||
empty.setAttribute("mousethrough", "always");
|
||||
empty.textContent = gFlyWebBundle.GetStringFromName("flyweb-items-empty");
|
||||
|
||||
let items = aDocument.createElement("vbox");
|
||||
items.id = "flyweb-items";
|
||||
items.setAttribute("class", "panel-subview-body");
|
||||
|
||||
panel.appendChild(label);
|
||||
panel.appendChild(empty);
|
||||
panel.appendChild(items);
|
||||
|
||||
panel.addEventListener("command", this);
|
||||
|
||||
aDocument.getElementById("PanelUI-multiView").appendChild(panel);
|
||||
|
||||
this._sheetURI = Services.io.newURI("chrome://flyweb/skin/flyweb.css", null, null);
|
||||
aDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).loadSheet(this._sheetURI, 1);
|
||||
},
|
||||
|
||||
onDestroyed(aDocument) {
|
||||
aDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).removeSheet(this._sheetURI, 1);
|
||||
},
|
||||
|
||||
onViewShowing(aEvent) {
|
||||
let doc = aEvent.target.ownerDocument;
|
||||
|
||||
let items = doc.getElementById("flyweb-items");
|
||||
let empty = doc.getElementById("flyweb-items-empty");
|
||||
|
||||
if (!gDiscoveryManagerInstance) {
|
||||
gDiscoveryManagerInstance = new DiscoveryManager(doc.defaultView);
|
||||
}
|
||||
|
||||
gDiscoveryManagerInstance.start((services) => {
|
||||
while (items.firstChild) {
|
||||
items.firstChild.remove();
|
||||
}
|
||||
|
||||
let fragment = doc.createDocumentFragment();
|
||||
|
||||
for (let service of services) {
|
||||
let button = doc.createElement("toolbarbutton");
|
||||
button.setAttribute("class", "subviewbutton cui-withicon");
|
||||
button.setAttribute("label", service.displayName);
|
||||
button.setAttribute("data-service-id", service.serviceId);
|
||||
fragment.appendChild(button);
|
||||
}
|
||||
|
||||
items.appendChild(fragment);
|
||||
|
||||
empty.hidden = services.length > 0;
|
||||
});
|
||||
},
|
||||
|
||||
onViewHiding(aEvent) {
|
||||
gDiscoveryManagerInstance.stop();
|
||||
},
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (aEvent.type === "command") {
|
||||
let serviceId = aEvent.target.getAttribute("data-service-id");
|
||||
gDiscoveryManagerInstance.pairWith(serviceId, (service) => {
|
||||
aEvent.view.openUILinkIn(service.uiUrl, "tab");
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Integration.contentPermission
|
||||
.register(FlyWebPermissionPromptIntegration);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
CustomizableUI.destroyWidget("flyweb-button");
|
||||
|
||||
if (gDiscoveryManagerInstance) {
|
||||
gDiscoveryManagerInstance.destroy();
|
||||
gDiscoveryManagerInstance = null;
|
||||
}
|
||||
|
||||
Integration.contentPermission
|
||||
.unregister(FlyWebPermissionPromptIntegration);
|
||||
}
|
||||
};
|
||||
32
browser/extensions/flyweb/install.rdf.in
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
#filter substitution
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>flyweb@mozilla.org</em:id>
|
||||
<em:version>1.0.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
||||
<!-- Target Application this theme can install into,
|
||||
with minimum and maximum supported versions. -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
|
||||
<em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>FlyWeb</em:name>
|
||||
<em:description>Discover nearby services in the browser</em:description>
|
||||
</Description>
|
||||
</RDF>
|
||||
10
browser/extensions/flyweb/jar.mn
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
[features/flyweb@mozilla.org] chrome.jar:
|
||||
% skin flyweb classic/1.0 %skin/linux/
|
||||
% skin flyweb classic/1.0 %skin/osx/ os=Darwin
|
||||
% skin flyweb classic/1.0 %skin/windows/ os=WINNT
|
||||
% skin flyweb-shared classic/1.0 %skin/shared/
|
||||
skin/ (skin/*)
|
||||
18
browser/extensions/flyweb/moz.build
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
||||
|
||||
FINAL_TARGET_FILES.features['flyweb@mozilla.org'] += [
|
||||
'bootstrap.js'
|
||||
]
|
||||
|
||||
FINAL_TARGET_PP_FILES.features['flyweb@mozilla.org'] += [
|
||||
'install.rdf.in'
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
40
browser/extensions/flyweb/skin/flyweb-icon.svg
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
|
||||
<circle fill="#797C80" cx="32" cy="52" r="6"/>
|
||||
<g>
|
||||
<path fill="#797C80" d="M6.894,15.255c-2.254,1.547-4.386,3.304-6.361,5.279L0.18,20.887l3.536,3.536l0.354-0.354
|
||||
c1.621-1.62,3.363-3.072,5.196-4.369C8.126,18.464,7.296,16.943,6.894,15.255z"/>
|
||||
<path fill="#797C80" d="M63.465,20.532C55.061,12.128,43.887,7.5,32,7.5c-2.265,0-4.504,0.17-6.703,0.501
|
||||
c0.822,1.44,1.3,3.1,1.312,4.87C28.382,12.631,30.181,12.5,32,12.5c10.55,0,20.468,4.108,27.928,11.567l0.354,0.354l3.537-3.535
|
||||
L63.465,20.532z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#797C80" d="M16.613,10.94c1.103,0,2,0.897,2,2s-0.897,2-2,2s-2-0.897-2-2S15.51,10.94,16.613,10.94 M16.613,6.94
|
||||
c-3.313,0-6,2.687-6,6s2.687,6,6,6s6-2.687,6-6S19.926,6.94,16.613,6.94L16.613,6.94z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#797C80" d="M46.492,37.502c-1.853-1.852-4.002-3.292-6.334-4.305c0.031,0.324,0.05,0.652,0.05,0.984
|
||||
c0,1.477-0.33,2.874-0.906,4.137c1.329,0.712,2.561,1.623,3.657,2.719l0.354,0.354l3.533-3.535L46.492,37.502z"/>
|
||||
<path fill="#797C80" d="M20.262,35.207c-0.972,0.683-1.9,1.439-2.758,2.297l-0.354,0.354l3.536,3.537l0.354-0.354
|
||||
c0.35-0.35,0.715-0.679,1.091-0.99C21.118,38.66,20.446,37.007,20.262,35.207z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#797C80" d="M30.209,32.182c1.102,0,1.999,0.897,1.999,2s-0.896,2-1.999,2c-1.103,0-2-0.897-2-2
|
||||
S29.106,32.182,30.209,32.182 M30.209,28.182c-3.313,0-6,2.686-6,6c0,3.312,2.687,6,6,6c3.313,0,5.999-2.688,5.999-6
|
||||
C36.208,30.867,33.522,28.182,30.209,28.182L30.209,28.182z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#797C80" d="M32.207,23.716c0-1.497,0.34-2.912,0.932-4.188C32.76,19.515,32.381,19.5,32,19.5
|
||||
c-8.681,0-16.843,3.381-22.981,9.52l-0.354,0.354l3.535,3.535l0.354-0.354C17.748,27.36,24.654,24.5,32,24.5
|
||||
c0.083,0,0.165,0.005,0.247,0.006C32.227,24.245,32.207,23.982,32.207,23.716z"/>
|
||||
<path fill="#797C80" d="M54.98,29.018c-0.987-0.987-2.033-1.896-3.119-2.738c-0.447,1.68-1.313,3.188-2.491,4.399
|
||||
c0.717,0.586,1.409,1.21,2.073,1.874l0.354,0.354l3.537-3.535L54.98,29.018z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#797C80" d="M42.207,21.716c1.103,0,2,0.897,2,2s-0.897,2-2,2s-2-0.897-2-2S41.104,21.716,42.207,21.716 M42.207,17.716
|
||||
c-3.313,0-6,2.687-6,6s2.687,6,6,6s6-2.687,6-6S45.521,17.716,42.207,17.716L42.207,17.716z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
5
browser/extensions/flyweb/skin/linux/flyweb.css
Normal file
|
|
@ -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/. */
|
||||
|
||||
@import url("chrome://flyweb-shared/skin/flyweb.css");
|
||||
BIN
browser/extensions/flyweb/skin/linux/icon-16.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
browser/extensions/flyweb/skin/linux/icon-32-anchored.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
browser/extensions/flyweb/skin/linux/icon-32.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
browser/extensions/flyweb/skin/linux/icon-64-anchored.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
browser/extensions/flyweb/skin/linux/icon-64.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
5
browser/extensions/flyweb/skin/osx/flyweb.css
Normal file
|
|
@ -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/. */
|
||||
|
||||
@import url("chrome://flyweb-shared/skin/flyweb.css");
|
||||
BIN
browser/extensions/flyweb/skin/osx/icon-16.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
browser/extensions/flyweb/skin/osx/icon-32-anchored.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
browser/extensions/flyweb/skin/osx/icon-32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
browser/extensions/flyweb/skin/osx/icon-64-anchored.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
browser/extensions/flyweb/skin/osx/icon-64.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
54
browser/extensions/flyweb/skin/shared/flyweb.css
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#flyweb-panel {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
#flyweb-items-empty {
|
||||
box-sizing: border-box;
|
||||
color: GrayText;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#flyweb-button {
|
||||
list-style-image: url("chrome://flyweb/skin/icon-16.png");
|
||||
}
|
||||
|
||||
#flyweb-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #flyweb-button {
|
||||
list-style-image: url("chrome://flyweb/skin/icon-32.png");
|
||||
}
|
||||
|
||||
#flyweb-button[cui-areatype="menu-panel"][panel-multiview-anchor="true"] {
|
||||
list-style-image: url("chrome://flyweb/skin/icon-32-anchored.png");
|
||||
}
|
||||
|
||||
#flyweb-items > toolbarbutton {
|
||||
list-style-image: url("chrome://flyweb/skin/icon-16.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
#flyweb-button {
|
||||
list-style-image: url("chrome://flyweb/skin/icon-32.png");
|
||||
}
|
||||
|
||||
#flyweb-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #flyweb-button {
|
||||
list-style-image: url("chrome://flyweb/skin/icon-64.png");
|
||||
}
|
||||
|
||||
#flyweb-button[cui-areatype="menu-panel"][panel-multiview-anchor="true"] {
|
||||
list-style-image: url("chrome://flyweb/skin/icon-64-anchored.png");
|
||||
}
|
||||
|
||||
#flyweb-items > toolbarbutton {
|
||||
list-style-image: url("chrome://flyweb/skin/icon-32.png");
|
||||
}
|
||||
|
||||
#flyweb-items > toolbarbutton > .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
5
browser/extensions/flyweb/skin/windows/flyweb.css
Normal file
|
|
@ -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/. */
|
||||
|
||||
@import url("chrome://flyweb-shared/skin/flyweb.css");
|
||||
BIN
browser/extensions/flyweb/skin/windows/icon-16.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
browser/extensions/flyweb/skin/windows/icon-32-anchored.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
browser/extensions/flyweb/skin/windows/icon-32.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
browser/extensions/flyweb/skin/windows/icon-64-anchored.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
browser/extensions/flyweb/skin/windows/icon-64.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
474
browser/extensions/formautofill/.eslintrc.js
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = { // eslint-disable-line no-undef
|
||||
"extends": "../../.eslintrc.js",
|
||||
|
||||
"globals": {
|
||||
"Components": true,
|
||||
"dump": true,
|
||||
"TextDecoder": false,
|
||||
"TextEncoder": false,
|
||||
},
|
||||
|
||||
"rules": {
|
||||
// Rules from the mozilla plugin
|
||||
"mozilla/balanced-listeners": "error",
|
||||
"mozilla/no-aArgs": "warn",
|
||||
"mozilla/no-cpows-in-tests": "warn",
|
||||
"mozilla/var-only-at-top-level": "warn",
|
||||
|
||||
"valid-jsdoc": ["error", {
|
||||
"prefer": {
|
||||
"return": "returns",
|
||||
},
|
||||
"preferType": {
|
||||
"Boolean": "boolean",
|
||||
"Number": "number",
|
||||
"String": "string",
|
||||
"bool": "boolean",
|
||||
},
|
||||
"requireParamDescription": false,
|
||||
"requireReturn": false,
|
||||
"requireReturnDescription": false,
|
||||
}],
|
||||
|
||||
// Braces only needed for multi-line arrow function blocks
|
||||
// "arrow-body-style": ["error", "as-needed"],
|
||||
|
||||
// Require spacing around =>
|
||||
"arrow-spacing": "error",
|
||||
|
||||
// Always require spacing around a single line block
|
||||
"block-spacing": "warn",
|
||||
|
||||
// Forbid spaces inside the square brackets of array literals.
|
||||
"array-bracket-spacing": ["error", "never"],
|
||||
|
||||
// Forbid spaces inside the curly brackets of object literals.
|
||||
"object-curly-spacing": ["error", "never"],
|
||||
|
||||
// No space padding in parentheses
|
||||
"space-in-parens": ["error", "never"],
|
||||
|
||||
// Enforce one true brace style (opening brace on the same line) and avoid
|
||||
// start and end braces on the same line.
|
||||
"brace-style": ["error", "1tbs", {"allowSingleLine": true}],
|
||||
|
||||
// No space before always a space after a comma
|
||||
"comma-spacing": ["error", {"before": false, "after": true}],
|
||||
|
||||
// Commas at the end of the line not the start
|
||||
"comma-style": "error",
|
||||
|
||||
// Don't require spaces around computed properties
|
||||
"computed-property-spacing": ["warn", "never"],
|
||||
|
||||
// Functions are not required to consistently return something or nothing
|
||||
"consistent-return": "off",
|
||||
|
||||
// Require braces around blocks that start a new line
|
||||
"curly": ["error", "all"],
|
||||
|
||||
// Always require a trailing EOL
|
||||
"eol-last": "error",
|
||||
|
||||
// Require function* name()
|
||||
"generator-star-spacing": ["error", {"before": false, "after": true}],
|
||||
|
||||
// Two space indent
|
||||
"indent": ["error", 2, {"SwitchCase": 1}],
|
||||
|
||||
// Space after colon not before in property declarations
|
||||
"key-spacing": ["error", {"beforeColon": false, "afterColon": true, "mode": "minimum"}],
|
||||
|
||||
// Require spaces before and after finally, catch, etc.
|
||||
"keyword-spacing": "error",
|
||||
|
||||
// Unix linebreaks
|
||||
"linebreak-style": ["error", "unix"],
|
||||
|
||||
// Always require parenthesis for new calls
|
||||
"new-parens": "error",
|
||||
|
||||
// Use [] instead of Array()
|
||||
"no-array-constructor": "error",
|
||||
|
||||
// No duplicate arguments in function declarations
|
||||
"no-dupe-args": "error",
|
||||
|
||||
// No duplicate keys in object declarations
|
||||
"no-dupe-keys": "error",
|
||||
|
||||
// No duplicate cases in switch statements
|
||||
"no-duplicate-case": "error",
|
||||
|
||||
// If an if block ends with a return no need for an else block
|
||||
// "no-else-return": "error",
|
||||
|
||||
// Disallow empty statements. This will report an error for:
|
||||
// try { something(); } catch (e) {}
|
||||
// but will not report it for:
|
||||
// try { something(); } catch (e) { /* Silencing the error because ...*/ }
|
||||
// which is a valid use case.
|
||||
"no-empty": "error",
|
||||
|
||||
// No empty character classes in regex
|
||||
"no-empty-character-class": "error",
|
||||
|
||||
// Disallow empty destructuring
|
||||
"no-empty-pattern": "error",
|
||||
|
||||
// No assiging to exception variable
|
||||
"no-ex-assign": "error",
|
||||
|
||||
// No using !! where casting to boolean is already happening
|
||||
"no-extra-boolean-cast": "warn",
|
||||
|
||||
// No double semicolon
|
||||
"no-extra-semi": "error",
|
||||
|
||||
// No overwriting defined functions
|
||||
"no-func-assign": "error",
|
||||
|
||||
// No invalid regular expresions
|
||||
"no-invalid-regexp": "error",
|
||||
|
||||
// No odd whitespace characters
|
||||
"no-irregular-whitespace": "error",
|
||||
|
||||
// No single if block inside an else block
|
||||
"no-lonely-if": "warn",
|
||||
|
||||
// No mixing spaces and tabs in indent
|
||||
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||
|
||||
// Disallow use of multiple spaces (sometimes used to align const values,
|
||||
// array or object items, etc.). It's hard to maintain and doesn't add that
|
||||
// much benefit.
|
||||
"no-multi-spaces": "warn",
|
||||
|
||||
// No reassigning native JS objects
|
||||
"no-native-reassign": "error",
|
||||
|
||||
// No (!foo in bar)
|
||||
"no-negated-in-lhs": "error",
|
||||
|
||||
// Nested ternary statements are confusing
|
||||
"no-nested-ternary": "error",
|
||||
|
||||
// Use {} instead of new Object()
|
||||
"no-new-object": "error",
|
||||
|
||||
// No Math() or JSON()
|
||||
"no-obj-calls": "error",
|
||||
|
||||
// No octal literals
|
||||
"no-octal": "error",
|
||||
|
||||
// No redeclaring variables
|
||||
"no-redeclare": "error",
|
||||
|
||||
// No unnecessary comparisons
|
||||
"no-self-compare": "error",
|
||||
|
||||
// No spaces between function name and parentheses
|
||||
"no-spaced-func": "warn",
|
||||
|
||||
// No trailing whitespace
|
||||
"no-trailing-spaces": "error",
|
||||
|
||||
// Error on newline where a semicolon is needed
|
||||
"no-unexpected-multiline": "error",
|
||||
|
||||
// No unreachable statements
|
||||
"no-unreachable": "error",
|
||||
|
||||
// No expressions where a statement is expected
|
||||
"no-unused-expressions": "error",
|
||||
|
||||
// No declaring variables that are never used
|
||||
"no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$"}],
|
||||
|
||||
// No using variables before defined
|
||||
"no-use-before-define": "error",
|
||||
|
||||
// No using with
|
||||
"no-with": "error",
|
||||
|
||||
// Always require semicolon at end of statement
|
||||
"semi": ["error", "always"],
|
||||
|
||||
// Require space before blocks
|
||||
"space-before-blocks": "error",
|
||||
|
||||
// Never use spaces before function parentheses
|
||||
"space-before-function-paren": ["error", {"anonymous": "never", "named": "never"}],
|
||||
|
||||
// Require spaces around operators, except for a|"off".
|
||||
"space-infix-ops": ["error", {"int32Hint": true}],
|
||||
|
||||
// ++ and -- should not need spacing
|
||||
"space-unary-ops": ["warn", {"nonwords": false}],
|
||||
|
||||
// No comparisons to NaN
|
||||
"use-isnan": "error",
|
||||
|
||||
// Only check typeof against valid results
|
||||
"valid-typeof": "error",
|
||||
|
||||
// Disallow using variables outside the blocks they are defined (especially
|
||||
// since only let and const are used, see "no-var").
|
||||
"block-scoped-var": "error",
|
||||
|
||||
// Allow trailing commas for easy list extension. Having them does not
|
||||
// impair readability, but also not required either.
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
|
||||
// Warn about cyclomatic complexity in functions.
|
||||
"complexity": "warn",
|
||||
|
||||
// Don't warn for inconsistent naming when capturing this (not so important
|
||||
// with auto-binding fat arrow functions).
|
||||
// "consistent-this": ["error", "self"],
|
||||
|
||||
// Don't require a default case in switch statements. Avoid being forced to
|
||||
// add a bogus default when you know all possible cases are handled.
|
||||
"default-case": "off",
|
||||
|
||||
// Enforce dots on the next line with property name.
|
||||
"dot-location": ["error", "property"],
|
||||
|
||||
// Encourage the use of dot notation whenever possible.
|
||||
"dot-notation": "error",
|
||||
|
||||
// Allow using == instead of ===, in the interest of landing something since
|
||||
// the devtools codebase is split on convention here.
|
||||
"eqeqeq": "off",
|
||||
|
||||
// Don't require function expressions to have a name.
|
||||
// This makes the code more verbose and hard to read. Our engine already
|
||||
// does a fantastic job assigning a name to the function, which includes
|
||||
// the enclosing function name, and worst case you have a line number that
|
||||
// you can just look up.
|
||||
"func-names": "off",
|
||||
|
||||
// Allow use of function declarations and expressions.
|
||||
"func-style": "off",
|
||||
|
||||
// Don't enforce the maximum depth that blocks can be nested. The complexity
|
||||
// rule is a better rule to check this.
|
||||
"max-depth": "off",
|
||||
|
||||
// Maximum length of a line.
|
||||
// Disabled because we exceed this in too many places.
|
||||
"max-len": ["off", 80],
|
||||
|
||||
// Maximum depth callbacks can be nested.
|
||||
"max-nested-callbacks": ["error", 4],
|
||||
|
||||
// Don't limit the number of parameters that can be used in a function.
|
||||
"max-params": "off",
|
||||
|
||||
// Don't limit the maximum number of statement allowed in a function. We
|
||||
// already have the complexity rule that's a better measurement.
|
||||
"max-statements": "off",
|
||||
|
||||
// Don't require a capital letter for constructors, only check if all new
|
||||
// operators are followed by a capital letter. Don't warn when capitalized
|
||||
// functions are used without the new operator.
|
||||
"new-cap": ["off", {"capIsNew": false}],
|
||||
|
||||
// Allow use of bitwise operators.
|
||||
"no-bitwise": "off",
|
||||
|
||||
// Disallow use of arguments.caller or arguments.callee.
|
||||
"no-caller": "error",
|
||||
|
||||
// Disallow the catch clause parameter name being the same as a variable in
|
||||
// the outer scope, to avoid confusion.
|
||||
"no-catch-shadow": "off",
|
||||
|
||||
// Disallow assignment in conditional expressions.
|
||||
"no-cond-assign": "error",
|
||||
|
||||
// Disallow using the console API.
|
||||
"no-console": "error",
|
||||
|
||||
// Allow using constant expressions in conditions like while (true)
|
||||
"no-constant-condition": "off",
|
||||
|
||||
// Allow use of the continue statement.
|
||||
"no-continue": "off",
|
||||
|
||||
// Disallow control characters in regular expressions.
|
||||
"no-control-regex": "error",
|
||||
|
||||
// Disallow use of debugger.
|
||||
"no-debugger": "error",
|
||||
|
||||
// Disallow deletion of variables (deleting properties is fine).
|
||||
"no-delete-var": "error",
|
||||
|
||||
// Allow division operators explicitly at beginning of regular expression.
|
||||
"no-div-regex": "off",
|
||||
|
||||
// Disallow use of eval(). We have other APIs to evaluate code in content.
|
||||
"no-eval": "error",
|
||||
|
||||
// Disallow adding to native types
|
||||
"no-extend-native": "error",
|
||||
|
||||
// Disallow unnecessary function binding.
|
||||
"no-extra-bind": "error",
|
||||
|
||||
// Allow unnecessary parentheses, as they may make the code more readable.
|
||||
"no-extra-parens": "off",
|
||||
|
||||
// Disallow fallthrough of case statements, except if there is a comment.
|
||||
"no-fallthrough": "error",
|
||||
|
||||
// Allow the use of leading or trailing decimal points in numeric literals.
|
||||
"no-floating-decimal": "off",
|
||||
|
||||
// Allow comments inline after code.
|
||||
"no-inline-comments": "off",
|
||||
|
||||
// Disallow use of labels for anything other then loops and switches.
|
||||
"no-labels": ["error", {"allowLoop": true}],
|
||||
|
||||
// Disallow use of multiline strings (use template strings instead).
|
||||
"no-multi-str": "warn",
|
||||
|
||||
// Disallow multiple empty lines.
|
||||
"no-multiple-empty-lines": ["warn", {"max": 2}],
|
||||
|
||||
// Allow reassignment of function parameters.
|
||||
"no-param-reassign": "off",
|
||||
|
||||
// Allow string concatenation with __dirname and __filename (not a node env).
|
||||
"no-path-concat": "off",
|
||||
|
||||
// Allow use of unary operators, ++ and --.
|
||||
"no-plusplus": "off",
|
||||
|
||||
// Allow using process.env (not a node environment).
|
||||
"no-process-env": "off",
|
||||
|
||||
// Allow using process.exit (not a node environment).
|
||||
"no-process-exit": "off",
|
||||
|
||||
// Disallow usage of __proto__ property.
|
||||
"no-proto": "error",
|
||||
|
||||
// Disallow multiple spaces in a regular expression literal.
|
||||
"no-regex-spaces": "error",
|
||||
|
||||
// Allow reserved words being used as object literal keys.
|
||||
"no-reserved-keys": "off",
|
||||
|
||||
// Don't restrict usage of specified node modules (not a node environment).
|
||||
"no-restricted-modules": "off",
|
||||
|
||||
// Disallow use of assignment in return statement. It is preferable for a
|
||||
// single line of code to have only one easily predictable effect.
|
||||
"no-return-assign": "error",
|
||||
|
||||
// Don't warn about declaration of variables already declared in the outer scope.
|
||||
"no-shadow": "off",
|
||||
|
||||
// Disallow shadowing of names such as arguments.
|
||||
"no-shadow-restricted-names": "error",
|
||||
|
||||
// Allow use of synchronous methods (not a node environment).
|
||||
"no-sync": "off",
|
||||
|
||||
// Allow the use of ternary operators.
|
||||
"no-ternary": "off",
|
||||
|
||||
// Disallow throwing literals (eg. throw "error" instead of
|
||||
// throw new Error("error")).
|
||||
"no-throw-literal": "error",
|
||||
|
||||
// Disallow use of undeclared variables unless mentioned in a /* global */
|
||||
// block. Note that globals from head.js are automatically imported in tests
|
||||
// by the import-headjs-globals rule form the mozilla eslint plugin.
|
||||
"no-undef": "error",
|
||||
|
||||
// Allow dangling underscores in identifiers (for privates).
|
||||
"no-underscore-dangle": "off",
|
||||
|
||||
// Allow use of undefined variable.
|
||||
"no-undefined": "off",
|
||||
|
||||
// Disallow the use of Boolean literals in conditional expressions.
|
||||
"no-unneeded-ternary": "error",
|
||||
|
||||
// We use var-only-at-top-level instead of no-var as we allow top level
|
||||
// vars.
|
||||
"no-var": "off",
|
||||
|
||||
// Allow using TODO/FIXME comments.
|
||||
"no-warning-comments": "off",
|
||||
|
||||
// Don't require method and property shorthand syntax for object literals.
|
||||
// We use this in the code a lot, but not consistently, and this seems more
|
||||
// like something to check at code review time.
|
||||
"object-shorthand": "off",
|
||||
|
||||
// Allow more than one variable declaration per function.
|
||||
"one-var": "off",
|
||||
|
||||
// Disallow padding within blocks.
|
||||
"padded-blocks": ["warn", "never"],
|
||||
|
||||
// Don't require quotes around object literal property names.
|
||||
"quote-props": "off",
|
||||
|
||||
// Double quotes should be used.
|
||||
"quotes": ["warn", "double", {"avoidEscape": true, "allowTemplateLiterals": true}],
|
||||
|
||||
// Require use of the second argument for parseInt().
|
||||
"radix": "error",
|
||||
|
||||
// Enforce spacing after semicolons.
|
||||
"semi-spacing": ["error", {"before": false, "after": true}],
|
||||
|
||||
// Don't require to sort variables within the same declaration block.
|
||||
// Anyway, one-var is disabled.
|
||||
"sort-vars": "off",
|
||||
|
||||
// Require a space immediately following the // in a line comment.
|
||||
"spaced-comment": ["error", "always"],
|
||||
|
||||
// Require "use strict" to be defined globally in the script.
|
||||
"strict": ["error", "global"],
|
||||
|
||||
// Allow vars to be declared anywhere in the scope.
|
||||
"vars-on-top": "off",
|
||||
|
||||
// Don't require immediate function invocation to be wrapped in parentheses.
|
||||
"wrap-iife": "off",
|
||||
|
||||
// Don't require regex literals to be wrapped in parentheses (which
|
||||
// supposedly prevent them from being mistaken for division operators).
|
||||
"wrap-regex": "off",
|
||||
|
||||
// Disallow Yoda conditions (where literal value comes first).
|
||||
"yoda": "error",
|
||||
|
||||
// disallow use of eval()-like methods
|
||||
"no-implied-eval": "error",
|
||||
|
||||
// Disallow function or variable declarations in nested blocks
|
||||
"no-inner-declarations": "error",
|
||||
|
||||
// Disallow usage of __iterator__ property
|
||||
"no-iterator": "error",
|
||||
|
||||
// Disallow labels that share a name with a variable
|
||||
"no-label-var": "error",
|
||||
|
||||
// Disallow creating new instances of String, Number, and Boolean
|
||||
"no-new-wrappers": "error",
|
||||
},
|
||||
};
|
||||
12
browser/extensions/formautofill/bootstrap.js
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* exported startup, shutdown, install, uninstall */
|
||||
|
||||
function startup() {}
|
||||
function shutdown() {}
|
||||
function install() {}
|
||||
function uninstall() {}
|
||||
134
browser/extensions/formautofill/content/FormAutofillContent.jsm
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Implements a service used by DOM content to request Form Autofill.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
/**
|
||||
* Handles profile autofill for a DOM Form element.
|
||||
* @param {HTMLFormElement} form Form that need to be auto filled
|
||||
*/
|
||||
function FormAutofillHandler(form) {
|
||||
this.form = form;
|
||||
this.fieldDetails = [];
|
||||
}
|
||||
|
||||
FormAutofillHandler.prototype = {
|
||||
/**
|
||||
* DOM Form element to which this object is attached.
|
||||
*/
|
||||
form: null,
|
||||
|
||||
/**
|
||||
* Array of collected data about relevant form fields. Each item is an object
|
||||
* storing the identifying details of the field and a reference to the
|
||||
* originally associated element from the form.
|
||||
*
|
||||
* The "section", "addressType", "contactType", and "fieldName" values are
|
||||
* used to identify the exact field when the serializable data is received
|
||||
* from the backend. There cannot be multiple fields which have
|
||||
* the same exact combination of these values.
|
||||
*
|
||||
* A direct reference to the associated element cannot be sent to the user
|
||||
* interface because processing may be done in the parent process.
|
||||
*/
|
||||
fieldDetails: null,
|
||||
|
||||
/**
|
||||
* Returns information from the form about fields that can be autofilled, and
|
||||
* populates the fieldDetails array on this object accordingly.
|
||||
*
|
||||
* @returns {Array<Object>} Serializable data structure that can be sent to the user
|
||||
* interface, or null if the operation failed because the constraints
|
||||
* on the allowed fields were not honored.
|
||||
*/
|
||||
collectFormFields() {
|
||||
let autofillData = [];
|
||||
|
||||
for (let element of this.form.elements) {
|
||||
// Query the interface and exclude elements that cannot be autocompleted.
|
||||
if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exclude elements to which no autocomplete field has been assigned.
|
||||
let info = element.getAutocompleteInfo();
|
||||
if (!info.fieldName || ["on", "off"].includes(info.fieldName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store the association between the field metadata and the element.
|
||||
if (this.fieldDetails.some(f => f.section == info.section &&
|
||||
f.addressType == info.addressType &&
|
||||
f.contactType == info.contactType &&
|
||||
f.fieldName == info.fieldName)) {
|
||||
// A field with the same identifier already exists.
|
||||
return null;
|
||||
}
|
||||
|
||||
let inputFormat = {
|
||||
section: info.section,
|
||||
addressType: info.addressType,
|
||||
contactType: info.contactType,
|
||||
fieldName: info.fieldName,
|
||||
};
|
||||
// Clone the inputFormat for caching the fields and elements together
|
||||
let formatWithElement = Object.assign({}, inputFormat);
|
||||
|
||||
inputFormat.index = autofillData.length;
|
||||
autofillData.push(inputFormat);
|
||||
|
||||
formatWithElement.element = element;
|
||||
this.fieldDetails.push(formatWithElement);
|
||||
}
|
||||
|
||||
return autofillData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Processes form fields that can be autofilled, and populates them with the
|
||||
* data provided by backend.
|
||||
*
|
||||
* @param {Array<Object>} autofillResult
|
||||
* Data returned by the user interface.
|
||||
* [{
|
||||
* section: Value originally provided to the user interface.
|
||||
* addressType: Value originally provided to the user interface.
|
||||
* contactType: Value originally provided to the user interface.
|
||||
* fieldName: Value originally provided to the user interface.
|
||||
* value: String with which the field should be updated.
|
||||
* index: Index to match the input in fieldDetails
|
||||
* }],
|
||||
* }
|
||||
*/
|
||||
autofillFormFields(autofillResult) {
|
||||
for (let field of autofillResult) {
|
||||
// Get the field details, if it was processed by the user interface.
|
||||
let fieldDetail = this.fieldDetails[field.index];
|
||||
|
||||
// Avoid the invalid value set
|
||||
if (!fieldDetail || !field.value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let info = fieldDetail.element.getAutocompleteInfo();
|
||||
if (field.section != info.section ||
|
||||
field.addressType != info.addressType ||
|
||||
field.contactType != info.contactType ||
|
||||
field.fieldName != info.fieldName) {
|
||||
Cu.reportError("Autocomplete tokens mismatched");
|
||||
continue;
|
||||
}
|
||||
|
||||
fieldDetail.element.setUserInput(field.value);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
|
||||
173
browser/extensions/formautofill/content/FormAutofillParent.jsm
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Implements a service used to access storage and communicate with content.
|
||||
*
|
||||
* A "fields" array is used to communicate with FormAutofillContent. Each item
|
||||
* represents a single input field in the content page as well as its
|
||||
* @autocomplete properties. The schema is as below. Please refer to
|
||||
* FormAutofillContent.jsm for more details.
|
||||
*
|
||||
* [
|
||||
* {
|
||||
* section,
|
||||
* addressType,
|
||||
* contactType,
|
||||
* fieldName,
|
||||
* value,
|
||||
* index
|
||||
* },
|
||||
* {
|
||||
* // ...
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
|
||||
/* exported FormAutofillParent */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ProfileStorage",
|
||||
"resource://formautofill/ProfileStorage.jsm");
|
||||
|
||||
const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
|
||||
|
||||
let FormAutofillParent = {
|
||||
_profileStore: null,
|
||||
|
||||
/**
|
||||
* Initializes ProfileStorage and registers the message handler.
|
||||
*/
|
||||
init: function() {
|
||||
let storePath =
|
||||
OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME);
|
||||
|
||||
this._profileStore = new ProfileStorage(storePath);
|
||||
this._profileStore.initialize();
|
||||
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageListenerManager);
|
||||
mm.addMessageListener("FormAutofill:PopulateFieldValues", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the message coming from FormAutofillContent.
|
||||
*
|
||||
* @param {string} message.name The name of the message.
|
||||
* @param {object} message.data The data of the message.
|
||||
* @param {nsIFrameMessageManager} message.target Caller's message manager.
|
||||
*/
|
||||
receiveMessage: function({name, data, target}) {
|
||||
switch (name) {
|
||||
case "FormAutofill:PopulateFieldValues":
|
||||
this._populateFieldValues(data, target);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the instance of ProfileStorage. To avoid syncing issues, anyone
|
||||
* who needs to access the profile should request the instance by this instead
|
||||
* of creating a new one.
|
||||
*
|
||||
* @returns {ProfileStorage}
|
||||
*/
|
||||
getProfileStore: function() {
|
||||
return this._profileStore;
|
||||
},
|
||||
|
||||
/**
|
||||
* Uninitializes FormAutofillParent. This is for testing only.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_uninit: function() {
|
||||
if (this._profileStore) {
|
||||
this._profileStore._saveImmediately();
|
||||
this._profileStore = null;
|
||||
}
|
||||
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageListenerManager);
|
||||
mm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates the field values and notifies content to fill in. Exception will
|
||||
* be thrown if there's no matching profile.
|
||||
*
|
||||
* @private
|
||||
* @param {string} data.guid
|
||||
* Indicates which profile to populate
|
||||
* @param {Fields} data.fields
|
||||
* The "fields" array collected from content.
|
||||
* @param {nsIFrameMessageManager} target
|
||||
* Content's message manager.
|
||||
*/
|
||||
_populateFieldValues({guid, fields}, target) {
|
||||
this._profileStore.notifyUsed(guid);
|
||||
this._fillInFields(this._profileStore.get(guid), fields);
|
||||
target.sendAsyncMessage("FormAutofill:fillForm", {fields});
|
||||
},
|
||||
|
||||
/**
|
||||
* Transforms a word with hyphen into camel case.
|
||||
* (e.g. transforms "address-type" into "addressType".)
|
||||
*
|
||||
* @private
|
||||
* @param {string} str The original string with hyphen.
|
||||
* @returns {string} The camel-cased output string.
|
||||
*/
|
||||
_camelCase(str) {
|
||||
return str.toLowerCase().replace(/-([a-z])/g, s => s[1].toUpperCase());
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the corresponding value from the specified profile according to a valid
|
||||
* @autocomplete field name.
|
||||
*
|
||||
* Note that the field name doesn't need to match the property name defined in
|
||||
* Profile object. This method can transform the raw data to fulfill it. (e.g.
|
||||
* inputting "country-name" as "fieldName" will get a full name transformed
|
||||
* from the country code that is recorded in "country" field.)
|
||||
*
|
||||
* @private
|
||||
* @param {Profile} profile The specified profile.
|
||||
* @param {string} fieldName A valid @autocomplete field name.
|
||||
* @returns {string} The corresponding value. Returns "undefined" if there's
|
||||
* no matching field.
|
||||
*/
|
||||
_getDataByFieldName(profile, fieldName) {
|
||||
let key = this._camelCase(fieldName);
|
||||
|
||||
// TODO: Transform the raw profile data to fulfill "fieldName" here.
|
||||
|
||||
return profile[key];
|
||||
},
|
||||
|
||||
/**
|
||||
* Fills in the "fields" array by the specified profile.
|
||||
*
|
||||
* @private
|
||||
* @param {Profile} profile The specified profile to fill in.
|
||||
* @param {Fields} fields The "fields" array collected from content.
|
||||
*/
|
||||
_fillInFields(profile, fields) {
|
||||
for (let field of fields) {
|
||||
let value = this._getDataByFieldName(profile, field.fieldName);
|
||||
if (value !== undefined) {
|
||||
field.value = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FormAutofillParent"];
|
||||
251
browser/extensions/formautofill/content/ProfileStorage.jsm
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Implements an interface of the storage of Form Autofill.
|
||||
*
|
||||
* The data is stored in JSON format, without indentation, using UTF-8 encoding.
|
||||
* With indentation applied, the file would look like this:
|
||||
*
|
||||
* {
|
||||
* version: 1,
|
||||
* profiles: [
|
||||
* {
|
||||
* guid, // 12 character...
|
||||
*
|
||||
* // profile
|
||||
* organization, // Company
|
||||
* streetAddress, // (Multiline)
|
||||
* addressLevel2, // City/Town
|
||||
* addressLevel1, // Province (Standardized code if possible)
|
||||
* postalCode,
|
||||
* country, // ISO 3166
|
||||
* tel,
|
||||
* email,
|
||||
*
|
||||
* // metadata
|
||||
* timeCreated, // in ms
|
||||
* timeLastUsed, // in ms
|
||||
* timeLastModified, // in ms
|
||||
* timesUsed
|
||||
* },
|
||||
* {
|
||||
* // ...
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
|
||||
"resource://gre/modules/JSONFile.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
|
||||
"@mozilla.org/uuid-generator;1",
|
||||
"nsIUUIDGenerator");
|
||||
|
||||
const SCHEMA_VERSION = 1;
|
||||
|
||||
// Name-related fields will be handled in follow-up bugs due to the complexity.
|
||||
const VALID_FIELDS = [
|
||||
"organization",
|
||||
"streetAddress",
|
||||
"addressLevel2",
|
||||
"addressLevel1",
|
||||
"postalCode",
|
||||
"country",
|
||||
"tel",
|
||||
"email",
|
||||
];
|
||||
|
||||
function ProfileStorage(path) {
|
||||
this._path = path;
|
||||
}
|
||||
|
||||
ProfileStorage.prototype = {
|
||||
/**
|
||||
* Loads the profile data from file to memory.
|
||||
*
|
||||
* @returns {Promise}
|
||||
* @resolves When the operation finished successfully.
|
||||
* @rejects JavaScript exception.
|
||||
*/
|
||||
initialize() {
|
||||
this._store = new JSONFile({
|
||||
path: this._path,
|
||||
dataPostProcessor: this._dataPostProcessor.bind(this),
|
||||
});
|
||||
return this._store.load();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new profile.
|
||||
*
|
||||
* @param {Profile} profile
|
||||
* The new profile for saving.
|
||||
*/
|
||||
add(profile) {
|
||||
this._store.ensureDataReady();
|
||||
|
||||
let profileToSave = this._normalizeProfile(profile);
|
||||
|
||||
profileToSave.guid = gUUIDGenerator.generateUUID().toString()
|
||||
.replace(/[{}-]/g, "").substring(0, 12);
|
||||
|
||||
// Metadata
|
||||
let now = Date.now();
|
||||
profileToSave.timeCreated = now;
|
||||
profileToSave.timeLastModified = now;
|
||||
profileToSave.timeLastUsed = 0;
|
||||
profileToSave.timesUsed = 0;
|
||||
|
||||
this._store.data.profiles.push(profileToSave);
|
||||
|
||||
this._store.saveSoon();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the specified profile.
|
||||
*
|
||||
* @param {string} guid
|
||||
* Indicates which profile to update.
|
||||
* @param {Profile} profile
|
||||
* The new profile used to overwrite the old one.
|
||||
*/
|
||||
update(guid, profile) {
|
||||
this._store.ensureDataReady();
|
||||
|
||||
let profileFound = this._findByGUID(guid);
|
||||
if (!profileFound) {
|
||||
throw new Error("No matching profile.");
|
||||
}
|
||||
|
||||
let profileToUpdate = this._normalizeProfile(profile);
|
||||
for (let field of VALID_FIELDS) {
|
||||
if (profileToUpdate[field] !== undefined) {
|
||||
profileFound[field] = profileToUpdate[field];
|
||||
} else {
|
||||
delete profileFound[field];
|
||||
}
|
||||
}
|
||||
|
||||
profileFound.timeLastModified = Date.now();
|
||||
|
||||
this._store.saveSoon();
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies the stroage of the use of the specified profile, so we can update
|
||||
* the metadata accordingly.
|
||||
*
|
||||
* @param {string} guid
|
||||
* Indicates which profile to be notified.
|
||||
*/
|
||||
notifyUsed(guid) {
|
||||
this._store.ensureDataReady();
|
||||
|
||||
let profileFound = this._findByGUID(guid);
|
||||
if (!profileFound) {
|
||||
throw new Error("No matching profile.");
|
||||
}
|
||||
|
||||
profileFound.timesUsed++;
|
||||
profileFound.timeLastUsed = Date.now();
|
||||
|
||||
this._store.saveSoon();
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the specified profile. No error occurs if the profile isn't found.
|
||||
*
|
||||
* @param {string} guid
|
||||
* Indicates which profile to remove.
|
||||
*/
|
||||
remove(guid) {
|
||||
this._store.ensureDataReady();
|
||||
|
||||
this._store.data.profiles =
|
||||
this._store.data.profiles.filter(profile => profile.guid != guid);
|
||||
this._store.saveSoon();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the profile with the specified GUID.
|
||||
*
|
||||
* @param {string} guid
|
||||
* Indicates which profile to retrieve.
|
||||
* @returns {Profile}
|
||||
* A clone of the profile.
|
||||
*/
|
||||
get(guid) {
|
||||
this._store.ensureDataReady();
|
||||
|
||||
let profileFound = this._findByGUID(guid);
|
||||
if (!profileFound) {
|
||||
throw new Error("No matching profile.");
|
||||
}
|
||||
|
||||
// Profile is cloned to avoid accidental modifications from outside.
|
||||
return this._clone(profileFound);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns all profiles.
|
||||
*
|
||||
* @returns {Array.<Profile>}
|
||||
* An array containing clones of all profiles.
|
||||
*/
|
||||
getAll() {
|
||||
this._store.ensureDataReady();
|
||||
|
||||
// Profiles are cloned to avoid accidental modifications from outside.
|
||||
return this._store.data.profiles.map(this._clone);
|
||||
},
|
||||
|
||||
_clone(profile) {
|
||||
return Object.assign({}, profile);
|
||||
},
|
||||
|
||||
_findByGUID(guid) {
|
||||
return this._store.data.profiles.find(profile => profile.guid == guid);
|
||||
},
|
||||
|
||||
_normalizeProfile(profile) {
|
||||
let result = {};
|
||||
for (let key in profile) {
|
||||
if (!VALID_FIELDS.includes(key)) {
|
||||
throw new Error(`"${key}" is not a valid field.`);
|
||||
}
|
||||
if (typeof profile[key] !== "string" &&
|
||||
typeof profile[key] !== "number") {
|
||||
throw new Error(`"${key}" contains invalid data type.`);
|
||||
}
|
||||
|
||||
result[key] = profile[key];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
_dataPostProcessor(data) {
|
||||
data.version = SCHEMA_VERSION;
|
||||
if (!data.profiles) {
|
||||
data.profiles = [];
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
// For test only.
|
||||
_saveImmediately() {
|
||||
return this._store._save();
|
||||
},
|
||||
};
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ProfileStorage"];
|
||||
32
browser/extensions/formautofill/install.rdf.in
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
#filter substitution
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>formautofill@mozilla.org</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
||||
<!-- Target Application this extension can install into,
|
||||
with minimum and maximum supported versions. -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
|
||||
<em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Form Autofill</em:name>
|
||||
<em:description>Autofill forms with saved profiles</em:description>
|
||||
</Description>
|
||||
</RDF>
|
||||
7
browser/extensions/formautofill/jar.mn
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
[features/formautofill@mozilla.org] chrome.jar:
|
||||
% resource formautofill %content/
|
||||
content/ (content/*)
|
||||
22
browser/extensions/formautofill/moz.build
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
||||
|
||||
FINAL_TARGET_FILES.features['formautofill@mozilla.org'] += [
|
||||
'bootstrap.js'
|
||||
]
|
||||
|
||||
FINAL_TARGET_PP_FILES.features['formautofill@mozilla.org'] += [
|
||||
'install.rdf.in'
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = { // eslint-disable-line no-undef
|
||||
"extends": [
|
||||
"../../../../../testing/mochitest/browser.eslintrc.js",
|
||||
],
|
||||
};
|
||||
3
browser/extensions/formautofill/test/browser/browser.ini
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[DEFAULT]
|
||||
|
||||
[browser_check_installed.js]
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
"use strict";
|
||||
|
||||
add_task(function* test_enabled() {
|
||||
let addon = yield new Promise(
|
||||
resolve => AddonManager.getAddonByID("formautofill@mozilla.org", resolve)
|
||||
);
|
||||
isnot(addon, null, "Check addon exists");
|
||||
is(addon.version, "1.0", "Check version");
|
||||
is(addon.name, "Form Autofill", "Check name");
|
||||
ok(addon.isCompatible, "Check application compatibility");
|
||||
ok(!addon.appDisabled, "Check not app disabled");
|
||||
ok(addon.isActive, "Check addon is active");
|
||||
is(addon.type, "extension", "Check type is 'extension'");
|
||||
});
|
||||
5
browser/extensions/formautofill/test/unit/.eslintrc
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": [
|
||||
"../../../../../testing/xpcshell/xpcshell.eslintrc.js"
|
||||
],
|
||||
}
|
||||
81
browser/extensions/formautofill/test/unit/head.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Provides infrastructure for automated login components tests.
|
||||
*/
|
||||
|
||||
/* exported importAutofillModule, getTempFile */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://testing-common/MockDocument.jsm");
|
||||
|
||||
// Redirect the path of the resouce in addon to the exact file path.
|
||||
let defineLazyModuleGetter = XPCOMUtils.defineLazyModuleGetter;
|
||||
XPCOMUtils.defineLazyModuleGetter = function() {
|
||||
let result = /^resource\:\/\/formautofill\/(.+)$/.exec(arguments[2]);
|
||||
if (result) {
|
||||
arguments[2] = Services.io.newFileURI(do_get_file(result[1])).spec;
|
||||
}
|
||||
return defineLazyModuleGetter.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Load the module by Service newFileURI API for running extension's XPCShell test
|
||||
function importAutofillModule(module) {
|
||||
return Cu.import(Services.io.newFileURI(do_get_file(module)).spec);
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
|
||||
"resource://gre/modules/DownloadPaths.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
// While the previous test file should have deleted all the temporary files it
|
||||
// used, on Windows these might still be pending deletion on the physical file
|
||||
// system. Thus, start from a new base number every time, to make a collision
|
||||
// with a file that is still pending deletion highly unlikely.
|
||||
let gFileCounter = Math.floor(Math.random() * 1000000);
|
||||
|
||||
/**
|
||||
* Returns a reference to a temporary file, that is guaranteed not to exist, and
|
||||
* to have never been created before.
|
||||
*
|
||||
* @param {string} leafName
|
||||
* Suggested leaf name for the file to be created.
|
||||
*
|
||||
* @returns {nsIFile} pointing to a non-existent file in a temporary directory.
|
||||
*
|
||||
* @note It is not enough to delete the file if it exists, or to delete the file
|
||||
* after calling nsIFile.createUnique, because on Windows the delete
|
||||
* operation in the file system may still be pending, preventing a new
|
||||
* file with the same name to be created.
|
||||
*/
|
||||
function getTempFile(leafName) {
|
||||
// Prepend a serial number to the extension in the suggested leaf name.
|
||||
let [base, ext] = DownloadPaths.splitBaseNameAndExtension(leafName);
|
||||
let finalLeafName = base + "-" + gFileCounter + ext;
|
||||
gFileCounter++;
|
||||
|
||||
// Get a file reference under the temporary directory for this test file.
|
||||
let file = FileUtils.getFile("TmpD", [finalLeafName]);
|
||||
do_check_false(file.exists());
|
||||
|
||||
do_register_cleanup(function() {
|
||||
if (file.exists()) {
|
||||
file.remove(false);
|
||||
}
|
||||
});
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
add_task(function* test_common_initialize() {
|
||||
Services.prefs.setBoolPref("dom.forms.autocomplete.experimental", true);
|
||||
|
||||
// Clean up after every test.
|
||||
do_register_cleanup(() => {
|
||||
Services.prefs.setBoolPref("dom.forms.autocomplete.experimental", false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Test for form auto fill content helper fill all inputs function.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
let {FormAutofillHandler} = importAutofillModule("FormAutofillContent.jsm");
|
||||
|
||||
const TESTCASES = [
|
||||
{
|
||||
description: "Form without autocomplete property",
|
||||
document: `<form><input id="given-name"><input id="family-name">
|
||||
<input id="street-addr"><input id="city"><input id="country">
|
||||
<input id='email'><input id="tel"></form>`,
|
||||
fieldDetails: [],
|
||||
profileData: [],
|
||||
expectedResult: {
|
||||
"given-name": "",
|
||||
"family-name": "",
|
||||
"street-addr": "",
|
||||
"city": "",
|
||||
"country": "",
|
||||
"email": "",
|
||||
"tel": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Form with autocomplete properties and 1 token",
|
||||
document: `<form><input id="given-name" autocomplete="given-name">
|
||||
<input id="family-name" autocomplete="family-name">
|
||||
<input id="street-addr" autocomplete="street-address">
|
||||
<input id="city" autocomplete="address-level2">
|
||||
<input id="country" autocomplete="country">
|
||||
<input id="email" autocomplete="email">
|
||||
<input id="tel" autocomplete="tel"></form>`,
|
||||
fieldDetails: [
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "given-name", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "family-name", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "street-address", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "email", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel", "element": {}},
|
||||
],
|
||||
profileData: [
|
||||
{"section": "", "addressType": "", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
|
||||
{"section": "", "addressType": "", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
|
||||
{"section": "", "addressType": "", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
|
||||
{"section": "", "addressType": "", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
|
||||
{"section": "", "addressType": "", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
|
||||
{"section": "", "addressType": "", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
|
||||
{"section": "", "addressType": "", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
|
||||
],
|
||||
expectedResult: {
|
||||
"given-name": "foo",
|
||||
"family-name": "bar",
|
||||
"street-addr": "2 Harrison St",
|
||||
"city": "San Francisco",
|
||||
"country": "US",
|
||||
"email": "foo@mozilla.com",
|
||||
"tel": "1234567",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Form with autocomplete properties and 2 tokens",
|
||||
document: `<form><input id="given-name" autocomplete="shipping given-name">
|
||||
<input id="family-name" autocomplete="shipping family-name">
|
||||
<input id="street-addr" autocomplete="shipping street-address">
|
||||
<input id="city" autocomplete="shipping address-level2">
|
||||
<input id="country" autocomplete="shipping country">
|
||||
<input id='email' autocomplete="shipping email">
|
||||
<input id="tel" autocomplete="shipping tel"></form>`,
|
||||
fieldDetails: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
|
||||
],
|
||||
profileData: [
|
||||
{"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
|
||||
],
|
||||
expectedResult: {
|
||||
"given-name": "foo",
|
||||
"family-name": "bar",
|
||||
"street-addr": "2 Harrison St",
|
||||
"city": "San Francisco",
|
||||
"country": "US",
|
||||
"email": "foo@mozilla.com",
|
||||
"tel": "1234567",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Form with autocomplete properties and profile is partly matched",
|
||||
document: `<form><input id="given-name" autocomplete="shipping given-name">
|
||||
<input id="family-name" autocomplete="shipping family-name">
|
||||
<input id="street-addr" autocomplete="shipping street-address">
|
||||
<input id="city" autocomplete="shipping address-level2">
|
||||
<input id="country" autocomplete="shipping country">
|
||||
<input id='email' autocomplete="shipping email">
|
||||
<input id="tel" autocomplete="shipping tel"></form>`,
|
||||
fieldDetails: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
|
||||
],
|
||||
profileData: [
|
||||
{"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6},
|
||||
],
|
||||
expectedResult: {
|
||||
"given-name": "foo",
|
||||
"family-name": "bar",
|
||||
"street-addr": "2 Harrison St",
|
||||
"city": "San Francisco",
|
||||
"country": "US",
|
||||
"email": "",
|
||||
"tel": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Form with autocomplete properties but mismatched",
|
||||
document: `<form><input id="given-name" autocomplete="shipping given-name">
|
||||
<input id="family-name" autocomplete="shipping family-name">
|
||||
<input id="street-addr" autocomplete="billing street-address">
|
||||
<input id="city" autocomplete="billing address-level2">
|
||||
<input id="country" autocomplete="billing country">
|
||||
<input id='email' autocomplete="shipping email">
|
||||
<input id="tel" autocomplete="shipping tel"></form>`,
|
||||
fieldDetails: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
|
||||
],
|
||||
profileData: [
|
||||
{"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
|
||||
{"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
|
||||
],
|
||||
expectedResult: {
|
||||
"given-name": "foo",
|
||||
"family-name": "bar",
|
||||
"street-addr": "",
|
||||
"city": "",
|
||||
"country": "",
|
||||
"email": "foo@mozilla.com",
|
||||
"tel": "1234567",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (let tc of TESTCASES) {
|
||||
(function() {
|
||||
let testcase = tc;
|
||||
add_task(function* () {
|
||||
do_print("Starting testcase: " + testcase.description);
|
||||
|
||||
let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
|
||||
testcase.document);
|
||||
let form = doc.querySelector("form");
|
||||
let handler = new FormAutofillHandler(form);
|
||||
|
||||
handler.fieldDetails = testcase.fieldDetails;
|
||||
handler.fieldDetails.forEach((field, index) => {
|
||||
field.element = doc.querySelectorAll("input")[index];
|
||||
});
|
||||
|
||||
handler.autofillFormFields(testcase.profileData);
|
||||
for (let id in testcase.expectedResult) {
|
||||
Assert.equal(doc.getElementById(id).value, testcase.expectedResult[id],
|
||||
"Check the " + id + " fields were filled with correct data");
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Test for form auto fill content helper collectFormFields functions.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
let {FormAutofillHandler} = importAutofillModule("FormAutofillContent.jsm");
|
||||
|
||||
const TESTCASES = [
|
||||
{
|
||||
description: "Form without autocomplete property",
|
||||
document: `<form><input id="given-name"><input id="family-name">
|
||||
<input id="street-addr"><input id="city"><input id="country">
|
||||
<input id='email'><input id="tel"></form>`,
|
||||
returnedFormat: [],
|
||||
fieldDetails: [],
|
||||
},
|
||||
{
|
||||
description: "Form with autocomplete properties and 1 token",
|
||||
document: `<form><input id="given-name" autocomplete="given-name">
|
||||
<input id="family-name" autocomplete="family-name">
|
||||
<input id="street-addr" autocomplete="street-address">
|
||||
<input id="city" autocomplete="address-level2">
|
||||
<input id="country" autocomplete="country">
|
||||
<input id="email" autocomplete="email">
|
||||
<input id="tel" autocomplete="tel"></form>`,
|
||||
returnedFormat: [
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "given-name", "index": 0},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "family-name", "index": 1},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "street-address", "index": 2},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2", "index": 3},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "country", "index": 4},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "email", "index": 5},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel", "index": 6},
|
||||
],
|
||||
fieldDetails: [
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "given-name", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "family-name", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "street-address", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "email", "element": {}},
|
||||
{"section": "", "addressType": "", "contactType": "", "fieldName": "tel", "element": {}},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: "Form with autocomplete properties and 2 tokens",
|
||||
document: `<form><input id="given-name" autocomplete="shipping given-name">
|
||||
<input id="family-name" autocomplete="shipping family-name">
|
||||
<input id="street-addr" autocomplete="shipping street-address">
|
||||
<input id="city" autocomplete="shipping address-level2">
|
||||
<input id="country" autocomplete="shipping country">
|
||||
<input id='email' autocomplete="shipping email">
|
||||
<input id="tel" autocomplete="shipping tel"></form>`,
|
||||
returnedFormat: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "index": 0},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "index": 1},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "index": 2},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "index": 3},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "index": 4},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "index": 5},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "index": 6},
|
||||
],
|
||||
fieldDetails: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: "Form with autocomplete properties and profile is partly matched",
|
||||
document: `<form><input id="given-name" autocomplete="shipping given-name">
|
||||
<input id="family-name" autocomplete="shipping family-name">
|
||||
<input id="street-addr" autocomplete="shipping street-address">
|
||||
<input id="city" autocomplete="shipping address-level2">
|
||||
<input id="country" autocomplete="shipping country">
|
||||
<input id='email' autocomplete="shipping email">
|
||||
<input id="tel" autocomplete="shipping tel"></form>`,
|
||||
returnedFormat: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "index": 0},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "index": 1},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "index": 2},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "index": 3},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "index": 4},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "index": 5},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "index": 6},
|
||||
],
|
||||
fieldDetails: [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
for (let tc of TESTCASES) {
|
||||
(function() {
|
||||
let testcase = tc;
|
||||
add_task(function* () {
|
||||
do_print("Starting testcase: " + testcase.description);
|
||||
|
||||
let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
|
||||
testcase.document);
|
||||
let form = doc.querySelector("form");
|
||||
let handler = new FormAutofillHandler(form);
|
||||
|
||||
Assert.deepEqual(handler.collectFormFields(), testcase.returnedFormat,
|
||||
"Check the format of form autofill were returned correctly");
|
||||
|
||||
Assert.deepEqual(handler.fieldDetails, testcase.fieldDetails,
|
||||
"Check the fieldDetails were set correctly");
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Test for populating field values in Form Autofill Parent.
|
||||
*/
|
||||
|
||||
/* global FormAutofillParent */
|
||||
|
||||
"use strict";
|
||||
|
||||
importAutofillModule("FormAutofillParent.jsm");
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const TEST_FIELDS = [
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "organization"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "postal-code"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
|
||||
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
|
||||
];
|
||||
|
||||
const TEST_GUID = "test-guid";
|
||||
|
||||
const TEST_PROFILE = {
|
||||
guid: TEST_GUID,
|
||||
organization: "World Wide Web Consortium",
|
||||
streetAddress: "32 Vassar Street\nMIT Room 32-G524",
|
||||
addressLevel2: "Cambridge",
|
||||
addressLevel1: "MA",
|
||||
postalCode: "02139",
|
||||
country: "US",
|
||||
tel: "+1 617 253 5702",
|
||||
email: "timbl@w3.org",
|
||||
};
|
||||
|
||||
function camelCase(str) {
|
||||
return str.toLowerCase().replace(/-([a-z])/g, s => s[1].toUpperCase());
|
||||
}
|
||||
|
||||
add_task(function* test_populateFieldValues() {
|
||||
FormAutofillParent.init();
|
||||
|
||||
let store = FormAutofillParent.getProfileStore();
|
||||
do_check_neq(store, null);
|
||||
|
||||
store.get = function(guid) {
|
||||
do_check_eq(guid, TEST_GUID);
|
||||
return store._clone(TEST_PROFILE);
|
||||
};
|
||||
|
||||
let notifyUsedCalledCount = 0;
|
||||
store.notifyUsed = function(guid) {
|
||||
do_check_eq(guid, TEST_GUID);
|
||||
notifyUsedCalledCount++;
|
||||
};
|
||||
|
||||
yield new Promise((resolve) => {
|
||||
FormAutofillParent.receiveMessage({
|
||||
name: "FormAutofill:PopulateFieldValues",
|
||||
data: {
|
||||
guid: TEST_GUID,
|
||||
fields: TEST_FIELDS,
|
||||
},
|
||||
target: {
|
||||
sendAsyncMessage: function(name, data) {
|
||||
do_check_eq(name, "FormAutofill:fillForm");
|
||||
|
||||
let fields = data.fields;
|
||||
do_check_eq(fields.length, TEST_FIELDS.length);
|
||||
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
do_check_eq(fields[i].fieldName, TEST_FIELDS[i].fieldName);
|
||||
do_check_eq(fields[i].value,
|
||||
TEST_PROFILE[camelCase(fields[i].fieldName)]);
|
||||
}
|
||||
|
||||
resolve();
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
do_check_eq(notifyUsedCalledCount, 1);
|
||||
|
||||
FormAutofillParent._uninit();
|
||||
do_check_null(FormAutofillParent.getProfileStore());
|
||||
});
|
||||
|
||||
add_task(function* test_populateFieldValues_with_invalid_guid() {
|
||||
FormAutofillParent.init();
|
||||
|
||||
Assert.throws(() => {
|
||||
FormAutofillParent.receiveMessage({
|
||||
name: "FormAutofill:PopulateFieldValues",
|
||||
data: {
|
||||
guid: "invalid-guid",
|
||||
fields: TEST_FIELDS,
|
||||
},
|
||||
target: {},
|
||||
});
|
||||
}, /No matching profile\./);
|
||||
|
||||
FormAutofillParent._uninit();
|
||||
});
|
||||
222
browser/extensions/formautofill/test/unit/test_profileStorage.js
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/**
|
||||
* Tests ProfileStorage object.
|
||||
*/
|
||||
|
||||
/* global ProfileStorage */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import(Services.io.newFileURI(do_get_file("ProfileStorage.jsm")).spec);
|
||||
|
||||
const TEST_STORE_FILE_NAME = "test-profile.json";
|
||||
|
||||
const TEST_PROFILE_1 = {
|
||||
organization: "World Wide Web Consortium",
|
||||
streetAddress: "32 Vassar Street\nMIT Room 32-G524",
|
||||
addressLevel2: "Cambridge",
|
||||
addressLevel1: "MA",
|
||||
postalCode: "02139",
|
||||
country: "US",
|
||||
tel: "+1 617 253 5702",
|
||||
email: "timbl@w3.org",
|
||||
};
|
||||
|
||||
const TEST_PROFILE_2 = {
|
||||
streetAddress: "Some Address",
|
||||
country: "US",
|
||||
};
|
||||
|
||||
const TEST_PROFILE_3 = {
|
||||
streetAddress: "Other Address",
|
||||
postalCode: "12345",
|
||||
};
|
||||
|
||||
const TEST_PROFILE_WITH_INVALID_FIELD = {
|
||||
streetAddress: "Another Address",
|
||||
invalidField: "INVALID",
|
||||
};
|
||||
|
||||
let prepareTestProfiles = Task.async(function* (path) {
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
profileStorage.add(TEST_PROFILE_1);
|
||||
profileStorage.add(TEST_PROFILE_2);
|
||||
yield profileStorage._saveImmediately();
|
||||
});
|
||||
|
||||
let do_check_profile_matches = (profileWithMeta, profile) => {
|
||||
for (let key in profile) {
|
||||
do_check_eq(profileWithMeta[key], profile[key]);
|
||||
}
|
||||
};
|
||||
|
||||
add_task(function* test_initialize() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
do_check_eq(profileStorage._store.data.version, 1);
|
||||
do_check_eq(profileStorage._store.data.profiles.length, 0);
|
||||
|
||||
let data = profileStorage._store.data;
|
||||
|
||||
yield profileStorage._saveImmediately();
|
||||
|
||||
profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
Assert.deepEqual(profileStorage._store.data, data);
|
||||
});
|
||||
|
||||
add_task(function* test_getAll() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
yield prepareTestProfiles(path);
|
||||
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
let profiles = profileStorage.getAll();
|
||||
|
||||
do_check_eq(profiles.length, 2);
|
||||
do_check_profile_matches(profiles[0], TEST_PROFILE_1);
|
||||
do_check_profile_matches(profiles[1], TEST_PROFILE_2);
|
||||
|
||||
// Modifying output shouldn't affect the storage.
|
||||
profiles[0].organization = "test";
|
||||
do_check_profile_matches(profileStorage.getAll()[0], TEST_PROFILE_1);
|
||||
});
|
||||
|
||||
add_task(function* test_get() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
yield prepareTestProfiles(path);
|
||||
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
let profiles = profileStorage.getAll();
|
||||
let guid = profiles[0].guid;
|
||||
|
||||
let profile = profileStorage.get(guid);
|
||||
do_check_profile_matches(profile, TEST_PROFILE_1);
|
||||
|
||||
// Modifying output shouldn't affect the storage.
|
||||
profile.organization = "test";
|
||||
do_check_profile_matches(profileStorage.get(guid), TEST_PROFILE_1);
|
||||
|
||||
Assert.throws(() => profileStorage.get("INVALID_GUID"),
|
||||
/No matching profile\./);
|
||||
});
|
||||
|
||||
add_task(function* test_add() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
yield prepareTestProfiles(path);
|
||||
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
let profiles = profileStorage.getAll();
|
||||
|
||||
do_check_eq(profiles.length, 2);
|
||||
|
||||
do_check_profile_matches(profiles[0], TEST_PROFILE_1);
|
||||
do_check_profile_matches(profiles[1], TEST_PROFILE_2);
|
||||
|
||||
do_check_neq(profiles[0].guid, undefined);
|
||||
do_check_neq(profiles[0].timeCreated, undefined);
|
||||
do_check_eq(profiles[0].timeLastModified, profiles[0].timeCreated);
|
||||
do_check_eq(profiles[0].timeLastUsed, 0);
|
||||
do_check_eq(profiles[0].timesUsed, 0);
|
||||
|
||||
Assert.throws(() => profileStorage.add(TEST_PROFILE_WITH_INVALID_FIELD),
|
||||
/"invalidField" is not a valid field\./);
|
||||
});
|
||||
|
||||
add_task(function* test_update() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
yield prepareTestProfiles(path);
|
||||
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
let profiles = profileStorage.getAll();
|
||||
let guid = profiles[1].guid;
|
||||
let timeLastModified = profiles[1].timeLastModified;
|
||||
|
||||
do_check_neq(profiles[1].country, undefined);
|
||||
|
||||
profileStorage.update(guid, TEST_PROFILE_3);
|
||||
yield profileStorage._saveImmediately();
|
||||
|
||||
profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
let profile = profileStorage.get(guid);
|
||||
|
||||
do_check_eq(profile.country, undefined);
|
||||
do_check_neq(profile.timeLastModified, timeLastModified);
|
||||
do_check_profile_matches(profile, TEST_PROFILE_3);
|
||||
|
||||
Assert.throws(
|
||||
() => profileStorage.update("INVALID_GUID", TEST_PROFILE_3),
|
||||
/No matching profile\./
|
||||
);
|
||||
|
||||
Assert.throws(
|
||||
() => profileStorage.update(guid, TEST_PROFILE_WITH_INVALID_FIELD),
|
||||
/"invalidField" is not a valid field\./
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function* test_notifyUsed() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
yield prepareTestProfiles(path);
|
||||
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
let profiles = profileStorage.getAll();
|
||||
let guid = profiles[1].guid;
|
||||
let timeLastUsed = profiles[1].timeLastUsed;
|
||||
let timesUsed = profiles[1].timesUsed;
|
||||
|
||||
profileStorage.notifyUsed(guid);
|
||||
yield profileStorage._saveImmediately();
|
||||
|
||||
profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
let profile = profileStorage.get(guid);
|
||||
|
||||
do_check_eq(profile.timesUsed, timesUsed + 1);
|
||||
do_check_neq(profile.timeLastUsed, timeLastUsed);
|
||||
|
||||
Assert.throws(() => profileStorage.notifyUsed("INVALID_GUID"),
|
||||
/No matching profile\./);
|
||||
});
|
||||
|
||||
add_task(function* test_remove() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
yield prepareTestProfiles(path);
|
||||
|
||||
let profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
let profiles = profileStorage.getAll();
|
||||
let guid = profiles[1].guid;
|
||||
|
||||
do_check_eq(profiles.length, 2);
|
||||
|
||||
profileStorage.remove(guid);
|
||||
yield profileStorage._saveImmediately();
|
||||
|
||||
profileStorage = new ProfileStorage(path);
|
||||
yield profileStorage.initialize();
|
||||
|
||||
profiles = profileStorage.getAll();
|
||||
|
||||
do_check_eq(profiles.length, 1);
|
||||
|
||||
Assert.throws(() => profileStorage.get(guid), /No matching profile\./);
|
||||
});
|
||||
12
browser/extensions/formautofill/test/unit/xpcshell.ini
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[DEFAULT]
|
||||
head = head.js
|
||||
tail =
|
||||
support-files =
|
||||
../../content/FormAutofillContent.jsm
|
||||
../../content/FormAutofillParent.jsm
|
||||
../../content/ProfileStorage.jsm
|
||||
|
||||
[test_autofillFormFields.js]
|
||||
[test_collectFormFields.js]
|
||||
[test_populateFieldValues.js]
|
||||
[test_profileStorage.js]
|
||||
20
browser/extensions/moz.build
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += [
|
||||
'aushelper',
|
||||
'e10srollout',
|
||||
'pdfjs',
|
||||
'pocket',
|
||||
'webcompat',
|
||||
]
|
||||
|
||||
# Only include the following system add-ons if building Aurora or Nightly
|
||||
if 'a' in CONFIG['GRE_MILESTONE']:
|
||||
DIRS += [
|
||||
'flyweb',
|
||||
'formautofill',
|
||||
]
|
||||
177
browser/extensions/pdfjs/LICENSE
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
3
browser/extensions/pdfjs/README.mozilla
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
This is the pdf.js project output, https://github.com/mozilla/pdf.js
|
||||
|
||||
Current extension version is: 1.6.315
|
||||
1
browser/extensions/pdfjs/chrome.manifest
Normal file
|
|
@ -0,0 +1 @@
|
|||
resource pdf.js content/
|
||||
348
browser/extensions/pdfjs/content/PdfJs.jsm
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* jshint esnext:true */
|
||||
/* globals Components, Services, XPCOMUtils, PdfjsChromeUtils,
|
||||
PdfjsContentUtils, PdfStreamConverter */
|
||||
|
||||
'use strict';
|
||||
|
||||
var EXPORTED_SYMBOLS = ['PdfJs'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cm = Components.manager;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const PREF_PREFIX = 'pdfjs';
|
||||
const PREF_DISABLED = PREF_PREFIX + '.disabled';
|
||||
const PREF_MIGRATION_VERSION = PREF_PREFIX + '.migrationVersion';
|
||||
const PREF_PREVIOUS_ACTION = PREF_PREFIX + '.previousHandler.preferredAction';
|
||||
const PREF_PREVIOUS_ASK = PREF_PREFIX +
|
||||
'.previousHandler.alwaysAskBeforeHandling';
|
||||
const PREF_DISABLED_PLUGIN_TYPES = 'plugin.disable_full_page_plugin_for_types';
|
||||
const TOPIC_PDFJS_HANDLER_CHANGED = 'pdfjs:handlerChanged';
|
||||
const TOPIC_PLUGINS_LIST_UPDATED = 'plugins-list-updated';
|
||||
const TOPIC_PLUGIN_INFO_UPDATED = 'plugin-info-updated';
|
||||
const PDF_CONTENT_TYPE = 'application/pdf';
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
var Svc = {};
|
||||
XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
|
||||
'@mozilla.org/mime;1',
|
||||
'nsIMIMEService');
|
||||
XPCOMUtils.defineLazyServiceGetter(Svc, 'pluginHost',
|
||||
'@mozilla.org/plugin/host;1',
|
||||
'nsIPluginHost');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PdfjsChromeUtils',
|
||||
'resource://pdf.js/PdfjsChromeUtils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PdfjsContentUtils',
|
||||
'resource://pdf.js/PdfjsContentUtils.jsm');
|
||||
|
||||
function getBoolPref(aPref, aDefaultValue) {
|
||||
try {
|
||||
return Services.prefs.getBoolPref(aPref);
|
||||
} catch (ex) {
|
||||
return aDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
function getIntPref(aPref, aDefaultValue) {
|
||||
try {
|
||||
return Services.prefs.getIntPref(aPref);
|
||||
} catch (ex) {
|
||||
return aDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
function isDefaultHandler() {
|
||||
if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
|
||||
return PdfjsContentUtils.isDefaultHandlerApp();
|
||||
}
|
||||
return PdfjsChromeUtils.isDefaultHandlerApp();
|
||||
}
|
||||
|
||||
function initializeDefaultPreferences() {
|
||||
var DEFAULT_PREFERENCES =
|
||||
{
|
||||
"showPreviousViewOnLoad": true,
|
||||
"defaultZoomValue": "",
|
||||
"sidebarViewOnLoad": 0,
|
||||
"enableHandToolOnLoad": false,
|
||||
"enableWebGL": false,
|
||||
"pdfBugEnabled": false,
|
||||
"disableRange": false,
|
||||
"disableStream": false,
|
||||
"disableAutoFetch": false,
|
||||
"disableFontFace": false,
|
||||
"disableTextLayer": false,
|
||||
"useOnlyCssZoom": false,
|
||||
"externalLinkTarget": 0,
|
||||
"enhanceTextSelection": false,
|
||||
"renderInteractiveForms": false,
|
||||
"disablePageLabels": false
|
||||
}
|
||||
|
||||
|
||||
var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
|
||||
var defaultValue;
|
||||
for (var key in DEFAULT_PREFERENCES) {
|
||||
defaultValue = DEFAULT_PREFERENCES[key];
|
||||
switch (typeof defaultValue) {
|
||||
case 'boolean':
|
||||
defaultBranch.setBoolPref(key, defaultValue);
|
||||
break;
|
||||
case 'number':
|
||||
defaultBranch.setIntPref(key, defaultValue);
|
||||
break;
|
||||
case 'string':
|
||||
defaultBranch.setCharPref(key, defaultValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register/unregister a constructor as a factory.
|
||||
function Factory() {}
|
||||
Factory.prototype = {
|
||||
register: function register(targetConstructor) {
|
||||
var proto = targetConstructor.prototype;
|
||||
this._classID = proto.classID;
|
||||
|
||||
var factory = XPCOMUtils._getFactory(targetConstructor);
|
||||
this._factory = factory;
|
||||
|
||||
var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.registerFactory(proto.classID, proto.classDescription,
|
||||
proto.contractID, factory);
|
||||
|
||||
if (proto.classID2) {
|
||||
this._classID2 = proto.classID2;
|
||||
registrar.registerFactory(proto.classID2, proto.classDescription,
|
||||
proto.contractID2, factory);
|
||||
}
|
||||
},
|
||||
|
||||
unregister: function unregister() {
|
||||
var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.unregisterFactory(this._classID, this._factory);
|
||||
if (this._classID2) {
|
||||
registrar.unregisterFactory(this._classID2, this._factory);
|
||||
}
|
||||
this._factory = null;
|
||||
}
|
||||
};
|
||||
|
||||
var PdfJs = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
_registered: false,
|
||||
_initialized: false,
|
||||
|
||||
init: function init(remote) {
|
||||
if (Services.appinfo.processType !==
|
||||
Services.appinfo.PROCESS_TYPE_DEFAULT) {
|
||||
throw new Error('PdfJs.init should only get called ' +
|
||||
'in the parent process.');
|
||||
}
|
||||
PdfjsChromeUtils.init();
|
||||
if (!remote) {
|
||||
PdfjsContentUtils.init();
|
||||
}
|
||||
this.initPrefs();
|
||||
this.updateRegistration();
|
||||
},
|
||||
|
||||
initPrefs: function initPrefs() {
|
||||
if (this._initialized) {
|
||||
return;
|
||||
}
|
||||
this._initialized = true;
|
||||
|
||||
if (!getBoolPref(PREF_DISABLED, true)) {
|
||||
this._migrate();
|
||||
}
|
||||
|
||||
// Listen for when pdf.js is completely disabled or a different pdf handler
|
||||
// is chosen.
|
||||
Services.prefs.addObserver(PREF_DISABLED, this, false);
|
||||
Services.prefs.addObserver(PREF_DISABLED_PLUGIN_TYPES, this, false);
|
||||
Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false);
|
||||
Services.obs.addObserver(this, TOPIC_PLUGINS_LIST_UPDATED, false);
|
||||
Services.obs.addObserver(this, TOPIC_PLUGIN_INFO_UPDATED, false);
|
||||
|
||||
initializeDefaultPreferences();
|
||||
},
|
||||
|
||||
updateRegistration: function updateRegistration() {
|
||||
if (this.enabled) {
|
||||
this._ensureRegistered();
|
||||
} else {
|
||||
this._ensureUnregistered();
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
if (this._initialized) {
|
||||
Services.prefs.removeObserver(PREF_DISABLED, this, false);
|
||||
Services.prefs.removeObserver(PREF_DISABLED_PLUGIN_TYPES, this, false);
|
||||
Services.obs.removeObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false);
|
||||
Services.obs.removeObserver(this, TOPIC_PLUGINS_LIST_UPDATED, false);
|
||||
Services.obs.removeObserver(this, TOPIC_PLUGIN_INFO_UPDATED, false);
|
||||
this._initialized = false;
|
||||
}
|
||||
this._ensureUnregistered();
|
||||
},
|
||||
|
||||
_migrate: function migrate() {
|
||||
const VERSION = 2;
|
||||
var currentVersion = getIntPref(PREF_MIGRATION_VERSION, 0);
|
||||
if (currentVersion >= VERSION) {
|
||||
return;
|
||||
}
|
||||
// Make pdf.js the default pdf viewer on the first migration.
|
||||
if (currentVersion < 1) {
|
||||
this._becomeHandler();
|
||||
}
|
||||
if (currentVersion < 2) {
|
||||
// cleaning up of unused database preference (see #3994)
|
||||
Services.prefs.clearUserPref(PREF_PREFIX + '.database');
|
||||
}
|
||||
Services.prefs.setIntPref(PREF_MIGRATION_VERSION, VERSION);
|
||||
},
|
||||
|
||||
_becomeHandler: function _becomeHandler() {
|
||||
let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf');
|
||||
let prefs = Services.prefs;
|
||||
if (handlerInfo.preferredAction !== Ci.nsIHandlerInfo.handleInternally &&
|
||||
handlerInfo.preferredAction !== false) {
|
||||
// Store the previous settings of preferredAction and
|
||||
// alwaysAskBeforeHandling in case we need to revert them in a hotfix that
|
||||
// would turn pdf.js off.
|
||||
prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction);
|
||||
prefs.setBoolPref(PREF_PREVIOUS_ASK, handlerInfo.alwaysAskBeforeHandling);
|
||||
}
|
||||
|
||||
let handlerService = Cc['@mozilla.org/uriloader/handler-service;1'].
|
||||
getService(Ci.nsIHandlerService);
|
||||
|
||||
// Change and save mime handler settings.
|
||||
handlerInfo.alwaysAskBeforeHandling = false;
|
||||
handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
|
||||
handlerService.store(handlerInfo);
|
||||
|
||||
// Also disable any plugins for pdfs.
|
||||
var stringTypes = '';
|
||||
var types = [];
|
||||
if (prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
|
||||
stringTypes = prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
|
||||
}
|
||||
if (stringTypes !== '') {
|
||||
types = stringTypes.split(',');
|
||||
}
|
||||
|
||||
if (types.indexOf(PDF_CONTENT_TYPE) === -1) {
|
||||
types.push(PDF_CONTENT_TYPE);
|
||||
}
|
||||
prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES, types.join(','));
|
||||
|
||||
// Update the category manager in case the plugins are already loaded.
|
||||
let categoryManager = Cc['@mozilla.org/categorymanager;1'];
|
||||
categoryManager.getService(Ci.nsICategoryManager).
|
||||
deleteCategoryEntry('Gecko-Content-Viewers',
|
||||
PDF_CONTENT_TYPE,
|
||||
false);
|
||||
},
|
||||
|
||||
// nsIObserver
|
||||
observe: function observe(aSubject, aTopic, aData) {
|
||||
this.updateRegistration();
|
||||
if (Services.appinfo.processType ===
|
||||
Services.appinfo.PROCESS_TYPE_DEFAULT) {
|
||||
let jsm = 'resource://pdf.js/PdfjsChromeUtils.jsm';
|
||||
let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
|
||||
PdfjsChromeUtils.notifyChildOfSettingsChange();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* pdf.js is only enabled if it is both selected as the pdf viewer and if the
|
||||
* global switch enabling it is true.
|
||||
* @return {boolean} Whether or not it's enabled.
|
||||
*/
|
||||
get enabled() {
|
||||
var disabled = getBoolPref(PREF_DISABLED, true);
|
||||
if (disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the 'application/pdf' preview handler is configured properly.
|
||||
if (!isDefaultHandler()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we have disabled plugin handling of 'application/pdf' in prefs
|
||||
if (Services.prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
|
||||
let disabledPluginTypes =
|
||||
Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES).split(',');
|
||||
if (disabledPluginTypes.indexOf(PDF_CONTENT_TYPE) >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is an enabled pdf plugin.
|
||||
// Note: this check is performed last because getPluginTags() triggers
|
||||
// costly plugin list initialization (bug 881575)
|
||||
let tags = Cc['@mozilla.org/plugin/host;1'].
|
||||
getService(Ci.nsIPluginHost).
|
||||
getPluginTags();
|
||||
let enabledPluginFound = tags.some(function(tag) {
|
||||
if (tag.disabled) {
|
||||
return false;
|
||||
}
|
||||
let mimeTypes = tag.getMimeTypes();
|
||||
return mimeTypes.some(function(mimeType) {
|
||||
return mimeType === PDF_CONTENT_TYPE;
|
||||
});
|
||||
});
|
||||
|
||||
// Use pdf.js if pdf plugin is not present or disabled
|
||||
return !enabledPluginFound;
|
||||
},
|
||||
|
||||
_ensureRegistered: function _ensureRegistered() {
|
||||
if (this._registered) {
|
||||
return;
|
||||
}
|
||||
this._pdfStreamConverterFactory = new Factory();
|
||||
Cu.import('resource://pdf.js/PdfStreamConverter.jsm');
|
||||
this._pdfStreamConverterFactory.register(PdfStreamConverter);
|
||||
|
||||
this._registered = true;
|
||||
},
|
||||
|
||||
_ensureUnregistered: function _ensureUnregistered() {
|
||||
if (!this._registered) {
|
||||
return;
|
||||
}
|
||||
this._pdfStreamConverterFactory.unregister();
|
||||
Cu.unload('resource://pdf.js/PdfStreamConverter.jsm');
|
||||
delete this._pdfStreamConverterFactory;
|
||||
|
||||
this._registered = false;
|
||||
}
|
||||
};
|
||||
|
||||
257
browser/extensions/pdfjs/content/PdfJsNetwork.jsm
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* globals Components, Services */
|
||||
|
||||
'use strict';
|
||||
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
var EXPORTED_SYMBOLS = ['NetworkManager'];
|
||||
|
||||
function log(aMsg) {
|
||||
var msg = 'network.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
|
||||
Services.console.logStringMessage(msg);
|
||||
}
|
||||
|
||||
var NetworkManager = (function NetworkManagerClosure() {
|
||||
|
||||
var OK_RESPONSE = 200;
|
||||
var PARTIAL_CONTENT_RESPONSE = 206;
|
||||
|
||||
function NetworkManager(url, args) {
|
||||
this.url = url;
|
||||
args = args || {};
|
||||
this.isHttp = /^https?:/i.test(url);
|
||||
this.httpHeaders = (this.isHttp && args.httpHeaders) || {};
|
||||
this.withCredentials = args.withCredentials || false;
|
||||
this.getXhr = args.getXhr ||
|
||||
function NetworkManager_getXhr() {
|
||||
return new XMLHttpRequest();
|
||||
};
|
||||
|
||||
this.currXhrId = 0;
|
||||
this.pendingRequests = Object.create(null);
|
||||
this.loadedRequests = Object.create(null);
|
||||
}
|
||||
|
||||
function getArrayBuffer(xhr) {
|
||||
var data = xhr.response;
|
||||
if (typeof data !== 'string') {
|
||||
return data;
|
||||
}
|
||||
var length = data.length;
|
||||
var array = new Uint8Array(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
array[i] = data.charCodeAt(i) & 0xFF;
|
||||
}
|
||||
return array.buffer;
|
||||
}
|
||||
|
||||
NetworkManager.prototype = {
|
||||
requestRange: function NetworkManager_requestRange(begin, end, listeners) {
|
||||
var args = {
|
||||
begin: begin,
|
||||
end: end
|
||||
};
|
||||
for (var prop in listeners) {
|
||||
args[prop] = listeners[prop];
|
||||
}
|
||||
return this.request(args);
|
||||
},
|
||||
|
||||
requestFull: function NetworkManager_requestFull(listeners) {
|
||||
return this.request(listeners);
|
||||
},
|
||||
|
||||
request: function NetworkManager_request(args) {
|
||||
var xhr = this.getXhr();
|
||||
var xhrId = this.currXhrId++;
|
||||
var pendingRequest = this.pendingRequests[xhrId] = {
|
||||
xhr: xhr
|
||||
};
|
||||
|
||||
xhr.open('GET', this.url);
|
||||
xhr.withCredentials = this.withCredentials;
|
||||
for (var property in this.httpHeaders) {
|
||||
var value = this.httpHeaders[property];
|
||||
if (typeof value === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
xhr.setRequestHeader(property, value);
|
||||
}
|
||||
if (this.isHttp && 'begin' in args && 'end' in args) {
|
||||
var rangeStr = args.begin + '-' + (args.end - 1);
|
||||
xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
|
||||
pendingRequest.expectedStatus = 206;
|
||||
} else {
|
||||
pendingRequest.expectedStatus = 200;
|
||||
}
|
||||
|
||||
var useMozChunkedLoading = !!args.onProgressiveData;
|
||||
if (useMozChunkedLoading) {
|
||||
xhr.responseType = 'moz-chunked-arraybuffer';
|
||||
pendingRequest.onProgressiveData = args.onProgressiveData;
|
||||
pendingRequest.mozChunked = true;
|
||||
} else {
|
||||
xhr.responseType = 'arraybuffer';
|
||||
}
|
||||
|
||||
if (args.onError) {
|
||||
xhr.onerror = function(evt) {
|
||||
args.onError(xhr.status);
|
||||
};
|
||||
}
|
||||
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
|
||||
xhr.onprogress = this.onProgress.bind(this, xhrId);
|
||||
|
||||
pendingRequest.onHeadersReceived = args.onHeadersReceived;
|
||||
pendingRequest.onDone = args.onDone;
|
||||
pendingRequest.onError = args.onError;
|
||||
pendingRequest.onProgress = args.onProgress;
|
||||
|
||||
xhr.send(null);
|
||||
|
||||
return xhrId;
|
||||
},
|
||||
|
||||
onProgress: function NetworkManager_onProgress(xhrId, evt) {
|
||||
var pendingRequest = this.pendingRequests[xhrId];
|
||||
if (!pendingRequest) {
|
||||
// Maybe abortRequest was called...
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingRequest.mozChunked) {
|
||||
var chunk = getArrayBuffer(pendingRequest.xhr);
|
||||
pendingRequest.onProgressiveData(chunk);
|
||||
}
|
||||
|
||||
var onProgress = pendingRequest.onProgress;
|
||||
if (onProgress) {
|
||||
onProgress(evt);
|
||||
}
|
||||
},
|
||||
|
||||
onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
|
||||
var pendingRequest = this.pendingRequests[xhrId];
|
||||
if (!pendingRequest) {
|
||||
// Maybe abortRequest was called...
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = pendingRequest.xhr;
|
||||
if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
|
||||
pendingRequest.onHeadersReceived();
|
||||
delete pendingRequest.onHeadersReceived;
|
||||
}
|
||||
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(xhrId in this.pendingRequests)) {
|
||||
// The XHR request might have been aborted in onHeadersReceived()
|
||||
// callback, in which case we should abort request
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.pendingRequests[xhrId];
|
||||
|
||||
// success status == 0 can be on ftp, file and other protocols
|
||||
if (xhr.status === 0 && this.isHttp) {
|
||||
if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var xhrStatus = xhr.status || OK_RESPONSE;
|
||||
|
||||
// From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
|
||||
// "A server MAY ignore the Range header". This means it's possible to
|
||||
// get a 200 rather than a 206 response from a range request.
|
||||
var ok_response_on_range_request =
|
||||
xhrStatus === OK_RESPONSE &&
|
||||
pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
|
||||
|
||||
if (!ok_response_on_range_request &&
|
||||
xhrStatus !== pendingRequest.expectedStatus) {
|
||||
if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadedRequests[xhrId] = true;
|
||||
|
||||
var chunk = getArrayBuffer(xhr);
|
||||
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
|
||||
var rangeHeader = xhr.getResponseHeader('Content-Range');
|
||||
var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
|
||||
var begin = parseInt(matches[1], 10);
|
||||
pendingRequest.onDone({
|
||||
begin: begin,
|
||||
chunk: chunk
|
||||
});
|
||||
} else if (pendingRequest.onProgressiveData) {
|
||||
pendingRequest.onDone(null);
|
||||
} else if (chunk) {
|
||||
pendingRequest.onDone({
|
||||
begin: 0,
|
||||
chunk: chunk
|
||||
});
|
||||
} else if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
},
|
||||
|
||||
hasPendingRequests: function NetworkManager_hasPendingRequests() {
|
||||
for (var xhrId in this.pendingRequests) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getRequestXhr: function NetworkManager_getXhr(xhrId) {
|
||||
return this.pendingRequests[xhrId].xhr;
|
||||
},
|
||||
|
||||
isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
|
||||
return !!(this.pendingRequests[xhrId].onProgressiveData);
|
||||
},
|
||||
|
||||
isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
|
||||
return xhrId in this.pendingRequests;
|
||||
},
|
||||
|
||||
isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
|
||||
return xhrId in this.loadedRequests;
|
||||
},
|
||||
|
||||
abortAllRequests: function NetworkManager_abortAllRequests() {
|
||||
for (var xhrId in this.pendingRequests) {
|
||||
this.abortRequest(xhrId | 0);
|
||||
}
|
||||
},
|
||||
|
||||
abortRequest: function NetworkManager_abortRequest(xhrId) {
|
||||
var xhr = this.pendingRequests[xhrId].xhr;
|
||||
delete this.pendingRequests[xhrId];
|
||||
xhr.abort();
|
||||
}
|
||||
};
|
||||
|
||||
return NetworkManager;
|
||||
})();
|
||||
|
||||
70
browser/extensions/pdfjs/content/PdfJsTelemetry.jsm
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* Copyright 2013 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* jshint esnext:true, maxlen: 100 */
|
||||
/* globals Components, Services */
|
||||
|
||||
'use strict';
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['PdfJsTelemetry'];
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
this.PdfJsTelemetry = {
|
||||
onViewerIsUsed: function () {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_USED');
|
||||
histogram.add(true);
|
||||
},
|
||||
onFallback: function () {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FALLBACK_SHOWN');
|
||||
histogram.add(true);
|
||||
},
|
||||
onDocumentSize: function (size) {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_SIZE_KB');
|
||||
histogram.add(size / 1024);
|
||||
},
|
||||
onDocumentVersion: function (versionId) {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_VERSION');
|
||||
histogram.add(versionId);
|
||||
},
|
||||
onDocumentGenerator: function (generatorId) {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_GENERATOR');
|
||||
histogram.add(generatorId);
|
||||
},
|
||||
onEmbed: function (isObject) {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_EMBED');
|
||||
histogram.add(isObject);
|
||||
},
|
||||
onFontType: function (fontTypeId) {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FONT_TYPES');
|
||||
histogram.add(fontTypeId);
|
||||
},
|
||||
onForm: function (isAcroform) {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FORM');
|
||||
histogram.add(isAcroform);
|
||||
},
|
||||
onPrint: function () {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_PRINT');
|
||||
histogram.add(true);
|
||||
},
|
||||
onStreamType: function (streamTypeId) {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_STREAM_TYPES');
|
||||
histogram.add(streamTypeId);
|
||||
},
|
||||
onTimeToView: function (ms) {
|
||||
let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_TIME_TO_VIEW_MS');
|
||||
histogram.add(ms);
|
||||
}
|
||||
};
|
||||
1045
browser/extensions/pdfjs/content/PdfStreamConverter.jsm
Normal file
357
browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* jshint esnext:true */
|
||||
/* globals Components, Services, XPCOMUtils */
|
||||
|
||||
'use strict';
|
||||
|
||||
var EXPORTED_SYMBOLS = ['PdfjsChromeUtils'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const PREF_PREFIX = 'pdfjs';
|
||||
const PDF_CONTENT_TYPE = 'application/pdf';
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
var Svc = {};
|
||||
XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
|
||||
'@mozilla.org/mime;1',
|
||||
'nsIMIMEService');
|
||||
|
||||
var DEFAULT_PREFERENCES =
|
||||
{
|
||||
"showPreviousViewOnLoad": true,
|
||||
"defaultZoomValue": "",
|
||||
"sidebarViewOnLoad": 0,
|
||||
"enableHandToolOnLoad": false,
|
||||
"enableWebGL": false,
|
||||
"pdfBugEnabled": false,
|
||||
"disableRange": false,
|
||||
"disableStream": false,
|
||||
"disableAutoFetch": false,
|
||||
"disableFontFace": false,
|
||||
"disableTextLayer": false,
|
||||
"useOnlyCssZoom": false,
|
||||
"externalLinkTarget": 0,
|
||||
"enhanceTextSelection": false,
|
||||
"renderInteractiveForms": false,
|
||||
"disablePageLabels": false
|
||||
}
|
||||
|
||||
|
||||
var PdfjsChromeUtils = {
|
||||
// For security purposes when running remote, we restrict preferences
|
||||
// content can access.
|
||||
_allowedPrefNames: Object.keys(DEFAULT_PREFERENCES),
|
||||
_ppmm: null,
|
||||
_mmg: null,
|
||||
|
||||
/*
|
||||
* Public API
|
||||
*/
|
||||
|
||||
init: function () {
|
||||
this._browsers = new WeakSet();
|
||||
if (!this._ppmm) {
|
||||
// global parent process message manager (PPMM)
|
||||
this._ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1'].
|
||||
getService(Ci.nsIMessageBroadcaster);
|
||||
this._ppmm.addMessageListener('PDFJS:Parent:clearUserPref', this);
|
||||
this._ppmm.addMessageListener('PDFJS:Parent:setIntPref', this);
|
||||
this._ppmm.addMessageListener('PDFJS:Parent:setBoolPref', this);
|
||||
this._ppmm.addMessageListener('PDFJS:Parent:setCharPref', this);
|
||||
this._ppmm.addMessageListener('PDFJS:Parent:setStringPref', this);
|
||||
this._ppmm.addMessageListener('PDFJS:Parent:isDefaultHandlerApp', this);
|
||||
|
||||
// global dom message manager (MMg)
|
||||
this._mmg = Cc['@mozilla.org/globalmessagemanager;1'].
|
||||
getService(Ci.nsIMessageListenerManager);
|
||||
this._mmg.addMessageListener('PDFJS:Parent:displayWarning', this);
|
||||
|
||||
this._mmg.addMessageListener('PDFJS:Parent:addEventListener', this);
|
||||
this._mmg.addMessageListener('PDFJS:Parent:removeEventListener', this);
|
||||
this._mmg.addMessageListener('PDFJS:Parent:updateControlState', this);
|
||||
|
||||
// observer to handle shutdown
|
||||
Services.obs.addObserver(this, 'quit-application', false);
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
if (this._ppmm) {
|
||||
this._ppmm.removeMessageListener('PDFJS:Parent:clearUserPref', this);
|
||||
this._ppmm.removeMessageListener('PDFJS:Parent:setIntPref', this);
|
||||
this._ppmm.removeMessageListener('PDFJS:Parent:setBoolPref', this);
|
||||
this._ppmm.removeMessageListener('PDFJS:Parent:setCharPref', this);
|
||||
this._ppmm.removeMessageListener('PDFJS:Parent:setStringPref', this);
|
||||
this._ppmm.removeMessageListener('PDFJS:Parent:isDefaultHandlerApp',
|
||||
this);
|
||||
|
||||
this._mmg.removeMessageListener('PDFJS:Parent:displayWarning', this);
|
||||
|
||||
this._mmg.removeMessageListener('PDFJS:Parent:addEventListener', this);
|
||||
this._mmg.removeMessageListener('PDFJS:Parent:removeEventListener', this);
|
||||
this._mmg.removeMessageListener('PDFJS:Parent:updateControlState', this);
|
||||
|
||||
Services.obs.removeObserver(this, 'quit-application', false);
|
||||
|
||||
this._mmg = null;
|
||||
this._ppmm = null;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Called by the main module when preference changes are picked up
|
||||
* in the parent process. Observers don't propagate so we need to
|
||||
* instruct the child to refresh its configuration and (possibly)
|
||||
* the module's registration.
|
||||
*/
|
||||
notifyChildOfSettingsChange: function () {
|
||||
if (Services.appinfo.processType ===
|
||||
Services.appinfo.PROCESS_TYPE_DEFAULT && this._ppmm) {
|
||||
// XXX kinda bad, we want to get the parent process mm associated
|
||||
// with the content process. _ppmm is currently the global process
|
||||
// manager, which means this is going to fire to every child process
|
||||
// we have open. Unfortunately I can't find a way to get at that
|
||||
// process specific mm from js.
|
||||
this._ppmm.broadcastAsyncMessage('PDFJS:Child:refreshSettings', {});
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic === 'quit-application') {
|
||||
this.uninit();
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function (aMsg) {
|
||||
switch (aMsg.name) {
|
||||
case 'PDFJS:Parent:clearUserPref':
|
||||
this._clearUserPref(aMsg.data.name);
|
||||
break;
|
||||
case 'PDFJS:Parent:setIntPref':
|
||||
this._setIntPref(aMsg.data.name, aMsg.data.value);
|
||||
break;
|
||||
case 'PDFJS:Parent:setBoolPref':
|
||||
this._setBoolPref(aMsg.data.name, aMsg.data.value);
|
||||
break;
|
||||
case 'PDFJS:Parent:setCharPref':
|
||||
this._setCharPref(aMsg.data.name, aMsg.data.value);
|
||||
break;
|
||||
case 'PDFJS:Parent:setStringPref':
|
||||
this._setStringPref(aMsg.data.name, aMsg.data.value);
|
||||
break;
|
||||
case 'PDFJS:Parent:isDefaultHandlerApp':
|
||||
return this.isDefaultHandlerApp();
|
||||
case 'PDFJS:Parent:displayWarning':
|
||||
this._displayWarning(aMsg);
|
||||
break;
|
||||
|
||||
|
||||
case 'PDFJS:Parent:updateControlState':
|
||||
return this._updateControlState(aMsg);
|
||||
case 'PDFJS:Parent:addEventListener':
|
||||
return this._addEventListener(aMsg);
|
||||
case 'PDFJS:Parent:removeEventListener':
|
||||
return this._removeEventListener(aMsg);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Internal
|
||||
*/
|
||||
|
||||
_findbarFromMessage: function(aMsg) {
|
||||
let browser = aMsg.target;
|
||||
let tabbrowser = browser.getTabBrowser();
|
||||
let tab = tabbrowser.getTabForBrowser(browser);
|
||||
return tabbrowser.getFindBar(tab);
|
||||
},
|
||||
|
||||
_updateControlState: function (aMsg) {
|
||||
let data = aMsg.data;
|
||||
this._findbarFromMessage(aMsg)
|
||||
.updateControlState(data.result, data.findPrevious);
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
// To avoid forwarding the message as a CPOW, create a structured cloneable
|
||||
// version of the event for both performance, and ease of usage, reasons.
|
||||
let type = aEvent.type;
|
||||
let detail = {
|
||||
query: aEvent.detail.query,
|
||||
caseSensitive: aEvent.detail.caseSensitive,
|
||||
highlightAll: aEvent.detail.highlightAll,
|
||||
findPrevious: aEvent.detail.findPrevious
|
||||
};
|
||||
|
||||
let browser = aEvent.currentTarget.browser;
|
||||
if (!this._browsers.has(browser)) {
|
||||
throw new Error('FindEventManager was not bound ' +
|
||||
'for the current browser.');
|
||||
}
|
||||
// Only forward the events if the current browser is a registered browser.
|
||||
let mm = browser.messageManager;
|
||||
mm.sendAsyncMessage('PDFJS:Child:handleEvent',
|
||||
{ type: type, detail: detail });
|
||||
aEvent.preventDefault();
|
||||
},
|
||||
|
||||
_types: ['find',
|
||||
'findagain',
|
||||
'findhighlightallchange',
|
||||
'findcasesensitivitychange'],
|
||||
|
||||
_addEventListener: function (aMsg) {
|
||||
let browser = aMsg.target;
|
||||
if (this._browsers.has(browser)) {
|
||||
throw new Error('FindEventManager was bound 2nd time ' +
|
||||
'without unbinding it first.');
|
||||
}
|
||||
|
||||
// Since this jsm is global, we need to store all the browsers
|
||||
// we have to forward the messages for.
|
||||
this._browsers.add(browser);
|
||||
|
||||
// And we need to start listening to find events.
|
||||
for (var i = 0; i < this._types.length; i++) {
|
||||
var type = this._types[i];
|
||||
this._findbarFromMessage(aMsg)
|
||||
.addEventListener(type, this, true);
|
||||
}
|
||||
},
|
||||
|
||||
_removeEventListener: function (aMsg) {
|
||||
let browser = aMsg.target;
|
||||
if (!this._browsers.has(browser)) {
|
||||
throw new Error('FindEventManager was unbound without binding it first.');
|
||||
}
|
||||
|
||||
this._browsers.delete(browser);
|
||||
|
||||
// No reason to listen to find events any longer.
|
||||
for (var i = 0; i < this._types.length; i++) {
|
||||
var type = this._types[i];
|
||||
this._findbarFromMessage(aMsg)
|
||||
.removeEventListener(type, this, true);
|
||||
}
|
||||
},
|
||||
|
||||
_ensurePreferenceAllowed: function (aPrefName) {
|
||||
let unPrefixedName = aPrefName.split(PREF_PREFIX + '.');
|
||||
if (unPrefixedName[0] !== '' ||
|
||||
this._allowedPrefNames.indexOf(unPrefixedName[1]) === -1) {
|
||||
let msg = '"' + aPrefName + '" ' +
|
||||
'can\'t be accessed from content. See PdfjsChromeUtils.';
|
||||
throw new Error(msg);
|
||||
}
|
||||
},
|
||||
|
||||
_clearUserPref: function (aPrefName) {
|
||||
this._ensurePreferenceAllowed(aPrefName);
|
||||
Services.prefs.clearUserPref(aPrefName);
|
||||
},
|
||||
|
||||
_setIntPref: function (aPrefName, aPrefValue) {
|
||||
this._ensurePreferenceAllowed(aPrefName);
|
||||
Services.prefs.setIntPref(aPrefName, aPrefValue);
|
||||
},
|
||||
|
||||
_setBoolPref: function (aPrefName, aPrefValue) {
|
||||
this._ensurePreferenceAllowed(aPrefName);
|
||||
Services.prefs.setBoolPref(aPrefName, aPrefValue);
|
||||
},
|
||||
|
||||
_setCharPref: function (aPrefName, aPrefValue) {
|
||||
this._ensurePreferenceAllowed(aPrefName);
|
||||
Services.prefs.setCharPref(aPrefName, aPrefValue);
|
||||
},
|
||||
|
||||
_setStringPref: function (aPrefName, aPrefValue) {
|
||||
this._ensurePreferenceAllowed(aPrefName);
|
||||
let str = Cc['@mozilla.org/supports-string;1']
|
||||
.createInstance(Ci.nsISupportsString);
|
||||
str.data = aPrefValue;
|
||||
Services.prefs.setComplexValue(aPrefName, Ci.nsISupportsString, str);
|
||||
},
|
||||
|
||||
/*
|
||||
* Svc.mime doesn't have profile information in the child, so
|
||||
* we bounce this pdfjs enabled configuration check over to the
|
||||
* parent.
|
||||
*/
|
||||
isDefaultHandlerApp: function () {
|
||||
var handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf');
|
||||
return (!handlerInfo.alwaysAskBeforeHandling &&
|
||||
handlerInfo.preferredAction === Ci.nsIHandlerInfo.handleInternally);
|
||||
},
|
||||
|
||||
/*
|
||||
* Display a notification warning when the renderer isn't sure
|
||||
* a pdf displayed correctly.
|
||||
*/
|
||||
_displayWarning: function (aMsg) {
|
||||
let data = aMsg.data;
|
||||
let browser = aMsg.target;
|
||||
|
||||
let tabbrowser = browser.getTabBrowser();
|
||||
let notificationBox = tabbrowser.getNotificationBox(browser);
|
||||
|
||||
// Flag so we don't send the message twice, since if the user clicks
|
||||
// "open with different viewer" both the button callback and
|
||||
// eventCallback will be called.
|
||||
let messageSent = false;
|
||||
function sendMessage(download) {
|
||||
let mm = browser.messageManager;
|
||||
mm.sendAsyncMessage('PDFJS:Child:fallbackDownload',
|
||||
{ download: download });
|
||||
}
|
||||
let buttons = [{
|
||||
label: data.label,
|
||||
accessKey: data.accessKey,
|
||||
callback: function() {
|
||||
messageSent = true;
|
||||
sendMessage(true);
|
||||
}
|
||||
}];
|
||||
notificationBox.appendNotification(data.message, 'pdfjs-fallback', null,
|
||||
notificationBox.PRIORITY_INFO_LOW,
|
||||
buttons,
|
||||
function eventsCallback(eventType) {
|
||||
// Currently there is only one event "removed" but if there are any other
|
||||
// added in the future we still only care about removed at the moment.
|
||||
if (eventType !== 'removed') {
|
||||
return;
|
||||
}
|
||||
// Don't send a response again if we already responded when the button was
|
||||
// clicked.
|
||||
if (messageSent) {
|
||||
return;
|
||||
}
|
||||
sendMessage(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
150
browser/extensions/pdfjs/content/PdfjsContentUtils.jsm
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* jshint esnext:true */
|
||||
/* globals Components, Services, XPCOMUtils */
|
||||
|
||||
'use strict';
|
||||
|
||||
var EXPORTED_SYMBOLS = ['PdfjsContentUtils'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
var PdfjsContentUtils = {
|
||||
_mm: null,
|
||||
|
||||
/*
|
||||
* Public API
|
||||
*/
|
||||
|
||||
get isRemote() {
|
||||
return (Services.appinfo.processType ===
|
||||
Services.appinfo.PROCESS_TYPE_CONTENT);
|
||||
},
|
||||
|
||||
init: function () {
|
||||
// child *process* mm, or when loaded into the parent for in-content
|
||||
// support the psuedo child process mm 'child PPMM'.
|
||||
if (!this._mm) {
|
||||
this._mm = Cc['@mozilla.org/childprocessmessagemanager;1'].
|
||||
getService(Ci.nsISyncMessageSender);
|
||||
this._mm.addMessageListener('PDFJS:Child:refreshSettings', this);
|
||||
Services.obs.addObserver(this, 'quit-application', false);
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
if (this._mm) {
|
||||
this._mm.removeMessageListener('PDFJS:Child:refreshSettings', this);
|
||||
Services.obs.removeObserver(this, 'quit-application');
|
||||
}
|
||||
this._mm = null;
|
||||
},
|
||||
|
||||
/*
|
||||
* prefs utilities - the child does not have write access to prefs.
|
||||
* note, the pref names here are cross-checked against a list of
|
||||
* approved pdfjs prefs in chrome utils.
|
||||
*/
|
||||
|
||||
clearUserPref: function (aPrefName) {
|
||||
this._mm.sendSyncMessage('PDFJS:Parent:clearUserPref', {
|
||||
name: aPrefName
|
||||
});
|
||||
},
|
||||
|
||||
setIntPref: function (aPrefName, aPrefValue) {
|
||||
this._mm.sendSyncMessage('PDFJS:Parent:setIntPref', {
|
||||
name: aPrefName,
|
||||
value: aPrefValue
|
||||
});
|
||||
},
|
||||
|
||||
setBoolPref: function (aPrefName, aPrefValue) {
|
||||
this._mm.sendSyncMessage('PDFJS:Parent:setBoolPref', {
|
||||
name: aPrefName,
|
||||
value: aPrefValue
|
||||
});
|
||||
},
|
||||
|
||||
setCharPref: function (aPrefName, aPrefValue) {
|
||||
this._mm.sendSyncMessage('PDFJS:Parent:setCharPref', {
|
||||
name: aPrefName,
|
||||
value: aPrefValue
|
||||
});
|
||||
},
|
||||
|
||||
setStringPref: function (aPrefName, aPrefValue) {
|
||||
this._mm.sendSyncMessage('PDFJS:Parent:setStringPref', {
|
||||
name: aPrefName,
|
||||
value: aPrefValue
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Forwards default app query to the parent where we check various
|
||||
* handler app settings only available in the parent process.
|
||||
*/
|
||||
isDefaultHandlerApp: function () {
|
||||
return this._mm.sendSyncMessage('PDFJS:Parent:isDefaultHandlerApp')[0];
|
||||
},
|
||||
|
||||
/*
|
||||
* Request the display of a notification warning in the associated window
|
||||
* when the renderer isn't sure a pdf displayed correctly.
|
||||
*/
|
||||
displayWarning: function (aWindow, aMessage, aLabel, accessKey) {
|
||||
// the child's dom frame mm associated with the window.
|
||||
let winmm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
||||
winmm.sendAsyncMessage('PDFJS:Parent:displayWarning', {
|
||||
message: aMessage,
|
||||
label: aLabel,
|
||||
accessKey: accessKey
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic === 'quit-application') {
|
||||
this.uninit();
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function (aMsg) {
|
||||
switch (aMsg.name) {
|
||||
case 'PDFJS:Child:refreshSettings':
|
||||
// Only react to this if we are remote.
|
||||
if (Services.appinfo.processType ===
|
||||
Services.appinfo.PROCESS_TYPE_CONTENT) {
|
||||
let jsm = 'resource://pdf.js/PdfJs.jsm';
|
||||
let pdfjs = Components.utils.import(jsm, {}).PdfJs;
|
||||
pdfjs.updateRegistration();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
8387
browser/extensions/pdfjs/content/build/pdf.js
Normal file
52491
browser/extensions/pdfjs/content/build/pdf.worker.js
vendored
Normal file
614
browser/extensions/pdfjs/content/network.js
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// NOTE: Be careful what goes in this file, as it is also used from the context
|
||||
// of the addon. So using warn/error in here will break the addon.
|
||||
|
||||
'use strict';
|
||||
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('pdfjs/core/network', ['exports', 'pdfjs/shared/util',
|
||||
'pdfjs/core/worker'], factory);
|
||||
} else if (typeof exports !== 'undefined') {
|
||||
factory(exports, require('../shared/util.js'), require('./worker.js'));
|
||||
} else {
|
||||
factory((root.pdfjsCoreNetwork = {}), root.pdfjsSharedUtil,
|
||||
root.pdfjsCoreWorker);
|
||||
}
|
||||
}(this, function (exports, sharedUtil, coreWorker) {
|
||||
if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
|
||||
throw new Error('Module "pdfjs/core/network" shall not ' +
|
||||
'be used with FIREFOX or MOZCENTRAL build.');
|
||||
}
|
||||
|
||||
var OK_RESPONSE = 200;
|
||||
var PARTIAL_CONTENT_RESPONSE = 206;
|
||||
|
||||
function NetworkManager(url, args) {
|
||||
this.url = url;
|
||||
args = args || {};
|
||||
this.isHttp = /^https?:/i.test(url);
|
||||
this.httpHeaders = (this.isHttp && args.httpHeaders) || {};
|
||||
this.withCredentials = args.withCredentials || false;
|
||||
this.getXhr = args.getXhr ||
|
||||
function NetworkManager_getXhr() {
|
||||
return new XMLHttpRequest();
|
||||
};
|
||||
|
||||
this.currXhrId = 0;
|
||||
this.pendingRequests = Object.create(null);
|
||||
this.loadedRequests = Object.create(null);
|
||||
}
|
||||
|
||||
function getArrayBuffer(xhr) {
|
||||
var data = xhr.response;
|
||||
if (typeof data !== 'string') {
|
||||
return data;
|
||||
}
|
||||
var length = data.length;
|
||||
var array = new Uint8Array(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
array[i] = data.charCodeAt(i) & 0xFF;
|
||||
}
|
||||
return array.buffer;
|
||||
}
|
||||
|
||||
var supportsMozChunked =
|
||||
typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME') ? false :
|
||||
(function supportsMozChunkedClosure() {
|
||||
try {
|
||||
var x = new XMLHttpRequest();
|
||||
// Firefox 37- required .open() to be called before setting responseType.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=707484
|
||||
// Even though the URL is not visited, .open() could fail if the URL is
|
||||
// blocked, e.g. via the connect-src CSP directive or the NoScript addon.
|
||||
// When this error occurs, this feature detection method will mistakenly
|
||||
// report that moz-chunked-arraybuffer is not supported in Firefox 37-.
|
||||
x.open('GET', 'https://example.com');
|
||||
x.responseType = 'moz-chunked-arraybuffer';
|
||||
return x.responseType === 'moz-chunked-arraybuffer';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
NetworkManager.prototype = {
|
||||
requestRange: function NetworkManager_requestRange(begin, end, listeners) {
|
||||
var args = {
|
||||
begin: begin,
|
||||
end: end
|
||||
};
|
||||
for (var prop in listeners) {
|
||||
args[prop] = listeners[prop];
|
||||
}
|
||||
return this.request(args);
|
||||
},
|
||||
|
||||
requestFull: function NetworkManager_requestFull(listeners) {
|
||||
return this.request(listeners);
|
||||
},
|
||||
|
||||
request: function NetworkManager_request(args) {
|
||||
var xhr = this.getXhr();
|
||||
var xhrId = this.currXhrId++;
|
||||
var pendingRequest = this.pendingRequests[xhrId] = {
|
||||
xhr: xhr
|
||||
};
|
||||
|
||||
xhr.open('GET', this.url);
|
||||
xhr.withCredentials = this.withCredentials;
|
||||
for (var property in this.httpHeaders) {
|
||||
var value = this.httpHeaders[property];
|
||||
if (typeof value === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
xhr.setRequestHeader(property, value);
|
||||
}
|
||||
if (this.isHttp && 'begin' in args && 'end' in args) {
|
||||
var rangeStr = args.begin + '-' + (args.end - 1);
|
||||
xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
|
||||
pendingRequest.expectedStatus = 206;
|
||||
} else {
|
||||
pendingRequest.expectedStatus = 200;
|
||||
}
|
||||
|
||||
var useMozChunkedLoading = supportsMozChunked && !!args.onProgressiveData;
|
||||
if (useMozChunkedLoading) {
|
||||
xhr.responseType = 'moz-chunked-arraybuffer';
|
||||
pendingRequest.onProgressiveData = args.onProgressiveData;
|
||||
pendingRequest.mozChunked = true;
|
||||
} else {
|
||||
xhr.responseType = 'arraybuffer';
|
||||
}
|
||||
|
||||
if (args.onError) {
|
||||
xhr.onerror = function(evt) {
|
||||
args.onError(xhr.status);
|
||||
};
|
||||
}
|
||||
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
|
||||
xhr.onprogress = this.onProgress.bind(this, xhrId);
|
||||
|
||||
pendingRequest.onHeadersReceived = args.onHeadersReceived;
|
||||
pendingRequest.onDone = args.onDone;
|
||||
pendingRequest.onError = args.onError;
|
||||
pendingRequest.onProgress = args.onProgress;
|
||||
|
||||
xhr.send(null);
|
||||
|
||||
return xhrId;
|
||||
},
|
||||
|
||||
onProgress: function NetworkManager_onProgress(xhrId, evt) {
|
||||
var pendingRequest = this.pendingRequests[xhrId];
|
||||
if (!pendingRequest) {
|
||||
// Maybe abortRequest was called...
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingRequest.mozChunked) {
|
||||
var chunk = getArrayBuffer(pendingRequest.xhr);
|
||||
pendingRequest.onProgressiveData(chunk);
|
||||
}
|
||||
|
||||
var onProgress = pendingRequest.onProgress;
|
||||
if (onProgress) {
|
||||
onProgress(evt);
|
||||
}
|
||||
},
|
||||
|
||||
onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
|
||||
var pendingRequest = this.pendingRequests[xhrId];
|
||||
if (!pendingRequest) {
|
||||
// Maybe abortRequest was called...
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = pendingRequest.xhr;
|
||||
if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
|
||||
pendingRequest.onHeadersReceived();
|
||||
delete pendingRequest.onHeadersReceived;
|
||||
}
|
||||
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(xhrId in this.pendingRequests)) {
|
||||
// The XHR request might have been aborted in onHeadersReceived()
|
||||
// callback, in which case we should abort request
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.pendingRequests[xhrId];
|
||||
|
||||
// success status == 0 can be on ftp, file and other protocols
|
||||
if (xhr.status === 0 && this.isHttp) {
|
||||
if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var xhrStatus = xhr.status || OK_RESPONSE;
|
||||
|
||||
// From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
|
||||
// "A server MAY ignore the Range header". This means it's possible to
|
||||
// get a 200 rather than a 206 response from a range request.
|
||||
var ok_response_on_range_request =
|
||||
xhrStatus === OK_RESPONSE &&
|
||||
pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
|
||||
|
||||
if (!ok_response_on_range_request &&
|
||||
xhrStatus !== pendingRequest.expectedStatus) {
|
||||
if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadedRequests[xhrId] = true;
|
||||
|
||||
var chunk = getArrayBuffer(xhr);
|
||||
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
|
||||
var rangeHeader = xhr.getResponseHeader('Content-Range');
|
||||
var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
|
||||
var begin = parseInt(matches[1], 10);
|
||||
pendingRequest.onDone({
|
||||
begin: begin,
|
||||
chunk: chunk
|
||||
});
|
||||
} else if (pendingRequest.onProgressiveData) {
|
||||
pendingRequest.onDone(null);
|
||||
} else if (chunk) {
|
||||
pendingRequest.onDone({
|
||||
begin: 0,
|
||||
chunk: chunk
|
||||
});
|
||||
} else if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
},
|
||||
|
||||
hasPendingRequests: function NetworkManager_hasPendingRequests() {
|
||||
for (var xhrId in this.pendingRequests) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getRequestXhr: function NetworkManager_getXhr(xhrId) {
|
||||
return this.pendingRequests[xhrId].xhr;
|
||||
},
|
||||
|
||||
isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
|
||||
return !!(this.pendingRequests[xhrId].onProgressiveData);
|
||||
},
|
||||
|
||||
isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
|
||||
return xhrId in this.pendingRequests;
|
||||
},
|
||||
|
||||
isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
|
||||
return xhrId in this.loadedRequests;
|
||||
},
|
||||
|
||||
abortAllRequests: function NetworkManager_abortAllRequests() {
|
||||
for (var xhrId in this.pendingRequests) {
|
||||
this.abortRequest(xhrId | 0);
|
||||
}
|
||||
},
|
||||
|
||||
abortRequest: function NetworkManager_abortRequest(xhrId) {
|
||||
var xhr = this.pendingRequests[xhrId].xhr;
|
||||
delete this.pendingRequests[xhrId];
|
||||
xhr.abort();
|
||||
}
|
||||
};
|
||||
|
||||
var assert = sharedUtil.assert;
|
||||
var createPromiseCapability = sharedUtil.createPromiseCapability;
|
||||
var isInt = sharedUtil.isInt;
|
||||
var MissingPDFException = sharedUtil.MissingPDFException;
|
||||
var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
|
||||
|
||||
/** @implements {IPDFStream} */
|
||||
function PDFNetworkStream(options) {
|
||||
this._options = options;
|
||||
var source = options.source;
|
||||
this._manager = new NetworkManager(source.url, {
|
||||
httpHeaders: source.httpHeaders,
|
||||
withCredentials: source.withCredentials
|
||||
});
|
||||
this._rangeChunkSize = source.rangeChunkSize;
|
||||
this._fullRequestReader = null;
|
||||
this._rangeRequestReaders = [];
|
||||
}
|
||||
|
||||
PDFNetworkStream.prototype = {
|
||||
_onRangeRequestReaderClosed:
|
||||
function PDFNetworkStream_onRangeRequestReaderClosed(reader) {
|
||||
var i = this._rangeRequestReaders.indexOf(reader);
|
||||
if (i >= 0) {
|
||||
this._rangeRequestReaders.splice(i, 1);
|
||||
}
|
||||
},
|
||||
|
||||
getFullReader: function PDFNetworkStream_getFullReader() {
|
||||
assert(!this._fullRequestReader);
|
||||
this._fullRequestReader =
|
||||
new PDFNetworkStreamFullRequestReader(this._manager, this._options);
|
||||
return this._fullRequestReader;
|
||||
},
|
||||
|
||||
getRangeReader: function PDFNetworkStream_getRangeReader(begin, end) {
|
||||
var reader = new PDFNetworkStreamRangeRequestReader(this._manager,
|
||||
begin, end);
|
||||
reader.onClosed = this._onRangeRequestReaderClosed.bind(this);
|
||||
this._rangeRequestReaders.push(reader);
|
||||
return reader;
|
||||
},
|
||||
|
||||
cancelAllRequests: function PDFNetworkStream_cancelAllRequests(reason) {
|
||||
if (this._fullRequestReader) {
|
||||
this._fullRequestReader.cancel(reason);
|
||||
}
|
||||
var readers = this._rangeRequestReaders.slice(0);
|
||||
readers.forEach(function (reader) {
|
||||
reader.cancel(reason);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** @implements {IPDFStreamReader} */
|
||||
function PDFNetworkStreamFullRequestReader(manager, options) {
|
||||
this._manager = manager;
|
||||
|
||||
var source = options.source;
|
||||
var args = {
|
||||
onHeadersReceived: this._onHeadersReceived.bind(this),
|
||||
onProgressiveData: source.disableStream ? null :
|
||||
this._onProgressiveData.bind(this),
|
||||
onDone: this._onDone.bind(this),
|
||||
onError: this._onError.bind(this),
|
||||
onProgress: this._onProgress.bind(this)
|
||||
};
|
||||
this._url = source.url;
|
||||
this._fullRequestId = manager.requestFull(args);
|
||||
this._headersReceivedCapability = createPromiseCapability();
|
||||
this._disableRange = options.disableRange || false;
|
||||
this._contentLength = source.length; // optional
|
||||
this._rangeChunkSize = source.rangeChunkSize;
|
||||
if (!this._rangeChunkSize && !this._disableRange) {
|
||||
this._disableRange = true;
|
||||
}
|
||||
|
||||
this._isStreamingSupported = false;
|
||||
this._isRangeSupported = false;
|
||||
|
||||
this._cachedChunks = [];
|
||||
this._requests = [];
|
||||
this._done = false;
|
||||
this._storedError = undefined;
|
||||
|
||||
this.onProgress = null;
|
||||
}
|
||||
|
||||
PDFNetworkStreamFullRequestReader.prototype = {
|
||||
_validateRangeRequestCapabilities: function
|
||||
PDFNetworkStreamFullRequestReader_validateRangeRequestCapabilities() {
|
||||
|
||||
if (this._disableRange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var networkManager = this._manager;
|
||||
var fullRequestXhrId = this._fullRequestId;
|
||||
var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId);
|
||||
if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var contentEncoding =
|
||||
fullRequestXhr.getResponseHeader('Content-Encoding') || 'identity';
|
||||
if (contentEncoding !== 'identity') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = fullRequestXhr.getResponseHeader('Content-Length');
|
||||
length = parseInt(length, 10);
|
||||
if (!isInt(length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._contentLength = length; // setting right content length
|
||||
|
||||
if (length <= 2 * this._rangeChunkSize) {
|
||||
// The file size is smaller than the size of two chunks, so it does
|
||||
// not make any sense to abort the request and retry with a range
|
||||
// request.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_onHeadersReceived:
|
||||
function PDFNetworkStreamFullRequestReader_onHeadersReceived() {
|
||||
|
||||
if (this._validateRangeRequestCapabilities()) {
|
||||
this._isRangeSupported = true;
|
||||
}
|
||||
|
||||
var networkManager = this._manager;
|
||||
var fullRequestXhrId = this._fullRequestId;
|
||||
if (networkManager.isStreamingRequest(fullRequestXhrId)) {
|
||||
// We can continue fetching when progressive loading is enabled,
|
||||
// and we don't need the autoFetch feature.
|
||||
this._isStreamingSupported = true;
|
||||
} else if (this._isRangeSupported) {
|
||||
// NOTE: by cancelling the full request, and then issuing range
|
||||
// requests, there will be an issue for sites where you can only
|
||||
// request the pdf once. However, if this is the case, then the
|
||||
// server should not be returning that it can support range
|
||||
// requests.
|
||||
networkManager.abortRequest(fullRequestXhrId);
|
||||
}
|
||||
|
||||
this._headersReceivedCapability.resolve();
|
||||
},
|
||||
|
||||
_onProgressiveData:
|
||||
function PDFNetworkStreamFullRequestReader_onProgressiveData(chunk) {
|
||||
if (this._requests.length > 0) {
|
||||
var requestCapability = this._requests.shift();
|
||||
requestCapability.resolve({value: chunk, done: false});
|
||||
} else {
|
||||
this._cachedChunks.push(chunk);
|
||||
}
|
||||
},
|
||||
|
||||
_onDone: function PDFNetworkStreamFullRequestReader_onDone(args) {
|
||||
if (args) {
|
||||
this._onProgressiveData(args.chunk);
|
||||
}
|
||||
this._done = true;
|
||||
if (this._cachedChunks.length > 0) {
|
||||
return;
|
||||
}
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({value: undefined, done: true});
|
||||
});
|
||||
this._requests = [];
|
||||
},
|
||||
|
||||
_onError: function PDFNetworkStreamFullRequestReader_onError(status) {
|
||||
var url = this._url;
|
||||
var exception;
|
||||
if (status === 404 || status === 0 && /^file:/.test(url)) {
|
||||
exception = new MissingPDFException('Missing PDF "' + url + '".');
|
||||
} else {
|
||||
exception = new UnexpectedResponseException(
|
||||
'Unexpected server response (' + status +
|
||||
') while retrieving PDF "' + url + '".', status);
|
||||
}
|
||||
this._storedError = exception;
|
||||
this._headersReceivedCapability.reject(exception);
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.reject(exception);
|
||||
});
|
||||
this._requests = [];
|
||||
this._cachedChunks = [];
|
||||
},
|
||||
|
||||
_onProgress: function PDFNetworkStreamFullRequestReader_onProgress(data) {
|
||||
if (this.onProgress) {
|
||||
this.onProgress({
|
||||
loaded: data.loaded,
|
||||
total: data.lengthComputable ? data.total : this._contentLength
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get isRangeSupported() {
|
||||
return this._isRangeSupported;
|
||||
},
|
||||
|
||||
get isStreamingSupported() {
|
||||
return this._isStreamingSupported;
|
||||
},
|
||||
|
||||
get contentLength() {
|
||||
return this._contentLength;
|
||||
},
|
||||
|
||||
get headersReady() {
|
||||
return this._headersReceivedCapability.promise;
|
||||
},
|
||||
|
||||
read: function PDFNetworkStreamFullRequestReader_read() {
|
||||
if (this._storedError) {
|
||||
return Promise.reject(this._storedError);
|
||||
}
|
||||
if (this._cachedChunks.length > 0) {
|
||||
var chunk = this._cachedChunks.shift();
|
||||
return Promise.resolve(chunk);
|
||||
}
|
||||
if (this._done) {
|
||||
return Promise.resolve({value: undefined, done: true});
|
||||
}
|
||||
var requestCapability = createPromiseCapability();
|
||||
this._requests.push(requestCapability);
|
||||
return requestCapability.promise;
|
||||
},
|
||||
|
||||
cancel: function PDFNetworkStreamFullRequestReader_cancel(reason) {
|
||||
this._done = true;
|
||||
this._headersReceivedCapability.reject(reason);
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({value: undefined, done: true});
|
||||
});
|
||||
this._requests = [];
|
||||
if (this._manager.isPendingRequest(this._fullRequestId)) {
|
||||
this._manager.abortRequest(this._fullRequestId);
|
||||
}
|
||||
this._fullRequestReader = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** @implements {IPDFStreamRangeReader} */
|
||||
function PDFNetworkStreamRangeRequestReader(manager, begin, end) {
|
||||
this._manager = manager;
|
||||
var args = {
|
||||
onDone: this._onDone.bind(this),
|
||||
onProgress: this._onProgress.bind(this)
|
||||
};
|
||||
this._requestId = manager.requestRange(begin, end, args);
|
||||
this._requests = [];
|
||||
this._queuedChunk = null;
|
||||
this._done = false;
|
||||
|
||||
this.onProgress = null;
|
||||
this.onClosed = null;
|
||||
}
|
||||
|
||||
PDFNetworkStreamRangeRequestReader.prototype = {
|
||||
_close: function PDFNetworkStreamRangeRequestReader_close() {
|
||||
if (this.onClosed) {
|
||||
this.onClosed(this);
|
||||
}
|
||||
},
|
||||
|
||||
_onDone: function PDFNetworkStreamRangeRequestReader_onDone(data) {
|
||||
var chunk = data.chunk;
|
||||
if (this._requests.length > 0) {
|
||||
var requestCapability = this._requests.shift();
|
||||
requestCapability.resolve({value: chunk, done: false});
|
||||
} else {
|
||||
this._queuedChunk = chunk;
|
||||
}
|
||||
this._done = true;
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({value: undefined, done: true});
|
||||
});
|
||||
this._requests = [];
|
||||
this._close();
|
||||
},
|
||||
|
||||
_onProgress: function PDFNetworkStreamRangeRequestReader_onProgress(evt) {
|
||||
if (!this.isStreamingSupported && this.onProgress) {
|
||||
this.onProgress({
|
||||
loaded: evt.loaded
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get isStreamingSupported() {
|
||||
return false; // TODO allow progressive range bytes loading
|
||||
},
|
||||
|
||||
read: function PDFNetworkStreamRangeRequestReader_read() {
|
||||
if (this._queuedChunk !== null) {
|
||||
var chunk = this._queuedChunk;
|
||||
this._queuedChunk = null;
|
||||
return Promise.resolve({value: chunk, done: false});
|
||||
}
|
||||
if (this._done) {
|
||||
return Promise.resolve({value: undefined, done: true});
|
||||
}
|
||||
var requestCapability = createPromiseCapability();
|
||||
this._requests.push(requestCapability);
|
||||
return requestCapability.promise;
|
||||
},
|
||||
|
||||
cancel: function PDFNetworkStreamRangeRequestReader_cancel(reason) {
|
||||
this._done = true;
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({value: undefined, done: true});
|
||||
});
|
||||
this._requests = [];
|
||||
if (this._manager.isPendingRequest(this._requestId)) {
|
||||
this._manager.abortRequest(this._requestId);
|
||||
}
|
||||
this._close();
|
||||
}
|
||||
};
|
||||
|
||||
coreWorker.setPDFNetworkStreamClass(PDFNetworkStream);
|
||||
|
||||
exports.PDFNetworkStream = PDFNetworkStream;
|
||||
exports.NetworkManager = NetworkManager;
|
||||
}));
|
||||
|
||||
36
browser/extensions/pdfjs/content/pdfjschildbootstrap.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* jshint esnext:true */
|
||||
/* globals Components, PdfjsContentUtils, PdfJs, Services */
|
||||
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* pdfjschildbootstrap.js loads into the content process to take care of
|
||||
* initializing our built-in version of pdfjs when running remote.
|
||||
*/
|
||||
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
Components.utils.import('resource://pdf.js/PdfJs.jsm');
|
||||
Components.utils.import('resource://pdf.js/PdfjsContentUtils.jsm');
|
||||
|
||||
// init content utils shim pdfjs will use to access privileged apis.
|
||||
PdfjsContentUtils.init();
|
||||
|
||||
if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
|
||||
// register various pdfjs factories that hook us into content loading.
|
||||
PdfJs.updateRegistration();
|
||||
}
|
||||
|
||||