зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to m-i
This commit is contained in:
Коммит
f3779e27ad
|
@ -11,12 +11,15 @@
|
|||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
||||
DIRS += ["source/modules/system"]
|
||||
|
||||
EXTRA_JS_MODULES.sdk += [
|
||||
'source/app-extension/bootstrap.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.sdk.system += [
|
||||
'source/modules/system/Startup.js',
|
||||
'source/modules/system/XulApp.js',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
|
||||
EXTRA_JS_MODULES.commonjs.method.test += [
|
||||
'source/lib/method/test/browser.js',
|
||||
|
|
|
@ -14,74 +14,25 @@ module.metadata = {
|
|||
require('chrome') // Otherwise CFX will complain about Components
|
||||
require('toolkit/loader') // Otherwise CFX will stip out loader.js
|
||||
require('sdk/addon/runner') // Otherwise CFX will stip out addon/runner.js
|
||||
require('sdk/system/xul-app') // Otherwise CFX will stip out sdk/system/xul-app
|
||||
*/
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const {
|
||||
incompatibility
|
||||
} = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {}).XulApp;
|
||||
|
||||
// `loadSandbox` is exposed by bootstrap.js
|
||||
const loaderURI = module.uri.replace("sdk/loader/cuddlefish.js",
|
||||
"toolkit/loader.js");
|
||||
const xulappURI = module.uri.replace("loader/cuddlefish.js",
|
||||
"system/xul-app.js");
|
||||
// We need to keep a reference to the sandbox in order to unload it in
|
||||
// bootstrap.js
|
||||
|
||||
const loaderSandbox = loadSandbox(loaderURI);
|
||||
const loaderModule = loaderSandbox.exports;
|
||||
|
||||
const xulappSandbox = loadSandbox(xulappURI);
|
||||
const xulappModule = xulappSandbox.exports;
|
||||
|
||||
const { override, load } = loaderModule;
|
||||
|
||||
/**
|
||||
* Ensure the current application satisfied the requirements specified in the
|
||||
* module given. If not, an exception related to the incompatibility is
|
||||
* returned; `null` otherwise.
|
||||
*
|
||||
* @param {Object} module
|
||||
* The module to check
|
||||
* @returns {Error}
|
||||
*/
|
||||
function incompatibility(module) {
|
||||
let { metadata, id } = module;
|
||||
|
||||
// if metadata or engines are not specified we assume compatibility is not
|
||||
// an issue.
|
||||
if (!metadata || !("engines" in metadata))
|
||||
return null;
|
||||
|
||||
let { engines } = metadata;
|
||||
|
||||
if (engines === null || typeof(engines) !== "object")
|
||||
return new Error("Malformed engines' property in metadata");
|
||||
|
||||
let applications = Object.keys(engines);
|
||||
|
||||
let versionRange;
|
||||
applications.forEach(function(name) {
|
||||
if (xulappModule.is(name)) {
|
||||
versionRange = engines[name];
|
||||
// Continue iteration. We want to ensure the module doesn't
|
||||
// contain a typo in the applications' name or some unknown
|
||||
// application - `is` function throws an exception in that case.
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof(versionRange) === "string") {
|
||||
if (xulappModule.satisfiesVersion(versionRange))
|
||||
return null;
|
||||
|
||||
return new Error("Unsupported Application version: The module " + id +
|
||||
" currently supports only version " + versionRange + " of " +
|
||||
xulappModule.name + ".");
|
||||
}
|
||||
|
||||
return new Error("Unsupported Application: The module " + id +
|
||||
" currently supports only " + applications.join(", ") + ".")
|
||||
}
|
||||
|
||||
function CuddlefishLoader(options) {
|
||||
let { manifest } = options;
|
||||
|
||||
|
@ -90,8 +41,7 @@ function CuddlefishLoader(options) {
|
|||
// cache to avoid subsequent loads via `require`.
|
||||
modules: override({
|
||||
'toolkit/loader': loaderModule,
|
||||
'sdk/loader/cuddlefish': exports,
|
||||
'sdk/system/xul-app': xulappModule
|
||||
'sdk/loader/cuddlefish': exports
|
||||
}, options.modules),
|
||||
resolve: function resolve(id, requirer) {
|
||||
let entry = requirer && requirer in manifest && manifest[requirer];
|
||||
|
|
|
@ -42,6 +42,9 @@ const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
|||
const { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {});
|
||||
const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");
|
||||
const {
|
||||
incompatibility
|
||||
} = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {}).XulApp;
|
||||
|
||||
// Define some shortcuts.
|
||||
const bind = Function.call.bind(Function.bind);
|
||||
|
@ -349,6 +352,12 @@ const load = iced(function load(loader, module) {
|
|||
});
|
||||
}
|
||||
|
||||
let (error = incompatibility(module)) {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (module.exports && typeof(module.exports) === 'object')
|
||||
freeze(module.exports);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["Startup"];
|
||||
this.EXPORTED_SYMBOLS = ["Startup"];
|
||||
|
||||
const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
@ -20,10 +20,10 @@ const NAME2TOPIC = {
|
|||
'Thunderbird': 'mail-startup-done'
|
||||
};
|
||||
|
||||
var Startup = {
|
||||
var exports = {
|
||||
initialized: !appStartupSrv.startingUp
|
||||
};
|
||||
var exports = Startup;
|
||||
this.Startup = exports;
|
||||
|
||||
let gOnceInitializedDeferred = defer();
|
||||
exports.onceInitialized = gOnceInitializedDeferred.promise;
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["XulApp"];
|
||||
this.EXPORTED_SYMBOLS = ["XulApp"];
|
||||
|
||||
var { classes: Cc, interfaces: Ci } = Components;
|
||||
|
||||
var exports = {};
|
||||
var XulApp = exports;
|
||||
this.XulApp = exports;
|
||||
|
||||
var appInfo = Cc["@mozilla.org/xre/app-info;1"].
|
||||
getService(Ci.nsIXULAppInfo);
|
||||
|
||||
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Ci.nsIXULAppInfo);
|
||||
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
|
||||
.getService(Ci.nsIVersionComparator);
|
||||
|
||||
|
@ -183,3 +184,51 @@ function satisfiesVersion(version, versionRange) {
|
|||
});
|
||||
}
|
||||
exports.satisfiesVersion = satisfiesVersion;
|
||||
|
||||
/**
|
||||
* Ensure the current application satisfied the requirements specified in the
|
||||
* module given. If not, an exception related to the incompatibility is
|
||||
* returned; `null` otherwise.
|
||||
*
|
||||
* @param {Object} module
|
||||
* The module to check
|
||||
* @returns {Error}
|
||||
*/
|
||||
function incompatibility(module) {
|
||||
let { metadata, id } = module;
|
||||
|
||||
// if metadata or engines are not specified we assume compatibility is not
|
||||
// an issue.
|
||||
if (!metadata || !("engines" in metadata))
|
||||
return null;
|
||||
|
||||
let { engines } = metadata;
|
||||
|
||||
if (engines === null || typeof(engines) !== "object")
|
||||
return new Error("Malformed engines' property in metadata");
|
||||
|
||||
let applications = Object.keys(engines);
|
||||
|
||||
let versionRange;
|
||||
applications.forEach(function(name) {
|
||||
if (is(name)) {
|
||||
versionRange = engines[name];
|
||||
// Continue iteration. We want to ensure the module doesn't
|
||||
// contain a typo in the applications' name or some unknown
|
||||
// application - `is` function throws an exception in that case.
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof(versionRange) === "string") {
|
||||
if (satisfiesVersion(versionRange))
|
||||
return null;
|
||||
|
||||
return new Error("Unsupported Application version: The module " + id +
|
||||
" currently supports only version " + versionRange + " of " +
|
||||
name + ".");
|
||||
}
|
||||
|
||||
return new Error("Unsupported Application: The module " + id +
|
||||
" currently supports only " + applications.join(", ") + ".")
|
||||
}
|
||||
exports.incompatibility = incompatibility;
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
# -*- 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/.
|
||||
|
||||
EXTRA_JS_MODULES.sdk.system += [
|
||||
'Startup.js',
|
||||
'XulApp.js',
|
||||
]
|
|
@ -1,10 +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/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Loader, Require, unload, override } = require('sdk/loader/cuddlefish');
|
||||
const app = require('sdk/system/xul-app');
|
||||
const packaging = require('@loader/options');
|
||||
|
||||
exports['test loader'] = function(assert) {
|
||||
|
@ -44,4 +44,19 @@ exports['test loader'] = function(assert) {
|
|||
'loader.unload() must call listeners in LIFO order.');
|
||||
};
|
||||
|
||||
require('test').run(exports);
|
||||
exports['test loader on unsupported modules'] = function(assert) {
|
||||
let loader = Loader({});
|
||||
let err = "";
|
||||
assert.throws(() => {
|
||||
if (!app.is('Firefox')) {
|
||||
require('./fixtures/loader/unsupported/firefox');
|
||||
}
|
||||
else {
|
||||
require('./fixtures/loader/unsupported/fennec');
|
||||
}
|
||||
}, /^Unsupported Application/, "throws Unsupported Application");
|
||||
|
||||
unload(loader);
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
let {
|
||||
|
@ -11,9 +10,10 @@ let { readURI } = require('sdk/net/url');
|
|||
|
||||
let root = module.uri.substr(0, module.uri.lastIndexOf('/'))
|
||||
|
||||
|
||||
// The following adds Debugger constructor to the global namespace.
|
||||
const { Cu } = require('chrome');
|
||||
const app = require('sdk/system/xul-app');
|
||||
|
||||
const { addDebuggerToGlobal } = Cu.import('resource://gre/modules/jsdebugger.jsm', {});
|
||||
addDebuggerToGlobal(this);
|
||||
|
||||
|
@ -331,7 +331,7 @@ exports['test console global by default'] = function (assert) {
|
|||
let uri = root + '/fixtures/loader/globals/';
|
||||
let loader = Loader({ paths: { '': uri }});
|
||||
let program = main(loader, 'main');
|
||||
|
||||
|
||||
assert.ok(typeof program.console === 'object', 'global `console` exists');
|
||||
assert.ok(typeof program.console.log === 'function', 'global `console.log` exists');
|
||||
|
||||
|
@ -374,4 +374,19 @@ exports["test require#resolve"] = function(assert) {
|
|||
assert.equal(root + "toolkit/loader.js", require.resolve("toolkit/loader"), "correct resolution of sdk module");
|
||||
};
|
||||
|
||||
require('test').run(exports);
|
||||
exports['test loader on unsupported modules'] = function(assert) {
|
||||
let loader = Loader({});
|
||||
let err = "";
|
||||
assert.throws(() => {
|
||||
if (!app.is('Firefox')) {
|
||||
require('./fixtures/loader/unsupported/firefox');
|
||||
}
|
||||
else {
|
||||
require('./fixtures/loader/unsupported/fennec');
|
||||
}
|
||||
}, /^Unsupported Application/, "throws Unsupported Application");
|
||||
|
||||
unload(loader);
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
|
|
@ -325,6 +325,9 @@ setUpdateTrackingId();
|
|||
(function setupAccessibility() {
|
||||
let accessibilityScope = {};
|
||||
SettingsListener.observe("accessibility.screenreader", false, function(value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (!('AccessFu' in accessibilityScope)) {
|
||||
Cu.import('resource://gre/modules/accessibility/AccessFu.jsm',
|
||||
accessibilityScope);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<!-- Stock Android things -->
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
|
||||
|
@ -127,7 +127,7 @@
|
|||
<!-- Stock Android things -->
|
||||
<project name="platform/external/icu4c" path="external/icu4c" revision="2bb01561780583cc37bc667f0ea79f48a122d8a2"/>
|
||||
<!-- dolphin specific things -->
|
||||
<project name="device/sprd" path="device/sprd" revision="ebb1ce6af72efe15c6919e2ceb9ee805ce2e5960"/>
|
||||
<project name="device/sprd" path="device/sprd" revision="0351ccd65808a2486e0fefb99674ca7a64c2c6dc"/>
|
||||
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="4e58336019b5cbcfd134caf55b142236cf986618"/>
|
||||
<project name="platform/frameworks/av" path="frameworks/av" revision="facca8d3e35431b66f85a4eb42bc6c5b24bd04da"/>
|
||||
<project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
|
||||
|
@ -137,7 +137,7 @@
|
|||
<project name="platform/system/core" path="system/core" revision="53d584d4a4b4316e4de9ee5f210d662f89b44e7e"/>
|
||||
<project name="u-boot" path="u-boot" revision="2d7a801a3e002078f885e8085fad374a564682e5"/>
|
||||
<project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="7feb3df0e150053e0143ef525f6e082bda320aea"/>
|
||||
<project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="cbc0a8e207a21bfaa96e07971ac1f380d9a677cf"/>
|
||||
<project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="69c8c336794666b010e34b2f501d89118513c546"/>
|
||||
<project name="vendor/sprd/partner" path="vendor/sprd/partner" revision="8649c7145972251af11b0639997edfecabfc7c2e"/>
|
||||
<project name="vendor/sprd/proprietories" path="vendor/sprd/proprietories" revision="d2466593022f7078aaaf69026adf3367c2adb7bb"/>
|
||||
</manifest>
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
|
||||
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<!-- Stock Android things -->
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
|
||||
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<!-- Stock Android things -->
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
|
||||
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
</project>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<!-- Stock Android things -->
|
||||
|
@ -123,7 +123,7 @@
|
|||
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="e8a318f7690092e639ba88891606f4183e846d3f"/>
|
||||
<project name="device/qcom/common" path="device/qcom/common" revision="878804e0becfe5635bb8ccbf2671333d546c6fb6"/>
|
||||
<project name="device-flame" path="device/t2m/flame" remote="b2g" revision="55ba09d8edffe7daffd954986b913319fd97890f"/>
|
||||
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="ebb14165369f5edc3f335d5bde6eef8439073589"/>
|
||||
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="49417cfc622074daa3c76b345a199f6731375800"/>
|
||||
<project name="kernel_lk" path="bootable/bootloader/lk" remote="b2g" revision="9eb619d2efdf4bd121587d8296f5c10481f750b8"/>
|
||||
<project name="platform_bootable_recovery" path="bootable/recovery" remote="b2g" revision="e81502511cda303c803e63f049574634bc96f9f2"/>
|
||||
<project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="81c4a859d75d413ad688067829d21b7ba9205f81"/>
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "51bb0dde2b9800784dc6b4688eb8108aa18de765",
|
||||
"revision": "90c5e3b6bc763bd6a40aa5671801ff6852ad951d",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
|
||||
<project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b72909030e214175144342f7e5df7e88a2b52fd4"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e5da0e462e51cf7f56963e87deb845f87a3a1cf4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c22462206967693ab96b6af1627ba6925f5723f"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="e29a2effcf580682728fcbab5608bcf82aad48b0"/>
|
||||
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIST_SUBDIR = 'browser'
|
||||
export('DIST_SUBDIR')
|
||||
|
|
|
@ -651,7 +651,7 @@
|
|||
; [Default Preferences]
|
||||
; All the pref files must be part of base to prevent migration bugs
|
||||
#ifdef MOZ_MULET
|
||||
@BINPATH@/defaults/pref/b2g.js
|
||||
@BINPATH@/browser/@PREF_DIR@/b2g.js
|
||||
#else
|
||||
@BINPATH@/@PREF_DIR@/b2g.js
|
||||
#endif
|
||||
|
|
|
@ -1482,10 +1482,6 @@ pref("devtools.browserconsole.filter.secwarn", true);
|
|||
// Text size in the Web Console. Use 0 for the system default size.
|
||||
pref("devtools.webconsole.fontSize", 0);
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 0);
|
||||
|
||||
// Persistent logging: |true| if you want the Web Console to keep all of the
|
||||
// logged messages after reloading the page, |false| if you want the output to
|
||||
// be cleared each time page navigation happens.
|
||||
|
@ -1616,6 +1612,7 @@ pref("loop.retry_delay.limit", 300000);
|
|||
pref("loop.feedback.baseUrl", "https://input.mozilla.org/api/v1/feedback");
|
||||
pref("loop.feedback.product", "Loop");
|
||||
pref("loop.debug.websocket", false);
|
||||
pref("loop.debug.sdk", false);
|
||||
|
||||
// serverURL to be assigned by services team
|
||||
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
|
||||
|
|
|
@ -4134,7 +4134,7 @@ function nsBrowserAccess() { }
|
|||
nsBrowserAccess.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
|
||||
|
||||
_openURIInNewTab: function(aURI, aOpener, aIsExternal) {
|
||||
_openURIInNewTab: function(aURI, aOpener, aIsExternal, aEnsureNonRemote=false) {
|
||||
let win, needToFocusWin;
|
||||
|
||||
// try the current window. if we're in a popup, fall back on the most recent browser window
|
||||
|
@ -4166,6 +4166,15 @@ nsBrowserAccess.prototype = {
|
|||
inBackground: loadInBackground});
|
||||
let browser = win.gBrowser.getBrowserForTab(tab);
|
||||
|
||||
// It's possible that we've been asked to open a new non-remote
|
||||
// browser in a window that defaults to having remote browsers -
|
||||
// this can happen if we're opening the new tab due to a window.open
|
||||
// or _blank anchor in a non-remote browser. If so, we have to force
|
||||
// the newly opened browser to also not be remote.
|
||||
if (win.gMultiProcessBrowser && aEnsureNonRemote) {
|
||||
win.gBrowser.updateBrowserRemoteness(browser, false);
|
||||
}
|
||||
|
||||
if (needToFocusWin || (!loadInBackground && aIsExternal))
|
||||
win.focus();
|
||||
|
||||
|
@ -4173,6 +4182,14 @@ nsBrowserAccess.prototype = {
|
|||
},
|
||||
|
||||
openURI: function (aURI, aOpener, aWhere, aContext) {
|
||||
// This function should only ever be called if we're opening a URI
|
||||
// from a non-remote browser window (via nsContentTreeOwner).
|
||||
if (aOpener && Cu.isCrossProcessWrapper(aOpener)) {
|
||||
Cu.reportError("nsBrowserAccess.openURI was passed a CPOW for aOpener. " +
|
||||
"openURI should only ever be called from non-remote browsers.");
|
||||
throw Cr.NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
var newWindow = null;
|
||||
var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
|
||||
|
||||
|
@ -4198,7 +4215,7 @@ nsBrowserAccess.prototype = {
|
|||
newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
|
||||
break;
|
||||
case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
|
||||
let browser = this._openURIInNewTab(aURI, aOpener, isExternal);
|
||||
let browser = this._openURIInNewTab(aURI, aOpener, isExternal, true);
|
||||
if (browser)
|
||||
newWindow = browser.contentWindow;
|
||||
break;
|
||||
|
|
|
@ -357,7 +357,7 @@ skip-if = e10s # Bug ?????? - test directly manipulates content
|
|||
[browser_parsable_css.js]
|
||||
skip-if = e10s
|
||||
[browser_parsable_script.js]
|
||||
skip-if = debug || asan # Times out on debug/asan, and we are less picky about our JS there
|
||||
skip-if = asan # Disabled because it takes a long time (see test for more information)
|
||||
|
||||
[browser_pinnedTabs.js]
|
||||
[browser_plainTextLinks.js]
|
||||
|
@ -483,3 +483,5 @@ skip-if = e10s
|
|||
skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
|
||||
[browser_bug1045809.js]
|
||||
skip-if = e10s
|
||||
[browser_bug1047603.js]
|
||||
skip-if = os == "linux" # Bug 1066856 - waiting for OMTC to be enabled by default on Linux.
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const OPEN_LOCATION_PREF = "browser.link.open_newwindow";
|
||||
const NON_REMOTE_PAGE = "about:crashes";
|
||||
|
||||
const SIMPLE_PAGE_HTML = `
|
||||
<a href="about:home" target="_blank" id="testAnchor">Open a window</a>
|
||||
`;
|
||||
|
||||
function frame_script() {
|
||||
addMessageListener("test:click", (message) => {
|
||||
let element = content.document.getElementById("testAnchor");
|
||||
element.click();
|
||||
});
|
||||
sendAsyncMessage("test:ready");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves once the frame_script is loaded
|
||||
* in the browser, and has seen the DOMContentLoaded event.
|
||||
*/
|
||||
function waitForFrameScriptReady(mm) {
|
||||
return new Promise((resolve, reject) => {
|
||||
mm.addMessageListener("test:ready", function onTestReady() {
|
||||
mm.removeMessageListener("test:ready", onTestReady);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes some browser in some window, and forces that browser
|
||||
* to become non-remote, and then navigates it to a page that
|
||||
* we're not supposed to be displaying remotely. Returns a
|
||||
* Promise that resolves when the browser is no longer remote.
|
||||
*/
|
||||
function prepareNonRemoteBrowser(aWindow, browser) {
|
||||
aWindow.gBrowser.updateBrowserRemoteness(browser, false);
|
||||
browser.loadURI(NON_REMOTE_PAGE);
|
||||
return new Promise((resolve, reject) => {
|
||||
waitForCondition(() => !browser.isRemoteBrowser, () => {
|
||||
resolve();
|
||||
}, "Waiting for browser to become non-remote");
|
||||
})
|
||||
}
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(OPEN_LOCATION_PREF);
|
||||
});
|
||||
|
||||
/**
|
||||
* Test that if we open a new tab from a link in a non-remote
|
||||
* browser in an e10s window, that the new tab's browser is also
|
||||
* not remote. Also tests with a private browsing window.
|
||||
*/
|
||||
add_task(function* test_new_tab() {
|
||||
let normalWindow = yield promiseOpenAndLoadWindow({
|
||||
remote: true
|
||||
}, true);
|
||||
let privateWindow = yield promiseOpenAndLoadWindow({
|
||||
remote: true,
|
||||
private: true,
|
||||
}, true);
|
||||
|
||||
for (let testWindow of [normalWindow, privateWindow]) {
|
||||
let testBrowser = testWindow.gBrowser.selectedBrowser;
|
||||
yield prepareNonRemoteBrowser(testWindow, testBrowser);
|
||||
|
||||
// Get our framescript ready
|
||||
let mm = testBrowser.messageManager;
|
||||
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
|
||||
let readyPromise = waitForFrameScriptReady(mm);
|
||||
yield readyPromise;
|
||||
|
||||
// Inject our test HTML into our non-remote tab.
|
||||
testBrowser.contentDocument.body.innerHTML = SIMPLE_PAGE_HTML;
|
||||
|
||||
// Click on the link in the browser, and wait for the new tab.
|
||||
mm.sendAsyncMessage("test:click");
|
||||
let tabOpenEvent = yield waitForNewTab(testWindow.gBrowser);
|
||||
let newTab = tabOpenEvent.target;
|
||||
ok(!newTab.linkedBrowser.isRemoteBrowser,
|
||||
"The opened browser should not be remote.");
|
||||
|
||||
testWindow.gBrowser.removeTab(newTab);
|
||||
}
|
||||
|
||||
normalWindow.close();
|
||||
privateWindow.close();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test that if we open a new window from a link in a non-remote
|
||||
* browser in an e10s window, that the new window is not an e10s
|
||||
* window. Also tests with a private browsing window.
|
||||
*/
|
||||
add_task(function* test_new_window() {
|
||||
let normalWindow = yield promiseOpenAndLoadWindow({
|
||||
remote: true
|
||||
}, true);
|
||||
let privateWindow = yield promiseOpenAndLoadWindow({
|
||||
remote: true,
|
||||
private: true,
|
||||
}, true);
|
||||
|
||||
// Fiddle with the prefs so that we open target="_blank" links
|
||||
// in new windows instead of new tabs.
|
||||
Services.prefs.setIntPref(OPEN_LOCATION_PREF,
|
||||
Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW);
|
||||
|
||||
for (let testWindow of [normalWindow, privateWindow]) {
|
||||
let testBrowser = testWindow.gBrowser.selectedBrowser;
|
||||
yield prepareNonRemoteBrowser(testWindow, testBrowser);
|
||||
|
||||
// Get our framescript ready
|
||||
let mm = testBrowser.messageManager;
|
||||
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
|
||||
let readyPromise = waitForFrameScriptReady(mm);
|
||||
yield readyPromise;
|
||||
|
||||
// Inject our test HTML into our non-remote window.
|
||||
testBrowser.contentDocument.body.innerHTML = SIMPLE_PAGE_HTML;
|
||||
|
||||
// Click on the link in the browser, and wait for the new window.
|
||||
let windowOpenPromise = promiseTopicObserved("browser-delayed-startup-finished");
|
||||
mm.sendAsyncMessage("test:click");
|
||||
let [newWindow] = yield windowOpenPromise;
|
||||
ok(!newWindow.gMultiProcessBrowser,
|
||||
"The opened window should not be an e10s window.");
|
||||
newWindow.close();
|
||||
}
|
||||
|
||||
normalWindow.close();
|
||||
privateWindow.close();
|
||||
|
||||
Services.prefs.clearUserPref(OPEN_LOCATION_PREF);
|
||||
});
|
|
@ -60,18 +60,58 @@ function parsePromise(uri) {
|
|||
}
|
||||
|
||||
add_task(function* checkAllTheJS() {
|
||||
let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
|
||||
// This asynchronously produces a list of URLs (sadly, mostly sync on our
|
||||
// test infrastructure because it runs against jarfiles there, and
|
||||
// our zipreader APIs are all sync)
|
||||
let uris = yield generateURIsFromDirTree(appDir, [".js", ".jsm"]);
|
||||
// In debug builds, even on a fast machine, collecting the file list may take
|
||||
// more than 30 seconds, and parsing all files may take four more minutes.
|
||||
// For this reason, this test must be explictly requested in debug builds by
|
||||
// using the "--setpref parse=<filter>" argument to mach. You can specify:
|
||||
// - A case-sensitive substring of the file name to test (slow).
|
||||
// - A single absolute URI printed out by a previous run (fast).
|
||||
// - An empty string to run the test on all files (slowest).
|
||||
let parseRequested = Services.prefs.prefHasUserValue("parse");
|
||||
let parseValue = parseRequested && Services.prefs.getCharPref("parse");
|
||||
if (SpecialPowers.isDebugBuild) {
|
||||
if (!parseRequested) {
|
||||
ok(true, "Test disabled on debug build. To run, execute: ./mach" +
|
||||
" mochitest-browser --setpref parse=<case_sensitive_filter>" +
|
||||
" browser/base/content/test/general/browser_parsable_script.js");
|
||||
return;
|
||||
}
|
||||
// Request a 10 minutes timeout (30 seconds * 20) for debug builds.
|
||||
requestLongerTimeout(20);
|
||||
}
|
||||
|
||||
let uris;
|
||||
// If an absolute URI is specified on the command line, use it immediately.
|
||||
if (parseValue && parseValue.contains(":")) {
|
||||
uris = [NetUtil.newURI(parseValue)];
|
||||
} else {
|
||||
let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
|
||||
// This asynchronously produces a list of URLs (sadly, mostly sync on our
|
||||
// test infrastructure because it runs against jarfiles there, and
|
||||
// our zipreader APIs are all sync)
|
||||
let startTimeMs = Date.now();
|
||||
info("Collecting URIs");
|
||||
uris = yield generateURIsFromDirTree(appDir, [".js", ".jsm"]);
|
||||
info("Collected URIs in " + (Date.now() - startTimeMs) + "ms");
|
||||
|
||||
// Apply the filter specified on the command line, if any.
|
||||
if (parseValue) {
|
||||
uris = uris.filter(uri => {
|
||||
if (uri.spec.contains(parseValue)) {
|
||||
return true;
|
||||
}
|
||||
info("Not checking filtered out " + uri.spec);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We create an array of promises so we can parallelize all our parsing
|
||||
// and file loading activity:
|
||||
let allPromises = [];
|
||||
for (let uri of uris) {
|
||||
if (uriIsWhiteListed(uri)) {
|
||||
info("Not checking " + uri.spec);
|
||||
info("Not checking whitelisted " + uri.spec);
|
||||
continue;
|
||||
}
|
||||
allPromises.push(parsePromise(uri.spec));
|
||||
|
|
|
@ -665,3 +665,7 @@ function assertWebRTCIndicatorStatus(expected) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function waitForNewTab(aTabBrowser) {
|
||||
return promiseWaitForEvent(aTabBrowser.tabContainer, "TabOpen");
|
||||
}
|
||||
|
|
|
@ -33,3 +33,7 @@ pref("browser.search.param.yahoo-fr-ja", "mozff");
|
|||
#ifdef MOZ_METRO
|
||||
pref("browser.search.param.yahoo-fr-metro", "");
|
||||
#endif
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 5);
|
|
@ -30,3 +30,7 @@ pref("browser.search.param.yahoo-fr-ja", "mozff");
|
|||
#ifdef MOZ_METRO
|
||||
pref("browser.search.param.yahoo-fr-metro", "");
|
||||
#endif
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 5);
|
|
@ -30,3 +30,7 @@ pref("browser.search.param.yahoo-fr-ja", "mozff");
|
|||
pref("browser.search.param.ms-pc-metro", "MOZW");
|
||||
pref("browser.search.param.yahoo-fr-metro", "mozilla_metro_search");
|
||||
#endif
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 0);
|
|
@ -29,3 +29,7 @@ pref("browser.search.param.yahoo-fr-ja", "mozff");
|
|||
#ifdef MOZ_METRO
|
||||
pref("browser.search.param.yahoo-fr-metro", "");
|
||||
#endif
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 0);
|
|
@ -30,11 +30,16 @@ function openAboutAccountsFromMenuPanel(entryPoint) {
|
|||
ok(syncButton, "The Sync button was added to the Panel Menu");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let handler = () => {
|
||||
gBrowser.selectedTab.removeEventListener("load", handler, true);
|
||||
let handler = (e) => {
|
||||
if (e.originalTarget != gBrowser.selectedTab.linkedBrowser.contentDocument ||
|
||||
e.target.location.href == "about:blank") {
|
||||
info("Skipping spurious 'load' event for " + e.target.location.href);
|
||||
return;
|
||||
}
|
||||
gBrowser.selectedTab.linkedBrowser.removeEventListener("load", handler, true);
|
||||
deferred.resolve();
|
||||
}
|
||||
gBrowser.selectedTab.addEventListener("load", handler, true);
|
||||
gBrowser.selectedTab.linkedBrowser.addEventListener("load", handler, true);
|
||||
|
||||
syncButton.click();
|
||||
yield deferred.promise;
|
||||
|
|
|
@ -41,6 +41,28 @@ const cloneErrorObject = function(error, targetWindow) {
|
|||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes an object or value available to an unprivileged target window.
|
||||
*
|
||||
* Primitives are returned as they are, while objects are cloned into the
|
||||
* specified target. Error objects are also handled correctly.
|
||||
*
|
||||
* @param {any} value Value or object to copy
|
||||
* @param {nsIDOMWindow} targetWindow The content window to copy to
|
||||
*/
|
||||
const cloneValueInto = function(value, targetWindow) {
|
||||
if (!value || typeof value != "object") {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Inspect for an error this way, because the Error object is special.
|
||||
if (value.constructor.name == "Error") {
|
||||
return cloneErrorObject(value, targetWindow);
|
||||
}
|
||||
|
||||
return Cu.cloneInto(value, targetWindow);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject any API containing _only_ function properties into the given window.
|
||||
*
|
||||
|
@ -56,17 +78,7 @@ const injectObjectAPI = function(api, targetWindow) {
|
|||
injectedAPI[func] = function(...params) {
|
||||
let callback = params.pop();
|
||||
api[func](...params, function(...results) {
|
||||
results = results.map(result => {
|
||||
if (result && typeof result == "object") {
|
||||
// Inspect for an error this way, because the Error object is special.
|
||||
if (result.constructor.name == "Error") {
|
||||
return cloneErrorObject(result.message)
|
||||
}
|
||||
return Cu.cloneInto(result, targetWindow);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
callback(...results);
|
||||
callback(...[cloneValueInto(r, targetWindow) for (r of results)]);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
@ -203,11 +215,11 @@ function injectLoopAPI(targetWindow) {
|
|||
value: function(callback) {
|
||||
// We translate from a promise to a callback, as we can't pass promises from
|
||||
// Promise.jsm across the priv versus unpriv boundary.
|
||||
return MozLoopService.register().then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
callback(null);
|
||||
}, err => {
|
||||
callback(err);
|
||||
});
|
||||
callback(cloneValueInto(err, targetWindow));
|
||||
}).catch(Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -357,11 +369,20 @@ function injectLoopAPI(targetWindow) {
|
|||
value: function(path, method, payloadObj, callback) {
|
||||
// XXX: Bug 1065153 - Should take a sessionType parameter instead of hard-coding GUEST
|
||||
// XXX Should really return a DOM promise here.
|
||||
return MozLoopService.hawkRequest(LOOP_SESSION_TYPE.GUEST, path, method, payloadObj).then((response) => {
|
||||
MozLoopService.hawkRequest(LOOP_SESSION_TYPE.GUEST, path, method, payloadObj).then((response) => {
|
||||
callback(null, response.body);
|
||||
}, (error) => {
|
||||
callback(Cu.cloneInto(error, targetWindow));
|
||||
});
|
||||
}, hawkError => {
|
||||
// The hawkError.error property, while usually a string representing
|
||||
// an HTTP response status message, may also incorrectly be a native
|
||||
// error object that will cause the cloning function to fail.
|
||||
callback(Cu.cloneInto({
|
||||
error: (hawkError.error && typeof hawkError.error == "string")
|
||||
? hawkError.error : "Unexpected exception",
|
||||
message: hawkError.message,
|
||||
code: hawkError.code,
|
||||
errno: hawkError.errno,
|
||||
}, targetWindow));
|
||||
}).catch(Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -11,9 +11,7 @@ var loop = loop || {};
|
|||
loop.conversation = (function(OT, mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedViews = loop.shared.views,
|
||||
// aliasing translation function as __ for concision
|
||||
__ = mozL10n.get;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
/**
|
||||
* App router.
|
||||
|
@ -24,11 +22,15 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
|
||||
|
||||
propTypes: {
|
||||
model: React.PropTypes.object.isRequired
|
||||
model: React.PropTypes.object.isRequired,
|
||||
video: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialProps: function() {
|
||||
return {showDeclineMenu: false};
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
showDeclineMenu: false,
|
||||
video: true
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -79,6 +81,37 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
this.setState({showDeclineMenu: false});
|
||||
},
|
||||
|
||||
/*
|
||||
* Generate props for <AcceptCallButton> component based on
|
||||
* incoming call type. An incoming video call will render a video
|
||||
* answer button primarily, an audio call will flip them.
|
||||
**/
|
||||
_answerModeProps: function() {
|
||||
var videoButton = {
|
||||
handler: this._handleAccept("audio-video"),
|
||||
className: "fx-embedded-btn-icon-video",
|
||||
tooltip: "incoming_call_accept_audio_video_tooltip"
|
||||
};
|
||||
var audioButton = {
|
||||
handler: this._handleAccept("audio"),
|
||||
className: "fx-embedded-btn-audio-small",
|
||||
tooltip: "incoming_call_accept_audio_only_tooltip"
|
||||
};
|
||||
var props = {};
|
||||
props.primary = videoButton;
|
||||
props.secondary = audioButton;
|
||||
|
||||
// When video is not enabled on this call, we swap the buttons around.
|
||||
if (!this.props.video) {
|
||||
audioButton.className = "fx-embedded-btn-icon-audio";
|
||||
videoButton.className = "fx-embedded-btn-video-small";
|
||||
props.primary = audioButton;
|
||||
props.secondary = videoButton;
|
||||
}
|
||||
|
||||
return props;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
/* jshint ignore:start */
|
||||
var btnClassAccept = "btn btn-accept";
|
||||
|
@ -91,7 +124,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
});
|
||||
return (
|
||||
React.DOM.div({className: conversationPanelClass},
|
||||
React.DOM.h2(null, __("incoming_call_title2")),
|
||||
React.DOM.h2(null, mozL10n.get("incoming_call_title2")),
|
||||
React.DOM.div({className: "btn-group incoming-call-action-group"},
|
||||
|
||||
React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"}),
|
||||
|
@ -102,7 +135,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
|
||||
React.DOM.button({className: btnClassDecline,
|
||||
onClick: this._handleDecline},
|
||||
__("incoming_call_cancel_button")
|
||||
mozL10n.get("incoming_call_cancel_button")
|
||||
),
|
||||
React.DOM.div({className: "btn-chevron",
|
||||
onClick: this._toggleDeclineMenu}
|
||||
|
@ -111,7 +144,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
|
||||
React.DOM.ul({className: dropdownMenuClassesDecline},
|
||||
React.DOM.li({className: "btn-block", onClick: this._handleDeclineBlock},
|
||||
__("incoming_call_cancel_and_block_button")
|
||||
mozL10n.get("incoming_call_cancel_and_block_button")
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -120,22 +153,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
|
||||
React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"}),
|
||||
|
||||
React.DOM.div({className: "btn-chevron-menu-group"},
|
||||
React.DOM.div({className: "btn-group"},
|
||||
React.DOM.button({className: btnClassAccept,
|
||||
onClick: this._handleAccept("audio-video")},
|
||||
React.DOM.span({className: "fx-embedded-answer-btn-text"},
|
||||
__("incoming_call_accept_button")
|
||||
),
|
||||
React.DOM.span({className: "fx-embedded-btn-icon-video"}
|
||||
)
|
||||
),
|
||||
React.DOM.div({className: "call-audio-only",
|
||||
onClick: this._handleAccept("audio"),
|
||||
title: __("incoming_call_accept_audio_only_tooltip")}
|
||||
)
|
||||
)
|
||||
),
|
||||
AcceptCallButton({mode: this._answerModeProps()}),
|
||||
|
||||
React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"})
|
||||
|
||||
|
@ -146,6 +164,41 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Incoming call view accept button, renders different primary actions
|
||||
* (answer with video / with audio only) based on the props received
|
||||
**/
|
||||
var AcceptCallButton = React.createClass({displayName: 'AcceptCallButton',
|
||||
|
||||
propTypes: {
|
||||
mode: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var mode = this.props.mode;
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
React.DOM.div({className: "btn-chevron-menu-group"},
|
||||
React.DOM.div({className: "btn-group"},
|
||||
React.DOM.button({className: "btn btn-accept",
|
||||
onClick: mode.primary.handler,
|
||||
title: mozL10n.get(mode.primary.tooltip)},
|
||||
React.DOM.span({className: "fx-embedded-answer-btn-text"},
|
||||
mozL10n.get("incoming_call_accept_button")
|
||||
),
|
||||
React.DOM.span({className: mode.primary.className})
|
||||
),
|
||||
React.DOM.div({className: mode.secondary.className,
|
||||
onClick: mode.secondary.handler,
|
||||
title: mozL10n.get(mode.secondary.tooltip)}
|
||||
)
|
||||
)
|
||||
)
|
||||
/* jshint ignore:end */
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Conversation router.
|
||||
*
|
||||
|
@ -225,7 +278,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
this._websocket.promiseConnect().then(function() {
|
||||
this.loadReactComponent(loop.conversation.IncomingCallView({
|
||||
model: this._conversation,
|
||||
video: {enabled: this._conversation.hasVideoStream("incoming")}
|
||||
video: this._conversation.hasVideoStream("incoming")
|
||||
}));
|
||||
}.bind(this), function() {
|
||||
this._handleSessionError();
|
||||
|
|
|
@ -11,9 +11,7 @@ var loop = loop || {};
|
|||
loop.conversation = (function(OT, mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedViews = loop.shared.views,
|
||||
// aliasing translation function as __ for concision
|
||||
__ = mozL10n.get;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
/**
|
||||
* App router.
|
||||
|
@ -24,11 +22,15 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
var IncomingCallView = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
model: React.PropTypes.object.isRequired
|
||||
model: React.PropTypes.object.isRequired,
|
||||
video: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialProps: function() {
|
||||
return {showDeclineMenu: false};
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
showDeclineMenu: false,
|
||||
video: true
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -79,6 +81,37 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
this.setState({showDeclineMenu: false});
|
||||
},
|
||||
|
||||
/*
|
||||
* Generate props for <AcceptCallButton> component based on
|
||||
* incoming call type. An incoming video call will render a video
|
||||
* answer button primarily, an audio call will flip them.
|
||||
**/
|
||||
_answerModeProps: function() {
|
||||
var videoButton = {
|
||||
handler: this._handleAccept("audio-video"),
|
||||
className: "fx-embedded-btn-icon-video",
|
||||
tooltip: "incoming_call_accept_audio_video_tooltip"
|
||||
};
|
||||
var audioButton = {
|
||||
handler: this._handleAccept("audio"),
|
||||
className: "fx-embedded-btn-audio-small",
|
||||
tooltip: "incoming_call_accept_audio_only_tooltip"
|
||||
};
|
||||
var props = {};
|
||||
props.primary = videoButton;
|
||||
props.secondary = audioButton;
|
||||
|
||||
// When video is not enabled on this call, we swap the buttons around.
|
||||
if (!this.props.video) {
|
||||
audioButton.className = "fx-embedded-btn-icon-audio";
|
||||
videoButton.className = "fx-embedded-btn-video-small";
|
||||
props.primary = audioButton;
|
||||
props.secondary = videoButton;
|
||||
}
|
||||
|
||||
return props;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
/* jshint ignore:start */
|
||||
var btnClassAccept = "btn btn-accept";
|
||||
|
@ -91,7 +124,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
});
|
||||
return (
|
||||
<div className={conversationPanelClass}>
|
||||
<h2>{__("incoming_call_title2")}</h2>
|
||||
<h2>{mozL10n.get("incoming_call_title2")}</h2>
|
||||
<div className="btn-group incoming-call-action-group">
|
||||
|
||||
<div className="fx-embedded-incoming-call-button-spacer"></div>
|
||||
|
@ -102,7 +135,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
|
||||
<button className={btnClassDecline}
|
||||
onClick={this._handleDecline}>
|
||||
{__("incoming_call_cancel_button")}
|
||||
{mozL10n.get("incoming_call_cancel_button")}
|
||||
</button>
|
||||
<div className="btn-chevron"
|
||||
onClick={this._toggleDeclineMenu}>
|
||||
|
@ -111,7 +144,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
|
||||
<ul className={dropdownMenuClassesDecline}>
|
||||
<li className="btn-block" onClick={this._handleDeclineBlock}>
|
||||
{__("incoming_call_cancel_and_block_button")}
|
||||
{mozL10n.get("incoming_call_cancel_and_block_button")}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -120,22 +153,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
|
||||
<div className="fx-embedded-incoming-call-button-spacer"></div>
|
||||
|
||||
<div className="btn-chevron-menu-group">
|
||||
<div className="btn-group">
|
||||
<button className={btnClassAccept}
|
||||
onClick={this._handleAccept("audio-video")}>
|
||||
<span className="fx-embedded-answer-btn-text">
|
||||
{__("incoming_call_accept_button")}
|
||||
</span>
|
||||
<span className="fx-embedded-btn-icon-video">
|
||||
</span>
|
||||
</button>
|
||||
<div className="call-audio-only"
|
||||
onClick={this._handleAccept("audio")}
|
||||
title={__("incoming_call_accept_audio_only_tooltip")} >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AcceptCallButton mode={this._answerModeProps()} />
|
||||
|
||||
<div className="fx-embedded-incoming-call-button-spacer"></div>
|
||||
|
||||
|
@ -146,6 +164,41 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Incoming call view accept button, renders different primary actions
|
||||
* (answer with video / with audio only) based on the props received
|
||||
**/
|
||||
var AcceptCallButton = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
mode: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var mode = this.props.mode;
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
<div className="btn-chevron-menu-group">
|
||||
<div className="btn-group">
|
||||
<button className="btn btn-accept"
|
||||
onClick={mode.primary.handler}
|
||||
title={mozL10n.get(mode.primary.tooltip)}>
|
||||
<span className="fx-embedded-answer-btn-text">
|
||||
{mozL10n.get("incoming_call_accept_button")}
|
||||
</span>
|
||||
<span className={mode.primary.className}></span>
|
||||
</button>
|
||||
<div className={mode.secondary.className}
|
||||
onClick={mode.secondary.handler}
|
||||
title={mozL10n.get(mode.secondary.tooltip)}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
/* jshint ignore:end */
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Conversation router.
|
||||
*
|
||||
|
@ -225,7 +278,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
this._websocket.promiseConnect().then(function() {
|
||||
this.loadReactComponent(loop.conversation.IncomingCallView({
|
||||
model: this._conversation,
|
||||
video: {enabled: this._conversation.hasVideoStream("incoming")}
|
||||
video: this._conversation.hasVideoStream("incoming")
|
||||
}));
|
||||
}.bind(this), function() {
|
||||
this._handleSessionError();
|
||||
|
|
|
@ -135,6 +135,7 @@ p {
|
|||
background-color: #f0ad4e;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-error,
|
||||
.btn-hangup,
|
||||
.btn-error + .btn-chevron {
|
||||
|
@ -142,6 +143,7 @@ p {
|
|||
border: 1px solid #d74345;
|
||||
}
|
||||
|
||||
.btn-cancel:hover,
|
||||
.btn-error:hover,
|
||||
.btn-hangup:hover,
|
||||
.btn-error + .btn-chevron:hover {
|
||||
|
@ -149,6 +151,7 @@ p {
|
|||
border: 1px solid #c53436;
|
||||
}
|
||||
|
||||
.btn-cancel:active,
|
||||
.btn-error:active,
|
||||
.btn-hangup:active,
|
||||
.btn-error + .btn-chevron:active {
|
||||
|
@ -222,7 +225,7 @@ p {
|
|||
/* Alerts */
|
||||
.alert {
|
||||
background: #eee;
|
||||
padding: .2em 1em;
|
||||
padding: .4em 1em;
|
||||
margin-bottom: 1em;
|
||||
border-bottom: 2px solid #E9E9E9;
|
||||
}
|
||||
|
@ -232,17 +235,12 @@ p {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.alert.alert-error {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
padding: 5px;
|
||||
font-size: 10px;
|
||||
justify-content: center;
|
||||
color: #FFF;
|
||||
.alert-error {
|
||||
background: repeating-linear-gradient(-45deg, #D74345, #D74345 10px, #D94B4D 10px, #D94B4D 20px) repeat scroll 0% 0% transparent;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.alert.alert-warning {
|
||||
.alert-warning {
|
||||
background: #fcf8e3;
|
||||
border: 1px solid #fbeed5;
|
||||
}
|
||||
|
|
|
@ -83,16 +83,54 @@
|
|||
max-width: 80%;
|
||||
}
|
||||
|
||||
.fx-embedded-btn-icon-video {
|
||||
.fx-embedded-btn-icon-video,
|
||||
.fx-embedded-btn-icon-audio {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: .8rem;
|
||||
height: .8rem;
|
||||
background-image: url("../img/video-inverse-14x14.png");
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fx-embedded-btn-icon-video,
|
||||
.fx-embedded-btn-video-small {
|
||||
background-image: url("../img/video-inverse-14x14.png");
|
||||
}
|
||||
|
||||
.fx-embedded-btn-icon-audio,
|
||||
.fx-embedded-btn-audio-small {
|
||||
background-image: url("../img/audio-inverse-14x14.png");
|
||||
}
|
||||
|
||||
.fx-embedded-btn-audio-small,
|
||||
.fx-embedded-btn-video-small {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-left: 1px solid rgba(255,255,255,.4);
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
background-color: #74BF43;
|
||||
background-position: center;
|
||||
background-size: 1rem;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fx-embedded-btn-video-small:hover,
|
||||
.fx-embedded-btn-audio-small:hover {
|
||||
background-color: #6cb23e;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.fx-embedded-btn-audio-small {
|
||||
background-image: url("../img/audio-inverse-14x14@2x.png");
|
||||
}
|
||||
.fx-embedded-btn-video-small {
|
||||
background-image: url("../img/video-inverse-14x14@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
.standalone .btn-hangup {
|
||||
width: auto;
|
||||
font-size: 12px;
|
||||
|
@ -233,30 +271,6 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.call-audio-only {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-left: 1px solid rgba(255,255,255,.4);
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
background-color: #74BF43;
|
||||
background-image: url("../img/audio-inverse-14x14.png");
|
||||
background-size: 1rem;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.call-audio-only:hover {
|
||||
background-color: #6cb23e;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.call-audio-only {
|
||||
background-image: url("../img/audio-inverse-14x14@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
/* Expired call url page */
|
||||
|
||||
.expired-url-info {
|
||||
|
|
|
@ -51,18 +51,6 @@ loop.shared.models = (function(l10n) {
|
|||
*/
|
||||
session: undefined,
|
||||
|
||||
/**
|
||||
* Pending call timeout value.
|
||||
* @type {Number}
|
||||
*/
|
||||
pendingCallTimeout: undefined,
|
||||
|
||||
/**
|
||||
* Pending call timer.
|
||||
* @type {Number}
|
||||
*/
|
||||
_pendingCallTimer: undefined,
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
|
@ -71,10 +59,6 @@ loop.shared.models = (function(l10n) {
|
|||
* Required:
|
||||
* - {OT} sdk: OT SDK object.
|
||||
*
|
||||
* Optional:
|
||||
* - {Number} pendingCallTimeout: Pending call timeout in milliseconds
|
||||
* (default: 20000).
|
||||
*
|
||||
* @param {Object} attributes Attributes object.
|
||||
* @param {Object} options Options object.
|
||||
*/
|
||||
|
@ -84,10 +68,12 @@ loop.shared.models = (function(l10n) {
|
|||
throw new Error("missing required sdk");
|
||||
}
|
||||
this.sdk = options.sdk;
|
||||
this.pendingCallTimeout = options.pendingCallTimeout || 20000;
|
||||
|
||||
// Ensure that any pending call timer is cleared on disconnect/error
|
||||
this.on("session:ended session:error", this._clearPendingCallTimer, this);
|
||||
// Set loop.debug.sdk to true in the browser, or standalone:
|
||||
// localStorage.setItem("debug.sdk", true);
|
||||
if (loop.shared.utils.getBoolPreference("debug.sdk")) {
|
||||
this.sdk.setLogLevel(this.sdk.DEBUG);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -112,20 +98,6 @@ loop.shared.models = (function(l10n) {
|
|||
* server for the outgoing call.
|
||||
*/
|
||||
outgoing: function(sessionData) {
|
||||
this._clearPendingCallTimer();
|
||||
|
||||
// Outgoing call has never reached destination, closing - see bug 1020448
|
||||
function handleOutgoingCallTimeout() {
|
||||
/*jshint validthis:true */
|
||||
if (!this.get("ongoing")) {
|
||||
this.trigger("timeout").endSession();
|
||||
}
|
||||
}
|
||||
|
||||
// Setup pending call timeout.
|
||||
this._pendingCallTimer = setTimeout(
|
||||
handleOutgoingCallTimeout.bind(this), this.pendingCallTimeout);
|
||||
|
||||
this.setOutgoingSessionData(sessionData);
|
||||
this.trigger("call:outgoing");
|
||||
},
|
||||
|
@ -278,15 +250,6 @@ loop.shared.models = (function(l10n) {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears current pending call timer, if any.
|
||||
*/
|
||||
_clearPendingCallTimer: function() {
|
||||
if (this._pendingCallTimer) {
|
||||
clearTimeout(this._pendingCallTimer);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Manages connection status
|
||||
* triggers apropriate event for connection error/success
|
||||
|
|
|
@ -29,7 +29,25 @@ loop.shared.utils = (function() {
|
|||
return platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for getting a boolean preference. It will either use the browser preferences
|
||||
* (if navigator.mozLoop is defined) or try to get them from localStorage.
|
||||
*
|
||||
* @param {String} prefName The name of the preference. Note that mozLoop adds
|
||||
* 'loop.' to the start of the string.
|
||||
*
|
||||
* @return The value of the preference, or false if not available.
|
||||
*/
|
||||
function getBoolPreference(prefName) {
|
||||
if (navigator.mozLoop) {
|
||||
return !!navigator.mozLoop.getLoopBoolPref(prefName);
|
||||
}
|
||||
|
||||
return !!localStorage.getItem(prefName);
|
||||
}
|
||||
|
||||
return {
|
||||
getTargetPlatform: getTargetPlatform
|
||||
getTargetPlatform: getTargetPlatform,
|
||||
getBoolPreference: getBoolPreference
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -36,11 +36,10 @@ loop.CallConnectionWebSocket = (function() {
|
|||
throw new Error("No websocketToken in options");
|
||||
}
|
||||
|
||||
// Save the debug pref now, to avoid getting it each time.
|
||||
if (navigator.mozLoop) {
|
||||
this._debugWebSocket =
|
||||
navigator.mozLoop.getLoopBoolPref("debug.websocket");
|
||||
}
|
||||
// Set loop.debug.sdk to true in the browser, or standalone:
|
||||
// localStorage.setItem("debug.websocket", true);
|
||||
this._debugWebSocket =
|
||||
loop.shared.utils.getBoolPreference("debug.websocket");
|
||||
|
||||
_.extend(this, Backbone.Events);
|
||||
};
|
||||
|
@ -148,6 +147,18 @@ loop.CallConnectionWebSocket = (function() {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies the server that the outgoing call is cancelled by the
|
||||
* user.
|
||||
*/
|
||||
cancel: function() {
|
||||
this._send({
|
||||
messageType: "action",
|
||||
event: "terminate",
|
||||
reason: "cancel"
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends data on the websocket.
|
||||
*
|
||||
|
@ -206,6 +217,7 @@ loop.CallConnectionWebSocket = (function() {
|
|||
this._completeConnection();
|
||||
break;
|
||||
case "progress":
|
||||
this.trigger("progress:" + msg.state);
|
||||
this.trigger("progress", msg);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -223,11 +223,13 @@
|
|||
}
|
||||
|
||||
.OT_closeButton {
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
position: absolute;
|
||||
font-size: 18px;
|
||||
color: #999999;
|
||||
cursor: pointer;
|
||||
font-size: 32px;
|
||||
line-height: 30px;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.OT_dialog-messages {
|
||||
|
@ -266,6 +268,10 @@
|
|||
color: #ffffff;
|
||||
}
|
||||
|
||||
.OT_dialog-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.OT_dialog-single-button {
|
||||
position: absolute;
|
||||
bottom: 41px;
|
||||
|
@ -275,6 +281,22 @@
|
|||
width: 193px;
|
||||
}
|
||||
|
||||
|
||||
.OT_dialog-single-button-wide {
|
||||
bottom: 35px;
|
||||
height: 140px;
|
||||
left: 5px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
.OT_dialog-single-button-with-title {
|
||||
margin: 0 auto;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
width: 270px;
|
||||
}
|
||||
|
||||
|
||||
.OT_dialog-button-pair {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
|
@ -301,11 +323,20 @@
|
|||
font-weight: 300;
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 150%;
|
||||
color: #A4A4A4;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.OT_dialog-button-title label {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.OT_dialog-button-title a,
|
||||
.OT_dialog-button-title a:link,
|
||||
.OT_dialog-button-title a:active {
|
||||
color: #02A1DE;
|
||||
}
|
||||
|
||||
.OT_dialog-button-title strong {
|
||||
color: #ffffff;
|
||||
|
@ -318,16 +349,19 @@
|
|||
display: block;
|
||||
line-height: 50px;
|
||||
height: 47px;
|
||||
background-color: #29A4DA;
|
||||
background-color: #1CA3DC;
|
||||
text-align: center;
|
||||
font-size: 16pt;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.OT_dialog-button.OT_dialog-button-disabled {
|
||||
background-color: #444444;
|
||||
color: #999999;
|
||||
cursor: not-allowed;
|
||||
|
||||
/* IE 8 */
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.OT_dialog-button.OT_dialog-button-large {
|
||||
|
@ -335,6 +369,16 @@
|
|||
height: 58px;
|
||||
}
|
||||
|
||||
.OT_dialog-button.OT_dialog-button-small {
|
||||
background-color: #444444;
|
||||
color: #999999;
|
||||
font-size: 12pt;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin: 20px auto 0 auto;
|
||||
width: 86px;
|
||||
}
|
||||
|
||||
.OT_dialog-progress-bar {
|
||||
border: 1px solid #4E4E4E;
|
||||
height: 8px;
|
||||
|
@ -518,6 +562,11 @@
|
|||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.OT_publisher .OT_edge-bar-item,
|
||||
.OT_subscriber .OT_edge-bar-item {
|
||||
z-index: 1; /* required to get audio level meter underneath */
|
||||
}
|
||||
|
||||
/* The publisher/subscriber name panel/archiving status bar */
|
||||
.OT_publisher .OT_name,
|
||||
.OT_subscriber .OT_name {
|
||||
|
@ -900,6 +949,8 @@
|
|||
|
||||
.OT_publisher .OT_edge-bar-item.OT_mode-on,
|
||||
.OT_subscriber .OT_edge-bar-item.OT_mode-on,
|
||||
.OT_publisher .OT_edge-bar-item.OT_mode-auto.OT_mode-on-hold,
|
||||
.OT_subscriber .OT_edge-bar-item.OT_mode-auto.OT_mode-on-hold,
|
||||
.OT_publisher:hover .OT_edge-bar-item.OT_mode-auto,
|
||||
.OT_subscriber:hover .OT_edge-bar-item.OT_mode-auto,
|
||||
.OT_publisher:hover .OT_edge-bar-item.OT_mode-mini-auto,
|
||||
|
@ -932,8 +983,10 @@
|
|||
}
|
||||
|
||||
.OT_publisher .OT_opentok.OT_mode-on,
|
||||
.OT_publisher .OT_opentok.OT_mode-auto.OT_mode-on-hold,
|
||||
.OT_publisher:hover .OT_opentok.OT_mode-auto,
|
||||
.OT_subscriber .OT_opentok.OT_mode-on,
|
||||
.OT_subscriber .OT_opentok.OT_mode-auto.OT_mode-on-hold,
|
||||
.OT_subscriber:hover .OT_opentok.OT_mode-auto {
|
||||
top: 8px;
|
||||
}
|
||||
|
@ -981,15 +1034,98 @@
|
|||
.OT_video-poster {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: 50% 50%;
|
||||
display: none;
|
||||
|
||||
opacity: .25;
|
||||
background-size: auto 76%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center bottom;
|
||||
background-image: url(../images/rtc/audioonly-silhouette.svg);
|
||||
}
|
||||
|
||||
.OT_audio-level-meter {
|
||||
position: absolute;
|
||||
width: 25%;
|
||||
max-width: 224px;
|
||||
min-width: 21px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.OT_audio-level-meter:before {
|
||||
/* makes the height of the container equals its width */
|
||||
content: '';
|
||||
display: block;
|
||||
padding-top: 100%;
|
||||
}
|
||||
|
||||
.OT_audio-level-meter__bar {
|
||||
position: absolute;
|
||||
width: 192%; /* meter value can overflow of 8% */
|
||||
height: 192%;
|
||||
top: -96% /* half of the size */;
|
||||
right: -96%;
|
||||
border-radius: 50%;
|
||||
|
||||
background-color: rgba(0, 0, 0, .8);
|
||||
}
|
||||
|
||||
.OT_audio-level-meter__audio-only-img {
|
||||
position: absolute;
|
||||
top: 22%;
|
||||
right: 15%;
|
||||
width: 40%;
|
||||
|
||||
opacity: .7;
|
||||
|
||||
background: url(../images/rtc/audioonly-headset.svg) no-repeat center;
|
||||
}
|
||||
|
||||
.OT_audio-level-meter__audio-only-img:before {
|
||||
/* makes the height of the container equals its width */
|
||||
content: '';
|
||||
display: block;
|
||||
padding-top: 100%;
|
||||
}
|
||||
|
||||
.OT_audio-level-meter__value {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-image: radial-gradient(circle, rgba(151,206,0,1) 0%, rgba(151,206,0,0) 100%);
|
||||
}
|
||||
|
||||
.OT_audio-level-meter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.OT_publisher .OT_video-poster {
|
||||
background-image: url(../images/rtc/audioonly-publisher.png);
|
||||
.OT_audio-level-meter.OT_mode-on,
|
||||
.OT_audio-only .OT_audio-level-meter.OT_mode-auto {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.OT_subscriber .OT_video-poster {
|
||||
background-image: url(../images/rtc/audioonly-subscriber.png);
|
||||
.OT_video-disabled-indicator {
|
||||
opacity: 1;
|
||||
border: none;
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-position:bottom right;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 3px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.OT_video-disabled {
|
||||
background-image: url(../images/rtc/video-disabled.png);
|
||||
}
|
||||
|
||||
.OT_video-disabled-warning {
|
||||
background-image: url(../images/rtc/video-disabled-warning.png);
|
||||
}
|
||||
|
||||
.OT_video-disabled-indicator.OT_active {
|
||||
display: block;
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -3,7 +3,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
LOOP_SERVER_URL := $(shell echo $${LOOP_SERVER_URL-http://localhost:5000})
|
||||
LOOP_PENDING_CALL_TIMEOUT := $(shell echo $${LOOP_PENDING_CALL_TIMEOUT-20000})
|
||||
NODE_LOCAL_BIN=./node_modules/.bin
|
||||
|
||||
install:
|
||||
|
@ -53,4 +52,3 @@ config:
|
|||
@echo "var loop = loop || {};" > content/config.js
|
||||
@echo "loop.config = loop.config || {};" >> content/config.js
|
||||
@echo "loop.config.serverUrl = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
|
||||
@echo "loop.config.pendingCallTimeout = `echo $(LOOP_PENDING_CALL_TIMEOUT)`;" >> content/config.js
|
||||
|
|
|
@ -21,9 +21,12 @@ body,
|
|||
.standalone-header {
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
padding: 1rem 5rem;
|
||||
border: 1px solid #E7E7E7;
|
||||
box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.header-box {
|
||||
padding: 1rem 5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
|
@ -103,7 +106,7 @@ body,
|
|||
}
|
||||
|
||||
.standalone-header-title,
|
||||
.standalone-call-btn-label {
|
||||
.standalone-btn-label {
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
|
@ -112,7 +115,7 @@ body,
|
|||
line-height: 2.2rem;
|
||||
}
|
||||
|
||||
.standalone-call-btn-label {
|
||||
.standalone-btn-label {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
|
@ -179,6 +182,10 @@ body,
|
|||
}
|
||||
}
|
||||
|
||||
.btn-pending-cancel-group > .btn-cancel {
|
||||
flex: 2 1 auto;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
/* Dimensions from spec
|
||||
* https://people.mozilla.org/~dhenein/labs/loop-link-spec/#call-start */
|
||||
|
|
|
@ -120,6 +120,16 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
var ConversationBranding = React.createClass({displayName: 'ConversationBranding',
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.h1({className: "standalone-header-title"},
|
||||
React.DOM.strong(null, mozL10n.get("brandShortname")), " ", mozL10n.get("clientShortname")
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var ConversationHeader = React.createClass({displayName: 'ConversationHeader',
|
||||
render: function() {
|
||||
var cx = React.addons.classSet;
|
||||
|
@ -138,10 +148,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
React.DOM.header({className: "standalone-header container-box"},
|
||||
React.DOM.h1({className: "standalone-header-title"},
|
||||
React.DOM.strong(null, mozL10n.get("brandShortname")), " ", mozL10n.get("clientShortname")
|
||||
),
|
||||
React.DOM.header({className: "standalone-header header-box container-box"},
|
||||
ConversationBranding(null),
|
||||
React.DOM.div({className: "loop-logo", title: "Firefox WebRTC! logo"}),
|
||||
React.DOM.h3({className: "call-url"},
|
||||
conversationUrl
|
||||
|
@ -165,6 +173,68 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
|
||||
getInitialState: function() {
|
||||
return {
|
||||
callState: this.props.callState || "connecting"
|
||||
}
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
websocket: React.PropTypes.instanceOf(loop.CallConnectionWebSocket)
|
||||
.isRequired
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.websocket.listenTo(this.props.websocket, "progress:alerting",
|
||||
this._handleRingingProgress);
|
||||
},
|
||||
|
||||
_handleRingingProgress: function() {
|
||||
this.setState({callState: "ringing"});
|
||||
},
|
||||
|
||||
_cancelOutgoingCall: function() {
|
||||
this.props.websocket.cancel();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var callState = mozL10n.get("call_progress_" + this.state.callState + "_description");
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
React.DOM.div({className: "container"},
|
||||
React.DOM.div({className: "container-box"},
|
||||
React.DOM.header({className: "pending-header header-box"},
|
||||
ConversationBranding(null)
|
||||
),
|
||||
|
||||
React.DOM.div({id: "cameraPreview"}),
|
||||
|
||||
React.DOM.div({id: "messages"}),
|
||||
|
||||
React.DOM.p({className: "standalone-btn-label"},
|
||||
callState
|
||||
),
|
||||
|
||||
React.DOM.div({className: "btn-pending-cancel-group btn-group"},
|
||||
React.DOM.div({className: "flex-padding-1"}),
|
||||
React.DOM.button({className: "btn btn-large btn-cancel",
|
||||
onClick: this._cancelOutgoingCall},
|
||||
React.DOM.span({className: "standalone-call-btn-text"},
|
||||
mozL10n.get("initiate_call_cancel_button")
|
||||
)
|
||||
),
|
||||
React.DOM.div({className: "flex-padding-1"})
|
||||
)
|
||||
),
|
||||
|
||||
ConversationFooter(null)
|
||||
)
|
||||
/* jshint ignore:end */
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Conversation launcher view. A ConversationModel is associated and attached
|
||||
* as a `model` property.
|
||||
|
@ -286,7 +356,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
ConversationHeader({
|
||||
urlCreationDateString: this.state.urlCreationDateString}),
|
||||
|
||||
React.DOM.p({className: "standalone-call-btn-label"},
|
||||
React.DOM.p({className: "standalone-btn-label"},
|
||||
mozL10n.get("initiate_call_button_label2")
|
||||
),
|
||||
|
||||
|
@ -352,6 +422,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
"unsupportedDevice": "unsupportedDevice",
|
||||
"unsupportedBrowser": "unsupportedBrowser",
|
||||
"call/expired": "expired",
|
||||
"call/pending/:token": "pendingConversation",
|
||||
"call/ongoing/:token": "loadConversation",
|
||||
"call/:token": "initiate"
|
||||
},
|
||||
|
@ -364,8 +435,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
|
||||
// Load default view
|
||||
this.loadReactComponent(HomeView(null));
|
||||
|
||||
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
||||
},
|
||||
|
||||
_onSessionExpired: function() {
|
||||
|
@ -417,7 +486,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
this._notifications.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
this._setupWebSocketAndCallView(loopToken);
|
||||
this.navigate("call/pending/" + loopToken, {
|
||||
trigger: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -427,16 +498,13 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
*
|
||||
* @param {string} loopToken The session token to use.
|
||||
*/
|
||||
_setupWebSocketAndCallView: function(loopToken) {
|
||||
_setupWebSocketAndCallView: function() {
|
||||
this._websocket = new loop.CallConnectionWebSocket({
|
||||
url: this._conversation.get("progressURL"),
|
||||
websocketToken: this._conversation.get("websocketToken"),
|
||||
callId: this._conversation.get("callId"),
|
||||
});
|
||||
this._websocket.promiseConnect().then(function() {
|
||||
this.navigate("call/ongoing/" + loopToken, {
|
||||
trigger: true
|
||||
});
|
||||
}.bind(this), function() {
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// this by better "call failed" UI.
|
||||
|
@ -464,30 +532,48 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
* it if appropraite.
|
||||
*/
|
||||
_handleWebSocketProgress: function(progressData) {
|
||||
if (progressData.state === "terminated") {
|
||||
// XXX Before adding more states here, the basic protocol messages to the
|
||||
// server need implementing on both the standalone and desktop side.
|
||||
// These are covered by bug 1045643, but also check the dependencies on
|
||||
// bug 1034041.
|
||||
//
|
||||
// Failure to do this will break desktop - standalone call setup. We're
|
||||
// ok to handle reject, as that is a specific message from the destkop via
|
||||
// the server.
|
||||
switch (progressData.reason) {
|
||||
case "reject":
|
||||
this._handleCallRejected();
|
||||
switch(progressData.state) {
|
||||
case "connecting": {
|
||||
this._handleCallConnecting();
|
||||
break;
|
||||
}
|
||||
case "terminated": {
|
||||
// At the moment, we show the same text regardless
|
||||
// of the terminated reason.
|
||||
this._handleCallTerminated(progressData.reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles call rejection.
|
||||
* XXX This should really display the call failed view - bug 1046959
|
||||
* will implement this.
|
||||
* Handles a call moving to the connecting stage.
|
||||
*/
|
||||
_handleCallRejected: function() {
|
||||
_handleCallConnecting: function() {
|
||||
var loopToken = this._conversation.get("loopToken");
|
||||
if (!loopToken) {
|
||||
this._notifications.errorL10n("missing_conversation_info");
|
||||
return;
|
||||
}
|
||||
|
||||
this.navigate("call/ongoing/" + loopToken, {
|
||||
trigger: true
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles call rejection.
|
||||
*
|
||||
* @param {String} reason The reason the call was terminated.
|
||||
*/
|
||||
_handleCallTerminated: function(reason) {
|
||||
this.endCall();
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
// For reasons other than cancel, display some notification text.
|
||||
if (reason !== "cancel") {
|
||||
// XXX This should really display the call failed view - bug 1046959
|
||||
// will implement this.
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -501,10 +587,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
this.navigate(route, {trigger: true});
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
* Default entry point.
|
||||
*/
|
||||
|
@ -549,6 +631,17 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
this.loadReactComponent(startView);
|
||||
},
|
||||
|
||||
pendingConversation: function(loopToken) {
|
||||
if (!this._conversation.isSessionReady()) {
|
||||
// User has loaded this url directly, actually setup the call.
|
||||
return this.navigate("call/" + loopToken, {trigger: true});
|
||||
}
|
||||
this._setupWebSocketAndCallView();
|
||||
this.loadReactComponent(PendingConversationView({
|
||||
websocket: this._websocket
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads conversation establishment view.
|
||||
*
|
||||
|
@ -596,8 +689,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
notifications: new sharedModels.NotificationCollection(),
|
||||
client: client,
|
||||
conversation: new sharedModels.ConversationModel({}, {
|
||||
sdk: OT,
|
||||
pendingCallTimeout: loop.config.pendingCallTimeout
|
||||
sdk: OT
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -616,6 +708,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
return {
|
||||
baseServerUrl: baseServerUrl,
|
||||
CallUrlExpiredView: CallUrlExpiredView,
|
||||
PendingConversationView: PendingConversationView,
|
||||
StartConversationView: StartConversationView,
|
||||
HomeView: HomeView,
|
||||
UnsupportedBrowserView: UnsupportedBrowserView,
|
||||
|
|
|
@ -120,6 +120,16 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
var ConversationBranding = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<h1 className="standalone-header-title">
|
||||
<strong>{mozL10n.get("brandShortname")}</strong> {mozL10n.get("clientShortname")}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var ConversationHeader = React.createClass({
|
||||
render: function() {
|
||||
var cx = React.addons.classSet;
|
||||
|
@ -138,10 +148,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
<header className="standalone-header container-box">
|
||||
<h1 className="standalone-header-title">
|
||||
<strong>{mozL10n.get("brandShortname")}</strong> {mozL10n.get("clientShortname")}
|
||||
</h1>
|
||||
<header className="standalone-header header-box container-box">
|
||||
<ConversationBranding />
|
||||
<div className="loop-logo" title="Firefox WebRTC! logo"></div>
|
||||
<h3 className="call-url">
|
||||
{conversationUrl}
|
||||
|
@ -165,6 +173,68 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
var PendingConversationView = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
callState: this.props.callState || "connecting"
|
||||
}
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
websocket: React.PropTypes.instanceOf(loop.CallConnectionWebSocket)
|
||||
.isRequired
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.websocket.listenTo(this.props.websocket, "progress:alerting",
|
||||
this._handleRingingProgress);
|
||||
},
|
||||
|
||||
_handleRingingProgress: function() {
|
||||
this.setState({callState: "ringing"});
|
||||
},
|
||||
|
||||
_cancelOutgoingCall: function() {
|
||||
this.props.websocket.cancel();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var callState = mozL10n.get("call_progress_" + this.state.callState + "_description");
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
<div className="container">
|
||||
<div className="container-box">
|
||||
<header className="pending-header header-box">
|
||||
<ConversationBranding />
|
||||
</header>
|
||||
|
||||
<div id="cameraPreview"></div>
|
||||
|
||||
<div id="messages"></div>
|
||||
|
||||
<p className="standalone-btn-label">
|
||||
{callState}
|
||||
</p>
|
||||
|
||||
<div className="btn-pending-cancel-group btn-group">
|
||||
<div className="flex-padding-1"></div>
|
||||
<button className="btn btn-large btn-cancel"
|
||||
onClick={this._cancelOutgoingCall} >
|
||||
<span className="standalone-call-btn-text">
|
||||
{mozL10n.get("initiate_call_cancel_button")}
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex-padding-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConversationFooter />
|
||||
</div>
|
||||
/* jshint ignore:end */
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Conversation launcher view. A ConversationModel is associated and attached
|
||||
* as a `model` property.
|
||||
|
@ -286,7 +356,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
<ConversationHeader
|
||||
urlCreationDateString={this.state.urlCreationDateString} />
|
||||
|
||||
<p className="standalone-call-btn-label">
|
||||
<p className="standalone-btn-label">
|
||||
{mozL10n.get("initiate_call_button_label2")}
|
||||
</p>
|
||||
|
||||
|
@ -352,6 +422,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
"unsupportedDevice": "unsupportedDevice",
|
||||
"unsupportedBrowser": "unsupportedBrowser",
|
||||
"call/expired": "expired",
|
||||
"call/pending/:token": "pendingConversation",
|
||||
"call/ongoing/:token": "loadConversation",
|
||||
"call/:token": "initiate"
|
||||
},
|
||||
|
@ -364,8 +435,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
|
||||
// Load default view
|
||||
this.loadReactComponent(<HomeView />);
|
||||
|
||||
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
||||
},
|
||||
|
||||
_onSessionExpired: function() {
|
||||
|
@ -417,7 +486,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
this._notifications.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
this._setupWebSocketAndCallView(loopToken);
|
||||
this.navigate("call/pending/" + loopToken, {
|
||||
trigger: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -427,16 +498,13 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
*
|
||||
* @param {string} loopToken The session token to use.
|
||||
*/
|
||||
_setupWebSocketAndCallView: function(loopToken) {
|
||||
_setupWebSocketAndCallView: function() {
|
||||
this._websocket = new loop.CallConnectionWebSocket({
|
||||
url: this._conversation.get("progressURL"),
|
||||
websocketToken: this._conversation.get("websocketToken"),
|
||||
callId: this._conversation.get("callId"),
|
||||
});
|
||||
this._websocket.promiseConnect().then(function() {
|
||||
this.navigate("call/ongoing/" + loopToken, {
|
||||
trigger: true
|
||||
});
|
||||
}.bind(this), function() {
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// this by better "call failed" UI.
|
||||
|
@ -464,30 +532,48 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
* it if appropraite.
|
||||
*/
|
||||
_handleWebSocketProgress: function(progressData) {
|
||||
if (progressData.state === "terminated") {
|
||||
// XXX Before adding more states here, the basic protocol messages to the
|
||||
// server need implementing on both the standalone and desktop side.
|
||||
// These are covered by bug 1045643, but also check the dependencies on
|
||||
// bug 1034041.
|
||||
//
|
||||
// Failure to do this will break desktop - standalone call setup. We're
|
||||
// ok to handle reject, as that is a specific message from the destkop via
|
||||
// the server.
|
||||
switch (progressData.reason) {
|
||||
case "reject":
|
||||
this._handleCallRejected();
|
||||
switch(progressData.state) {
|
||||
case "connecting": {
|
||||
this._handleCallConnecting();
|
||||
break;
|
||||
}
|
||||
case "terminated": {
|
||||
// At the moment, we show the same text regardless
|
||||
// of the terminated reason.
|
||||
this._handleCallTerminated(progressData.reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles call rejection.
|
||||
* XXX This should really display the call failed view - bug 1046959
|
||||
* will implement this.
|
||||
* Handles a call moving to the connecting stage.
|
||||
*/
|
||||
_handleCallRejected: function() {
|
||||
_handleCallConnecting: function() {
|
||||
var loopToken = this._conversation.get("loopToken");
|
||||
if (!loopToken) {
|
||||
this._notifications.errorL10n("missing_conversation_info");
|
||||
return;
|
||||
}
|
||||
|
||||
this.navigate("call/ongoing/" + loopToken, {
|
||||
trigger: true
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles call rejection.
|
||||
*
|
||||
* @param {String} reason The reason the call was terminated.
|
||||
*/
|
||||
_handleCallTerminated: function(reason) {
|
||||
this.endCall();
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
// For reasons other than cancel, display some notification text.
|
||||
if (reason !== "cancel") {
|
||||
// XXX This should really display the call failed view - bug 1046959
|
||||
// will implement this.
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -501,10 +587,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
this.navigate(route, {trigger: true});
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
* Default entry point.
|
||||
*/
|
||||
|
@ -549,6 +631,17 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
this.loadReactComponent(startView);
|
||||
},
|
||||
|
||||
pendingConversation: function(loopToken) {
|
||||
if (!this._conversation.isSessionReady()) {
|
||||
// User has loaded this url directly, actually setup the call.
|
||||
return this.navigate("call/" + loopToken, {trigger: true});
|
||||
}
|
||||
this._setupWebSocketAndCallView();
|
||||
this.loadReactComponent(PendingConversationView({
|
||||
websocket: this._websocket
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads conversation establishment view.
|
||||
*
|
||||
|
@ -596,8 +689,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
notifications: new sharedModels.NotificationCollection(),
|
||||
client: client,
|
||||
conversation: new sharedModels.ConversationModel({}, {
|
||||
sdk: OT,
|
||||
pendingCallTimeout: loop.config.pendingCallTimeout
|
||||
sdk: OT
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -616,6 +708,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
return {
|
||||
baseServerUrl: baseServerUrl,
|
||||
CallUrlExpiredView: CallUrlExpiredView,
|
||||
PendingConversationView: PendingConversationView,
|
||||
StartConversationView: StartConversationView,
|
||||
HomeView: HomeView,
|
||||
UnsupportedBrowserView: UnsupportedBrowserView,
|
||||
|
|
|
@ -36,7 +36,7 @@ initiate_call_button_label2=Ready to start your conversation?
|
|||
initiate_audio_video_call_button2=Start
|
||||
initiate_audio_video_call_tooltip2=Start a video conversation
|
||||
initiate_audio_call_button2=Voice conversation
|
||||
reject_incoming_call=Cancel
|
||||
initiate_call_cancel_button=Cancel
|
||||
legal_text_and_links=By using this product you agree to the {{terms_of_use_url}} and {{privacy_notice_url}}
|
||||
terms_of_use_link_text=Terms of use
|
||||
privacy_notice_link_text=Privacy notice
|
||||
|
|
|
@ -15,8 +15,7 @@ app.get('/content/config.js', function (req, res) {
|
|||
res.send(
|
||||
"var loop = loop || {};" +
|
||||
"loop.config = loop.config || {};" +
|
||||
"loop.config.serverUrl = 'http://localhost:" + loopServerPort + "';" +
|
||||
"loop.config.pendingCallTimeout = 20000;"
|
||||
"loop.config.serverUrl = 'http://localhost:" + loopServerPort + "';"
|
||||
);
|
||||
|
||||
});
|
||||
|
|
|
@ -242,7 +242,7 @@ describe("loop.conversation", function() {
|
|||
});
|
||||
});
|
||||
|
||||
it("should create the view with video.enabled=false", function(done) {
|
||||
it("should create the view with video=false", function(done) {
|
||||
sandbox.stub(conversation, "get").withArgs("callType").returns("audio");
|
||||
|
||||
router._setupWebSocketAndCallView();
|
||||
|
@ -252,7 +252,7 @@ describe("loop.conversation", function() {
|
|||
sinon.assert.calledOnce(loop.conversation.IncomingCallView);
|
||||
sinon.assert.calledWithExactly(loop.conversation.IncomingCallView,
|
||||
{model: conversation,
|
||||
video: {enabled: false}});
|
||||
video: false});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -584,11 +584,104 @@ describe("loop.conversation", function() {
|
|||
var Model = Backbone.Model.extend({});
|
||||
model = new Model();
|
||||
sandbox.spy(model, "trigger");
|
||||
sandbox.stub(model, "set");
|
||||
|
||||
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
|
||||
model: model
|
||||
model: model,
|
||||
video: true
|
||||
}));
|
||||
});
|
||||
|
||||
describe("default answer mode", function() {
|
||||
it("should display video as primary answer mode", function() {
|
||||
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
|
||||
model: model,
|
||||
video: true
|
||||
}));
|
||||
var primaryBtn = view.getDOMNode()
|
||||
.querySelector('.fx-embedded-btn-icon-video');
|
||||
|
||||
expect(primaryBtn).not.to.eql(null);
|
||||
});
|
||||
|
||||
it("should display audio as primary answer mode", function() {
|
||||
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
|
||||
model: model,
|
||||
video: false
|
||||
}));
|
||||
var primaryBtn = view.getDOMNode()
|
||||
.querySelector('.fx-embedded-btn-icon-audio');
|
||||
|
||||
expect(primaryBtn).not.to.eql(null);
|
||||
});
|
||||
|
||||
it("should accept call with video", function() {
|
||||
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
|
||||
model: model,
|
||||
video: true
|
||||
}));
|
||||
var primaryBtn = view.getDOMNode()
|
||||
.querySelector('.fx-embedded-btn-icon-video');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(primaryBtn);
|
||||
|
||||
sinon.assert.calledOnce(model.set);
|
||||
sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio-video");
|
||||
sinon.assert.calledOnce(model.trigger);
|
||||
sinon.assert.calledWithExactly(model.trigger, "accept");
|
||||
});
|
||||
|
||||
it("should accept call with audio", function() {
|
||||
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
|
||||
model: model,
|
||||
video: false
|
||||
}));
|
||||
var primaryBtn = view.getDOMNode()
|
||||
.querySelector('.fx-embedded-btn-icon-audio');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(primaryBtn);
|
||||
|
||||
sinon.assert.calledOnce(model.set);
|
||||
sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio");
|
||||
sinon.assert.calledOnce(model.trigger);
|
||||
sinon.assert.calledWithExactly(model.trigger, "accept");
|
||||
});
|
||||
|
||||
it("should accept call with video when clicking on secondary btn",
|
||||
function() {
|
||||
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
|
||||
model: model,
|
||||
video: false
|
||||
}));
|
||||
var secondaryBtn = view.getDOMNode()
|
||||
.querySelector('.fx-embedded-btn-video-small');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(secondaryBtn);
|
||||
|
||||
sinon.assert.calledOnce(model.set);
|
||||
sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio-video");
|
||||
sinon.assert.calledOnce(model.trigger);
|
||||
sinon.assert.calledWithExactly(model.trigger, "accept");
|
||||
});
|
||||
|
||||
it("should accept call with audio when clicking on secondary btn",
|
||||
function() {
|
||||
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
|
||||
model: model,
|
||||
video: true
|
||||
}));
|
||||
var secondaryBtn = view.getDOMNode()
|
||||
.querySelector('.fx-embedded-btn-audio-small');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(secondaryBtn);
|
||||
|
||||
sinon.assert.calledOnce(model.set);
|
||||
sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio");
|
||||
sinon.assert.calledOnce(model.trigger);
|
||||
sinon.assert.calledWithExactly(model.trigger, "accept");
|
||||
});
|
||||
});
|
||||
|
||||
describe("click event on .btn-accept", function() {
|
||||
it("should trigger an 'accept' conversation model event", function () {
|
||||
var buttonAccept = view.getDOMNode().querySelector(".btn-accept");
|
||||
|
@ -601,7 +694,6 @@ describe("loop.conversation", function() {
|
|||
|
||||
it("should set selectedCallType to audio-video", function () {
|
||||
var buttonAccept = view.getDOMNode().querySelector(".btn-accept");
|
||||
sandbox.stub(model, "set");
|
||||
|
||||
TestUtils.Simulate.click(buttonAccept);
|
||||
|
||||
|
@ -611,29 +703,6 @@ describe("loop.conversation", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("click event on .call-audio-only", function() {
|
||||
|
||||
it("should trigger an 'accept' conversation model event", function () {
|
||||
var buttonAccept = view.getDOMNode().querySelector(".call-audio-only");
|
||||
model.trigger.withArgs("accept");
|
||||
TestUtils.Simulate.click(buttonAccept);
|
||||
|
||||
/* Setting a model property triggers 2 events */
|
||||
sinon.assert.calledOnce(model.trigger.withArgs("accept"));
|
||||
});
|
||||
|
||||
|
||||
it("should set selectedCallType to audio", function() {
|
||||
var buttonAccept = view.getDOMNode().querySelector(".call-audio-only");
|
||||
sandbox.stub(model, "set");
|
||||
|
||||
TestUtils.Simulate.click(buttonAccept);
|
||||
|
||||
sinon.assert.calledOnce(model.set);
|
||||
sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio");
|
||||
});
|
||||
});
|
||||
|
||||
describe("click event on .btn-decline", function() {
|
||||
it("should trigger an 'decline' conversation model event", function() {
|
||||
var buttonDecline = view.getDOMNode().querySelector(".btn-decline");
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
</script>
|
||||
|
||||
<!-- App scripts -->
|
||||
<script src="../../content/shared/js/utils.js"></script>
|
||||
<script src="../../content/shared/js/models.js"></script>
|
||||
<script src="../../content/shared/js/mixins.js"></script>
|
||||
<script src="../../content/shared/js/views.js"></script>
|
||||
|
@ -43,6 +44,7 @@
|
|||
<!-- Test scripts -->
|
||||
<script src="models_test.js"></script>
|
||||
<script src="mixins_test.js"></script>
|
||||
<script src="utils_test.js"></script>
|
||||
<script src="views_test.js"></script>
|
||||
<script src="router_test.js"></script>
|
||||
<script src="websocket_test.js"></script>
|
||||
|
|
|
@ -53,13 +53,6 @@ describe("loop.shared.models", function() {
|
|||
new sharedModels.ConversationModel({}, {});
|
||||
}).to.Throw(Error, /missing required sdk/);
|
||||
});
|
||||
|
||||
it("should accept a pendingCallTimeout option", function() {
|
||||
expect(new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
}).pendingCallTimeout).eql(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("constructed", function() {
|
||||
|
@ -68,8 +61,7 @@ describe("loop.shared.models", function() {
|
|||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
sdk: fakeSDK
|
||||
});
|
||||
conversation.set("loopToken", "fakeToken");
|
||||
fakeBaseServerUrl = "http://fakeBaseServerUrl";
|
||||
|
@ -121,25 +113,6 @@ describe("loop.shared.models", function() {
|
|||
|
||||
conversation.outgoing();
|
||||
});
|
||||
|
||||
it("should end the session on outgoing call timeout", function() {
|
||||
conversation.outgoing();
|
||||
|
||||
sandbox.clock.tick(1001);
|
||||
|
||||
sinon.assert.calledOnce(conversation.endSession);
|
||||
});
|
||||
|
||||
it("should trigger a `timeout` event on outgoing call timeout",
|
||||
function(done) {
|
||||
conversation.once("timeout", function() {
|
||||
done();
|
||||
});
|
||||
|
||||
conversation.outgoing();
|
||||
|
||||
sandbox.clock.tick(1001);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setSessionData", function() {
|
||||
|
@ -168,11 +141,8 @@ describe("loop.shared.models", function() {
|
|||
var model;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(sharedModels.ConversationModel.prototype,
|
||||
"_clearPendingCallTimer");
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
sdk: fakeSDK
|
||||
});
|
||||
model.startSession();
|
||||
});
|
||||
|
@ -281,18 +251,6 @@ describe("loop.shared.models", function() {
|
|||
expect(model.get("ongoing")).eql(false);
|
||||
});
|
||||
|
||||
it("should clear a pending timer on session:ended", function() {
|
||||
model.trigger("session:ended");
|
||||
|
||||
sinon.assert.calledOnce(model._clearPendingCallTimer);
|
||||
});
|
||||
|
||||
it("should clear a pending timer on session:error", function() {
|
||||
model.trigger("session:error");
|
||||
|
||||
sinon.assert.calledOnce(model._clearPendingCallTimer);
|
||||
});
|
||||
|
||||
describe("connectionDestroyed event received", function() {
|
||||
var fakeEvent = {reason: "ko", connection: {connectionId: 42}};
|
||||
|
||||
|
@ -341,8 +299,7 @@ describe("loop.shared.models", function() {
|
|||
|
||||
beforeEach(function() {
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
sdk: fakeSDK
|
||||
});
|
||||
model.startSession();
|
||||
});
|
||||
|
@ -381,8 +338,7 @@ describe("loop.shared.models", function() {
|
|||
|
||||
beforeEach(function() {
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
sdk: fakeSDK
|
||||
});
|
||||
model.startSession();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* global loop, sinon */
|
||||
/* jshint newcap:false */
|
||||
|
||||
var expect = chai.expect;
|
||||
|
||||
describe("loop.shared.utils", function() {
|
||||
"use strict";
|
||||
|
||||
var sandbox;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe("#getBoolPreference", function() {
|
||||
afterEach(function() {
|
||||
navigator.mozLoop = undefined;
|
||||
localStorage.removeItem("test.true");
|
||||
});
|
||||
|
||||
describe("mozLoop set", function() {
|
||||
beforeEach(function() {
|
||||
navigator.mozLoop = {
|
||||
getLoopBoolPref: function(prefName) {
|
||||
return prefName === "test.true";
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it("should return the mozLoop preference", function() {
|
||||
expect(sharedUtils.getBoolPreference("test.true")).eql(true);
|
||||
});
|
||||
|
||||
it("should not use the localStorage value", function() {
|
||||
localStorage.setItem("test.false", true);
|
||||
|
||||
expect(sharedUtils.getBoolPreference("test.false")).eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mozLoop not set", function() {
|
||||
it("should return the localStorage value", function() {
|
||||
localStorage.setItem("test.true", true);
|
||||
|
||||
expect(sharedUtils.getBoolPreference("test.true")).eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -176,6 +176,22 @@ describe("loop.CallConnectionWebSocket", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#cancel", function() {
|
||||
it("should send a terminate message to the server with a reason of cancel",
|
||||
function() {
|
||||
callWebSocket.promiseConnect();
|
||||
|
||||
callWebSocket.cancel();
|
||||
|
||||
sinon.assert.calledOnce(dummySocket.send);
|
||||
sinon.assert.calledWithExactly(dummySocket.send, JSON.stringify({
|
||||
messageType: "action",
|
||||
event: "terminate",
|
||||
reason: "cancel"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(callWebSocket, "trigger");
|
||||
|
@ -195,9 +211,24 @@ describe("loop.CallConnectionWebSocket", function() {
|
|||
data: JSON.stringify(eventData)
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(callWebSocket.trigger);
|
||||
sinon.assert.called(callWebSocket.trigger);
|
||||
sinon.assert.calledWithExactly(callWebSocket.trigger, "progress", eventData);
|
||||
});
|
||||
|
||||
it("should trigger a progress:<state> event on the callWebSocket", function() {
|
||||
var eventData = {
|
||||
messageType: "progress",
|
||||
state: "terminate",
|
||||
reason: "reject"
|
||||
};
|
||||
|
||||
dummySocket.onmessage({
|
||||
data: JSON.stringify(eventData)
|
||||
});
|
||||
|
||||
sinon.assert.called(callWebSocket.trigger);
|
||||
sinon.assert.calledWithExactly(callWebSocket.trigger, "progress:terminate");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Error", function() {
|
||||
|
|
|
@ -88,6 +88,14 @@ describe("loop.webapp", function() {
|
|||
sandbox.stub(router, "navigate");
|
||||
});
|
||||
|
||||
describe("#initialize", function() {
|
||||
it("should require a conversation option", function() {
|
||||
expect(function() {
|
||||
new loop.webapp.WebappRouter();
|
||||
}).to.Throw(Error, /missing required conversation/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#startCall", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(router, "_setupWebSocketAndCallView");
|
||||
|
@ -109,14 +117,15 @@ describe("loop.webapp", function() {
|
|||
"missing_conversation_info");
|
||||
});
|
||||
|
||||
it("should setup the websocket if session token is available", function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
it("should navigate to the pending view if session token is available",
|
||||
function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
|
||||
router.startCall();
|
||||
router.startCall();
|
||||
|
||||
sinon.assert.calledOnce(router._setupWebSocketAndCallView);
|
||||
sinon.assert.calledWithExactly(router._setupWebSocketAndCallView, "fake");
|
||||
});
|
||||
sinon.assert.calledOnce(router.navigate);
|
||||
sinon.assert.calledWithMatch(router.navigate, "call/pending/fake");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#_setupWebSocketAndCallView", function() {
|
||||
|
@ -126,7 +135,7 @@ describe("loop.webapp", function() {
|
|||
sessionToken: "sessionToken",
|
||||
apiKey: "apiKey",
|
||||
callId: "Hello",
|
||||
progressURL: "http://progress.example.com",
|
||||
progressURL: "http://invalid/url",
|
||||
websocketToken: 123
|
||||
});
|
||||
});
|
||||
|
@ -154,23 +163,13 @@ describe("loop.webapp", function() {
|
|||
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
|
||||
sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
|
||||
callId: "Hello",
|
||||
url: "http://progress.example.com",
|
||||
url: "http://invalid/url",
|
||||
// The websocket token is converted to a hex string.
|
||||
websocketToken: "7b"
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should navigate to call/ongoing/:token", function(done) {
|
||||
router._setupWebSocketAndCallView("fake");
|
||||
|
||||
promise.then(function () {
|
||||
sinon.assert.calledOnce(router.navigate);
|
||||
sinon.assert.calledWithMatch(router.navigate, "call/ongoing/fake");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Websocket connection failed", function() {
|
||||
|
@ -226,6 +225,7 @@ describe("loop.webapp", function() {
|
|||
describe("state: terminate, reason: reject", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(router, "endCall");
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
});
|
||||
|
||||
it("should end the call", function() {
|
||||
|
@ -237,17 +237,39 @@ describe("loop.webapp", function() {
|
|||
sinon.assert.calledOnce(router.endCall);
|
||||
});
|
||||
|
||||
it("should display an error message", function() {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
it("should display an error message if the reason is not 'cancel'",
|
||||
function() {
|
||||
router._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "reject"
|
||||
});
|
||||
|
||||
router._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "reject"
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifications.errorL10n,
|
||||
"call_timeout_notification_text");
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(router._notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifications.errorL10n,
|
||||
"call_timeout_notification_text");
|
||||
it("should not display an error message if the reason is 'cancel'",
|
||||
function() {
|
||||
router._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "cancel"
|
||||
});
|
||||
|
||||
sinon.assert.notCalled(notifications.errorL10n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("state: connecting", function() {
|
||||
it("should navigate to the ongoing view", function() {
|
||||
conversation.set({"loopToken": "fakeToken"});
|
||||
|
||||
router._websocket.trigger("progress", {
|
||||
state: "connecting"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(router.navigate);
|
||||
sinon.assert.calledWithMatch(router.navigate, "call/ongoing/fake");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -334,6 +356,38 @@ describe("loop.webapp", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#pendingConversation", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(router, "_setupWebSocketAndCallView");
|
||||
conversation.setOutgoingSessionData({
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
apiKey: "apiKey",
|
||||
callId: "Hello",
|
||||
progressURL: "http://progress.example.com",
|
||||
websocketToken: 123
|
||||
});
|
||||
});
|
||||
|
||||
it("should setup the websocket", function() {
|
||||
router.pendingConversation();
|
||||
|
||||
sinon.assert.calledOnce(router._setupWebSocketAndCallView);
|
||||
sinon.assert.calledWithExactly(router._setupWebSocketAndCallView);
|
||||
});
|
||||
|
||||
it("should load the PendingConversationView", function() {
|
||||
router.pendingConversation();
|
||||
|
||||
sinon.assert.calledOnce(router.loadReactComponent);
|
||||
sinon.assert.calledWith(router.loadReactComponent,
|
||||
sinon.match(function(value) {
|
||||
return React.addons.TestUtils.isDescriptorOfType(
|
||||
value, loop.webapp.PendingConversationView);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("#loadConversation", function() {
|
||||
it("should load the ConversationView if session is set", function() {
|
||||
conversation.set("sessionId", "fakeSessionId");
|
||||
|
@ -548,15 +602,46 @@ describe("loop.webapp", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("StartConversationView", function() {
|
||||
describe("#initialize", function() {
|
||||
it("should require a conversation option", function() {
|
||||
expect(function() {
|
||||
new loop.webapp.WebappRouter();
|
||||
}).to.Throw(Error, /missing required conversation/);
|
||||
describe("PendingConversationView", function() {
|
||||
var view, websocket;
|
||||
|
||||
beforeEach(function() {
|
||||
websocket = new loop.CallConnectionWebSocket({
|
||||
url: "wss://fake/",
|
||||
callId: "callId",
|
||||
websocketToken: "7b"
|
||||
});
|
||||
|
||||
sinon.stub(websocket, "cancel");
|
||||
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.PendingConversationView({
|
||||
websocket: websocket
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe("#_cancelOutgoingCall", function() {
|
||||
it("should inform the websocket to cancel the setup", function() {
|
||||
var button = view.getDOMNode().querySelector(".btn-cancel");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
sinon.assert.calledOnce(websocket.cancel);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
describe("progress:alerting", function() {
|
||||
it("should update the callstate to ringing", function () {
|
||||
websocket.trigger("progress:alerting");
|
||||
|
||||
expect(view.state.callState).to.be.equal("ringing");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("StartConversationView", function() {
|
||||
describe("#initiate", function() {
|
||||
var conversation, setupOutgoingCall, view, fakeSubmitEvent,
|
||||
requestCallUrlInfo;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
|
||||
var PendingConversationView = loop.webapp.PendingConversationView;
|
||||
var StartConversationView = loop.webapp.StartConversationView;
|
||||
|
||||
// 3. Shared components
|
||||
|
@ -134,9 +135,17 @@
|
|||
),
|
||||
|
||||
Section({name: "IncomingCallView"},
|
||||
Example({summary: "Default", dashed: "true", style: {width: "280px"}},
|
||||
Example({summary: "Default / incoming video call", dashed: "true", style: {width: "280px"}},
|
||||
React.DOM.div({className: "fx-embedded"},
|
||||
IncomingCallView({model: mockConversationModel})
|
||||
IncomingCallView({model: mockConversationModel,
|
||||
video: {enabled: true}})
|
||||
)
|
||||
),
|
||||
|
||||
Example({summary: "Default / incoming audio only call", dashed: "true", style: {width: "280px"}},
|
||||
React.DOM.div({className: "fx-embedded"},
|
||||
IncomingCallView({model: mockConversationModel,
|
||||
video: {enabled: false}})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
@ -144,7 +153,9 @@
|
|||
Section({name: "IncomingCallView-ActiveState"},
|
||||
Example({summary: "Default", dashed: "true", style: {width: "280px"}},
|
||||
React.DOM.div({className: "fx-embedded"},
|
||||
IncomingCallView({model: mockConversationModel, showDeclineMenu: true})
|
||||
IncomingCallView({model: mockConversationModel,
|
||||
showDeclineMenu: true,
|
||||
video: {enabled: true}})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
@ -195,6 +206,19 @@
|
|||
)
|
||||
),
|
||||
|
||||
Section({name: "PendingConversationView"},
|
||||
Example({summary: "Pending conversation view (connecting)", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
PendingConversationView(null)
|
||||
)
|
||||
),
|
||||
Example({summary: "Pending conversation view (ringing)", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
PendingConversationView({callState: "ringing"})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
Section({name: "StartConversationView"},
|
||||
Example({summary: "Start conversation view", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
|
||||
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
|
||||
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
|
||||
var PendingConversationView = loop.webapp.PendingConversationView;
|
||||
var StartConversationView = loop.webapp.StartConversationView;
|
||||
|
||||
// 3. Shared components
|
||||
|
@ -134,9 +135,17 @@
|
|||
</Section>
|
||||
|
||||
<Section name="IncomingCallView">
|
||||
<Example summary="Default" dashed="true" style={{width: "280px"}}>
|
||||
<Example summary="Default / incoming video call" dashed="true" style={{width: "280px"}}>
|
||||
<div className="fx-embedded">
|
||||
<IncomingCallView model={mockConversationModel} />
|
||||
<IncomingCallView model={mockConversationModel}
|
||||
video={{enabled: true}} />
|
||||
</div>
|
||||
</Example>
|
||||
|
||||
<Example summary="Default / incoming audio only call" dashed="true" style={{width: "280px"}}>
|
||||
<div className="fx-embedded">
|
||||
<IncomingCallView model={mockConversationModel}
|
||||
video={{enabled: false}} />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
@ -144,7 +153,9 @@
|
|||
<Section name="IncomingCallView-ActiveState">
|
||||
<Example summary="Default" dashed="true" style={{width: "280px"}}>
|
||||
<div className="fx-embedded" >
|
||||
<IncomingCallView model={mockConversationModel} showDeclineMenu={true} />
|
||||
<IncomingCallView model={mockConversationModel}
|
||||
showDeclineMenu={true}
|
||||
video={{enabled: true}} />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
@ -195,6 +206,19 @@
|
|||
</div>
|
||||
</Section>
|
||||
|
||||
<Section name="PendingConversationView">
|
||||
<Example summary="Pending conversation view (connecting)" dashed="true">
|
||||
<div className="standalone">
|
||||
<PendingConversationView />
|
||||
</div>
|
||||
</Example>
|
||||
<Example summary="Pending conversation view (ringing)" dashed="true">
|
||||
<div className="standalone">
|
||||
<PendingConversationView callState="ringing"/>
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="StartConversationView">
|
||||
<Example summary="Start conversation view" dashed="true">
|
||||
<div className="standalone">
|
||||
|
|
|
@ -91,39 +91,6 @@ Tools.options = {
|
|||
}
|
||||
}
|
||||
|
||||
Tools.webConsole = {
|
||||
id: "webconsole",
|
||||
key: l10n("cmd.commandkey", webConsoleStrings),
|
||||
accesskey: l10n("webConsoleCmd.accesskey", webConsoleStrings),
|
||||
modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
|
||||
ordinal: 2,
|
||||
icon: "chrome://browser/skin/devtools/tool-webconsole.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://browser/content/devtools/webconsole.xul",
|
||||
label: l10n("ToolboxTabWebconsole.label", webConsoleStrings),
|
||||
menuLabel: l10n("MenuWebconsole.label", webConsoleStrings),
|
||||
panelLabel: l10n("ToolboxWebConsole.panelLabel", webConsoleStrings),
|
||||
tooltip: l10n("ToolboxWebconsole.tooltip", webConsoleStrings),
|
||||
inMenu: true,
|
||||
commands: "devtools/webconsole/console-commands",
|
||||
|
||||
preventClosingOnKey: true,
|
||||
onkey: function(panel, toolbox) {
|
||||
if (toolbox.splitConsole)
|
||||
return toolbox.focusConsoleInput();
|
||||
|
||||
panel.focusInput();
|
||||
},
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return true;
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
return new WebConsolePanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
Tools.inspector = {
|
||||
id: "inspector",
|
||||
accesskey: l10n("inspector.accesskey", inspectorStrings),
|
||||
|
@ -157,6 +124,39 @@ Tools.inspector = {
|
|||
}
|
||||
};
|
||||
|
||||
Tools.webConsole = {
|
||||
id: "webconsole",
|
||||
key: l10n("cmd.commandkey", webConsoleStrings),
|
||||
accesskey: l10n("webConsoleCmd.accesskey", webConsoleStrings),
|
||||
modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
|
||||
ordinal: 2,
|
||||
icon: "chrome://browser/skin/devtools/tool-webconsole.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://browser/content/devtools/webconsole.xul",
|
||||
label: l10n("ToolboxTabWebconsole.label", webConsoleStrings),
|
||||
menuLabel: l10n("MenuWebconsole.label", webConsoleStrings),
|
||||
panelLabel: l10n("ToolboxWebConsole.panelLabel", webConsoleStrings),
|
||||
tooltip: l10n("ToolboxWebconsole.tooltip", webConsoleStrings),
|
||||
inMenu: true,
|
||||
commands: "devtools/webconsole/console-commands",
|
||||
|
||||
preventClosingOnKey: true,
|
||||
onkey: function(panel, toolbox) {
|
||||
if (toolbox.splitConsole)
|
||||
return toolbox.focusConsoleInput();
|
||||
|
||||
panel.focusInput();
|
||||
},
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return true;
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
return new WebConsolePanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
Tools.jsdebugger = {
|
||||
id: "jsdebugger",
|
||||
key: l10n("debuggerMenu.commandkey", debuggerStrings),
|
||||
|
@ -248,26 +248,6 @@ Tools.canvasDebugger = {
|
|||
}
|
||||
};
|
||||
|
||||
Tools.webAudioEditor = {
|
||||
id: "webaudioeditor",
|
||||
ordinal: 10,
|
||||
visibilityswitch: "devtools.webaudioeditor.enabled",
|
||||
icon: "chrome://browser/skin/devtools/tool-webaudio.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://browser/content/devtools/webaudioeditor.xul",
|
||||
label: l10n("ToolboxWebAudioEditor1.label", webAudioEditorStrings),
|
||||
panelLabel: l10n("ToolboxWebAudioEditor1.panelLabel", webAudioEditorStrings),
|
||||
tooltip: l10n("ToolboxWebAudioEditor1.tooltip", webAudioEditorStrings),
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon;
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
return new WebAudioEditorPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
Tools.jsprofiler = {
|
||||
id: "jsprofiler",
|
||||
accesskey: l10n("profiler.accesskey", profilerStrings),
|
||||
|
@ -366,9 +346,29 @@ Tools.storage = {
|
|||
}
|
||||
};
|
||||
|
||||
Tools.webAudioEditor = {
|
||||
id: "webaudioeditor",
|
||||
ordinal: 11,
|
||||
visibilityswitch: "devtools.webaudioeditor.enabled",
|
||||
icon: "chrome://browser/skin/devtools/tool-webaudio.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://browser/content/devtools/webaudioeditor.xul",
|
||||
label: l10n("ToolboxWebAudioEditor1.label", webAudioEditorStrings),
|
||||
panelLabel: l10n("ToolboxWebAudioEditor1.panelLabel", webAudioEditorStrings),
|
||||
tooltip: l10n("ToolboxWebAudioEditor1.tooltip", webAudioEditorStrings),
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon;
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
return new WebAudioEditorPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
Tools.scratchpad = {
|
||||
id: "scratchpad",
|
||||
ordinal: 11,
|
||||
ordinal: 12,
|
||||
visibilityswitch: "devtools.scratchpad.enabled",
|
||||
icon: "chrome://browser/skin/devtools/tool-scratchpad.svg",
|
||||
invertIconForLightTheme: true,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
const CC = Components.Constructor;
|
||||
|
||||
// We also need a valid nsIXulAppInfo service as Webapps.jsm is querying it
|
||||
Cu.import("resource://testing-common/AppInfo.jsm");
|
||||
updateAppInfo();
|
||||
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let require = devtools.require;
|
|
@ -7,9 +7,6 @@
|
|||
|
||||
// Tests the BezierCanvas API in the CubicBezierWidget module
|
||||
|
||||
const Cu = Components.utils;
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let require = devtools.require;
|
||||
let {CubicBezier, BezierCanvas} = require("devtools/shared/widgets/CubicBezierWidget");
|
||||
|
||||
function run_test() {
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
|
||||
// Tests the CubicBezier API in the CubicBezierWidget module
|
||||
|
||||
const Cu = Components.utils;
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let require = devtools.require;
|
||||
let {CubicBezier} = require("devtools/shared/widgets/CubicBezierWidget");
|
||||
|
||||
function run_test() {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const Cu = Components.utils;
|
||||
let {Loader} = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
|
||||
|
||||
let loader = new Loader.Loader({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[DEFAULT]
|
||||
head =
|
||||
head = head.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
const CC = Components.Constructor;
|
||||
|
||||
// We also need a valid nsIXulAppInfo
|
||||
Cu.import("resource://testing-common/AppInfo.jsm");
|
||||
updateAppInfo();
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
|
@ -3,8 +3,6 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
const {parseDeclarations} = devtools.require("devtools/styleinspector/css-parsing-utils");
|
||||
|
||||
const TEST_DATA = [
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
const {parseSingleValue} = devtools.require("devtools/styleinspector/css-parsing-utils");
|
||||
|
||||
const TEST_DATA = [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[DEFAULT]
|
||||
head =
|
||||
head = head.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
|
||||
|
|
|
@ -132,10 +132,11 @@ let UI = {
|
|||
break;
|
||||
case "project":
|
||||
this.updateTitle();
|
||||
this.closeToolbox();
|
||||
this.destroyToolbox();
|
||||
this.updateCommands();
|
||||
this.updateProjectButton();
|
||||
this.openProject();
|
||||
this.autoStartProject();
|
||||
break;
|
||||
case "project-is-not-running":
|
||||
case "project-is-running":
|
||||
|
@ -481,6 +482,26 @@ let UI = {
|
|||
}, console.error);
|
||||
},
|
||||
|
||||
autoStartProject: function() {
|
||||
let project = AppManager.selectedProject;
|
||||
|
||||
if (!project) {
|
||||
return;
|
||||
}
|
||||
if (!(project.type == "runtimeApp" ||
|
||||
project.type == "mainProcess" ||
|
||||
project.type == "tab")) {
|
||||
return; // For something that is not an editable app, we're done.
|
||||
}
|
||||
|
||||
Task.spawn(function() {
|
||||
if (project.type == "runtimeApp") {
|
||||
yield UI.busyUntil(AppManager.runRuntimeApp(), "running app");
|
||||
}
|
||||
yield UI.createToolbox();
|
||||
});
|
||||
},
|
||||
|
||||
/********** DECK **********/
|
||||
|
||||
setupDeck: function() {
|
||||
|
@ -629,7 +650,7 @@ let UI = {
|
|||
} catch(e) { console.error(e); }
|
||||
},
|
||||
|
||||
closeToolbox: function() {
|
||||
destroyToolbox: function() {
|
||||
if (this.toolboxPromise) {
|
||||
this.toolboxPromise.then(toolbox => {
|
||||
toolbox.destroy();
|
||||
|
@ -638,6 +659,13 @@ let UI = {
|
|||
}
|
||||
},
|
||||
|
||||
createToolbox: function() {
|
||||
this.toolboxPromise = AppManager.getTarget().then((target) => {
|
||||
return this.showToolbox(target);
|
||||
}, console.error);
|
||||
return this.busyUntil(this.toolboxPromise, "opening toolbox");
|
||||
},
|
||||
|
||||
showToolbox: function(target) {
|
||||
if (this.toolboxIframe) {
|
||||
return;
|
||||
|
@ -999,14 +1027,10 @@ let Cmds = {
|
|||
|
||||
toggleToolbox: function() {
|
||||
if (UI.toolboxIframe) {
|
||||
UI.closeToolbox();
|
||||
UI.destroyToolbox();
|
||||
return promise.resolve();
|
||||
} else {
|
||||
UI.toolboxPromise = AppManager.getTarget().then((target) => {
|
||||
return UI.showToolbox(target);
|
||||
}, console.error);
|
||||
UI.busyUntil(UI.toolboxPromise, "opening toolbox");
|
||||
return UI.toolboxPromise;
|
||||
return UI.createToolbox();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -333,9 +333,6 @@ exports.AppManager = AppManager = {
|
|||
this.tabStore.selectedTab = null;
|
||||
|
||||
if (this.selectedProject) {
|
||||
if (this.selectedProject.type == "runtimeApp") {
|
||||
this.runRuntimeApp();
|
||||
}
|
||||
if (this.selectedProject.type == "packaged" ||
|
||||
this.selectedProject.type == "hosted") {
|
||||
this.validateProject(this.selectedProject);
|
||||
|
|
|
@ -58,6 +58,8 @@ TabStore.prototype = {
|
|||
_resetStore: function() {
|
||||
this.response = null;
|
||||
this.tabs = [];
|
||||
this._selectedTab = null;
|
||||
this._selectedTabTargetPromise = null;
|
||||
},
|
||||
|
||||
_onStatusChanged: function() {
|
||||
|
@ -115,6 +117,7 @@ TabStore.prototype = {
|
|||
// which is the selected project. This should be done as part of the
|
||||
// project-agnostic work.
|
||||
_selectedTab: null,
|
||||
_selectedTabTargetPromise: null,
|
||||
get selectedTab() {
|
||||
return this._selectedTab;
|
||||
},
|
||||
|
@ -134,13 +137,18 @@ TabStore.prototype = {
|
|||
return tab.actor === this._selectedTab.actor;
|
||||
});
|
||||
if (!alive) {
|
||||
this._selectedTab = null;
|
||||
this._selectedTabTargetPromise = null;
|
||||
this.emit("closed");
|
||||
}
|
||||
},
|
||||
|
||||
getTargetForTab: function() {
|
||||
if (this._selectedTabTargetPromise) {
|
||||
return this._selectedTabTargetPromise;
|
||||
}
|
||||
let store = this;
|
||||
return Task.spawn(function*() {
|
||||
this._selectedTabTargetPromise = Task.spawn(function*() {
|
||||
// If you connect to a tab, then detach from it, the root actor may have
|
||||
// de-listed the actors that belong to the tab. This breaks the toolbox
|
||||
// if you try to connect to the same tab again. To work around this
|
||||
|
@ -152,6 +160,7 @@ TabStore.prototype = {
|
|||
chrome: false
|
||||
});
|
||||
});
|
||||
return this._selectedTabTargetPromise;
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -101,6 +101,18 @@ function nextTick() {
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
function waitForUpdate(win, update) {
|
||||
let deferred = promise.defer();
|
||||
win.AppManager.on("app-manager-update", function onUpdate(e, what) {
|
||||
if (what !== update) {
|
||||
return;
|
||||
}
|
||||
win.AppManager.off("app-manager-update", onUpdate);
|
||||
deferred.resolve();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function documentIsLoaded(doc) {
|
||||
let deferred = promise.defer();
|
||||
if (doc.readyState == "complete") {
|
||||
|
|
|
@ -95,17 +95,28 @@
|
|||
ok(!isPlayActive(), "play button is disabled 4");
|
||||
ok(!isStopActive(), "stop button is disabled 4");
|
||||
|
||||
deferred = promise.defer();
|
||||
win.AppManager.connection.once(
|
||||
win.Connection.Events.CONNECTED,
|
||||
() => deferred.resolve());
|
||||
|
||||
win.document.querySelectorAll(".runtime-panel-item-custom")[1].click();
|
||||
|
||||
yield deferred.promise;
|
||||
yield waitForUpdate(win, "list-tabs-response");
|
||||
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
|
||||
|
||||
ok(win.AppManager.isMainProcessDebuggable(), "Main process available");
|
||||
|
||||
// Select main process
|
||||
yield win.Cmds.showProjectPanel();
|
||||
SimpleTest.executeSoon(() => {
|
||||
win.document.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click();
|
||||
});
|
||||
|
||||
yield waitForUpdate(win, "project");
|
||||
|
||||
// Toolbox opens automatically for main process / runtime apps
|
||||
ok(win.UI.toolboxPromise, "Toolbox promise exists");
|
||||
yield win.UI.toolboxPromise;
|
||||
|
||||
ok(win.UI.toolboxIframe, "Toolbox iframe exists");
|
||||
|
||||
yield win.Cmds.disconnectRuntime();
|
||||
|
||||
yield closeWebIDE(win);
|
||||
|
|
|
@ -174,6 +174,7 @@ initiate_call_button_label2=Ready to start your conversation?
|
|||
incoming_call_title2=Conversation Request
|
||||
incoming_call_accept_button=Accept
|
||||
incoming_call_accept_audio_only_tooltip=Accept with voice
|
||||
incoming_call_accept_audio_video_tooltip=Accept with video
|
||||
incoming_call_cancel_button=Cancel
|
||||
incoming_call_cancel_and_block_button=Cancel and Block
|
||||
incoming_call_block_button=Block
|
||||
|
|
|
@ -647,6 +647,28 @@ nsDOMCameraControl::Capabilities()
|
|||
return caps.forget();
|
||||
}
|
||||
|
||||
class ImmediateErrorCallback : public nsRunnable
|
||||
{
|
||||
public:
|
||||
ImmediateErrorCallback(CameraErrorCallback* aCallback, const nsAString& aMessage)
|
||||
: mCallback(aCallback)
|
||||
, mMessage(aMessage)
|
||||
{ }
|
||||
|
||||
NS_IMETHODIMP
|
||||
Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ErrorResult ignored;
|
||||
mCallback->Call(mMessage, ignored);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
protected:
|
||||
nsRefPtr<CameraErrorCallback> mCallback;
|
||||
nsString mMessage;
|
||||
};
|
||||
|
||||
// Methods.
|
||||
void
|
||||
nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
|
||||
|
@ -658,6 +680,20 @@ nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
|
|||
{
|
||||
MOZ_ASSERT(mCameraControl);
|
||||
|
||||
nsRefPtr<CameraStartRecordingCallback> cb = mStartRecordingOnSuccessCb;
|
||||
if (cb) {
|
||||
if (aOnError.WasPassed()) {
|
||||
DOM_CAMERA_LOGT("%s:onError WasPassed\n", __func__);
|
||||
NS_DispatchToMainThread(new ImmediateErrorCallback(&aOnError.Value(),
|
||||
NS_LITERAL_STRING("StartRecordingInProgress")));
|
||||
} else {
|
||||
DOM_CAMERA_LOGT("%s:onError NS_ERROR_FAILURE\n", __func__);
|
||||
// Only throw if no error callback was passed in.
|
||||
aRv = NS_ERROR_FAILURE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
|
@ -745,28 +781,6 @@ nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
|
|||
aRv = mCameraControl->StartPreview();
|
||||
}
|
||||
|
||||
class ImmediateErrorCallback : public nsRunnable
|
||||
{
|
||||
public:
|
||||
ImmediateErrorCallback(CameraErrorCallback* aCallback, const nsAString& aMessage)
|
||||
: mCallback(aCallback)
|
||||
, mMessage(aMessage)
|
||||
{ }
|
||||
|
||||
NS_IMETHODIMP
|
||||
Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ErrorResult ignored;
|
||||
mCallback->Call(mMessage, ignored);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
protected:
|
||||
nsRefPtr<CameraErrorCallback> mCallback;
|
||||
nsString mMessage;
|
||||
};
|
||||
|
||||
void
|
||||
nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration,
|
||||
const Optional<OwningNonNull<CameraSetConfigurationCallback> >& aOnSuccess,
|
||||
|
|
|
@ -2473,6 +2473,11 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
@Override
|
||||
public void openOptionsMenu() {
|
||||
// Disable menu access (for hardware buttons) when the software menu button is inaccessible.
|
||||
if (mBrowserToolbar.isEditing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (areTabsShown()) {
|
||||
mTabsPanel.showMenu();
|
||||
return;
|
||||
|
|
|
@ -13,12 +13,13 @@ import org.mozilla.gecko.db.BrowserDB;
|
|||
import org.mozilla.gecko.home.BookmarksListAdapter.FolderInfo;
|
||||
import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
|
||||
import org.mozilla.gecko.home.BookmarksListAdapter.RefreshType;
|
||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
|
@ -78,6 +79,7 @@ public class BookmarksPanel extends HomeFragment {
|
|||
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
|
||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE));
|
||||
info.bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
|
||||
info.itemType = RemoveItemType.BOOKMARKS;
|
||||
return info;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,8 +18,8 @@ import org.mozilla.gecko.TelemetryContract;
|
|||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
|
@ -95,6 +95,7 @@ public class HistoryPanel extends HomeFragment {
|
|||
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
|
||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
|
||||
info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
|
||||
info.itemType = RemoveItemType.HISTORY;
|
||||
final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
|
||||
if (cursor.isNull(bookmarkIdCol)) {
|
||||
// If this is a combined cursor, we may get a history item without a
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.mozilla.gecko.home;
|
|||
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelType;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
|
|
@ -24,6 +24,12 @@ public class HomeContextMenuInfo extends AdapterContextMenuInfo {
|
|||
public int historyId = -1;
|
||||
public int bookmarkId = -1;
|
||||
public int readingListItemId = -1;
|
||||
public RemoveItemType itemType = null;
|
||||
|
||||
// Item type to be handled with "Remove" selection.
|
||||
public static enum RemoveItemType {
|
||||
BOOKMARKS, HISTORY, READING_LIST
|
||||
}
|
||||
|
||||
public HomeContextMenuInfo(View targetView, int position, long id) {
|
||||
super(targetView, position, id);
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.mozilla.gecko.TelemetryContract;
|
|||
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
|
@ -248,15 +249,11 @@ public abstract class HomeFragment extends Fragment {
|
|||
}
|
||||
|
||||
if (itemId == R.id.home_remove) {
|
||||
if (info instanceof TopSitesGridContextMenuInfo) {
|
||||
(new RemoveItemByUrlTask(context, info.url, info.position)).execute();
|
||||
return true;
|
||||
}
|
||||
// For Top Sites grid items, position is required in case item is Pinned.
|
||||
final int position = info instanceof TopSitesGridContextMenuInfo ? info.position : -1;
|
||||
|
||||
if (info.isInReadingList() || info.hasBookmarkId() || info.hasHistoryId()) {
|
||||
(new RemoveItemByUrlTask(context, info.url)).execute();
|
||||
return true;
|
||||
}
|
||||
(new RemoveItemByUrlTask(context, info.url, info.itemType, position)).execute();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -316,7 +313,6 @@ public abstract class HomeFragment extends Fragment {
|
|||
return mCanLoadHint;
|
||||
}
|
||||
|
||||
|
||||
protected abstract void load();
|
||||
|
||||
protected boolean canLoad() {
|
||||
|
@ -332,27 +328,22 @@ public abstract class HomeFragment extends Fragment {
|
|||
mIsLoaded = true;
|
||||
}
|
||||
|
||||
private static class RemoveItemByUrlTask extends UIAsyncTask.WithoutParams<Void> {
|
||||
protected static class RemoveItemByUrlTask extends UIAsyncTask.WithoutParams<Void> {
|
||||
private final Context mContext;
|
||||
private final String mUrl;
|
||||
private final RemoveItemType mType;
|
||||
private final int mPosition;
|
||||
|
||||
/**
|
||||
* Remove bookmark/history/reading list item by url.
|
||||
*/
|
||||
public RemoveItemByUrlTask(Context context, String url) {
|
||||
this(context, url, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove bookmark/history/reading list item by url, and also unpin the
|
||||
* Remove bookmark/history/reading list type item by url, and also unpin the
|
||||
* Top Sites grid item at index <code>position</code>.
|
||||
*/
|
||||
public RemoveItemByUrlTask(Context context, String url, int position) {
|
||||
public RemoveItemByUrlTask(Context context, String url, RemoveItemType type, int position) {
|
||||
super(ThreadUtils.getBackgroundHandler());
|
||||
|
||||
mContext = context;
|
||||
mUrl = url;
|
||||
mType = type;
|
||||
mPosition = position;
|
||||
}
|
||||
|
||||
|
@ -367,22 +358,34 @@ public abstract class HomeFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
BrowserDB.removeBookmarksWithURL(cr, mUrl);
|
||||
BrowserDB.removeHistoryEntry(cr, mUrl);
|
||||
switch(mType) {
|
||||
case BOOKMARKS:
|
||||
BrowserDB.removeBookmarksWithURL(cr, mUrl);
|
||||
break;
|
||||
|
||||
BrowserDB.removeReadingListItemWithURL(cr, mUrl);
|
||||
case HISTORY:
|
||||
BrowserDB.removeHistoryEntry(cr, mUrl);
|
||||
break;
|
||||
|
||||
final JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("url", mUrl);
|
||||
json.put("notify", false);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "error building JSON arguments");
|
||||
case READING_LIST:
|
||||
BrowserDB.removeReadingListItemWithURL(cr, mUrl);
|
||||
|
||||
final JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("url", mUrl);
|
||||
json.put("notify", false);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "error building JSON arguments");
|
||||
}
|
||||
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", json.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e(LOGTAG, "Can't remove item type " + mType.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", json.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.mozilla.gecko.TelemetryContract;
|
|||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserContract.URLColumns;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -97,6 +98,7 @@ public class ReadingListPanel extends HomeFragment {
|
|||
info.url = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.URL));
|
||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.TITLE));
|
||||
info.readingListItemId = cursor.getInt(cursor.getColumnIndexOrThrow(ReadingListItems._ID));
|
||||
info.itemType = RemoveItemType.READING_LIST;
|
||||
return info;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,7 +12,6 @@ import org.mozilla.gecko.Telemetry;
|
|||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.ThumbnailHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract.TopSites;
|
||||
import org.mozilla.gecko.db.TopSitesCursorWrapper;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
|
@ -20,7 +19,6 @@ import android.content.Context;
|
|||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View;
|
||||
|
@ -277,6 +275,7 @@ public class TopSitesGridView extends GridView {
|
|||
|
||||
public TopSitesGridContextMenuInfo(View targetView, int position, long id) {
|
||||
super(targetView, position, id);
|
||||
this.itemType = RemoveItemType.HISTORY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.mozilla.gecko.db.URLMetadata;
|
|||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
|
||||
import org.mozilla.gecko.home.TopSitesGridView.OnEditPinnedSiteListener;
|
||||
|
@ -179,6 +180,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
info.url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
|
||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
|
||||
info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.HISTORY_ID));
|
||||
info.itemType = RemoveItemType.HISTORY;
|
||||
final int bookmarkIdCol = cursor.getColumnIndexOrThrow(TopSites.BOOKMARK_ID);
|
||||
if (cursor.isNull(bookmarkIdCol)) {
|
||||
// If this is a combined cursor, we may get a history item without a
|
||||
|
|
|
@ -100,6 +100,8 @@
|
|||
"to bookmark", not the noun "a bookmark". -->
|
||||
<!ENTITY overlay_share_bookmark_btn_label "Bookmark">
|
||||
<!ENTITY overlay_share_reading_list_btn_label "Add to Reading List">
|
||||
<!ENTITY overlay_share_bookmark_btn_label_already "Already bookmarked">
|
||||
<!ENTITY overlay_share_reading_list_btn_label_already "Already in Reading List">
|
||||
<!ENTITY overlay_share_send_other "Send to other devices">
|
||||
|
||||
<!-- Localization note (overlay_share_send_tab_btn_label) : Used on the
|
||||
|
|
|
@ -397,6 +397,7 @@ gbjar.sources += [
|
|||
'tabs/TabsLayoutItemView.java',
|
||||
'tabs/TabsListLayout.java',
|
||||
'tabs/TabsPanel.java',
|
||||
'tabs/TabStrip.java',
|
||||
'tabs/TabStripAdapter.java',
|
||||
'tabs/TabStripItemView.java',
|
||||
'tabs/TabStripView.java',
|
||||
|
@ -495,11 +496,13 @@ if CONFIG['MOZ_ANDROID_SHARE_OVERLAY']:
|
|||
gbjar.sources += [
|
||||
'overlays/OverlayConstants.java',
|
||||
'overlays/service/OverlayActionService.java',
|
||||
'overlays/service/ShareData.java',
|
||||
'overlays/service/sharemethods/AddBookmark.java',
|
||||
'overlays/service/sharemethods/AddToReadingList.java',
|
||||
'overlays/service/sharemethods/ParcelableClientRecord.java',
|
||||
'overlays/service/sharemethods/SendTab.java',
|
||||
'overlays/service/sharemethods/ShareMethod.java',
|
||||
'overlays/ui/OverlayDialogButton.java',
|
||||
'overlays/ui/OverlayToastHelper.java',
|
||||
'overlays/ui/SendTabDeviceListArrayAdapter.java',
|
||||
'overlays/ui/SendTabList.java',
|
||||
|
|
|
@ -48,7 +48,7 @@ public class OverlayActionService extends Service {
|
|||
private static final String LOGTAG = "GeckoOverlayService";
|
||||
|
||||
// Map used for selecting the appropriate helper object when handling a share.
|
||||
private final Map<ShareMethod.Type, ShareMethod> shareTypes = new EnumMap<>(ShareMethod.Type.class);
|
||||
final Map<ShareMethod.Type, ShareMethod> shareTypes = new EnumMap<>(ShareMethod.Type.class);
|
||||
|
||||
// Map relating Strings representing share types to the corresponding ShareMethods.
|
||||
// Share methods are initialised (and shown in the UI) in the order they are given here.
|
||||
|
@ -88,34 +88,34 @@ public class OverlayActionService extends Service {
|
|||
/**
|
||||
* Reinitialise all ShareMethods, causing them to broadcast any UI update events necessary.
|
||||
*/
|
||||
private void initShareMethods(Context context) {
|
||||
shareTypes.clear();
|
||||
private void initShareMethods(final Context context) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
shareTypes.clear();
|
||||
|
||||
shareTypes.put(ShareMethod.Type.ADD_BOOKMARK, new AddBookmark(context));
|
||||
shareTypes.put(ShareMethod.Type.ADD_TO_READING_LIST, new AddToReadingList(context));
|
||||
shareTypes.put(ShareMethod.Type.SEND_TAB, new SendTab(context));
|
||||
shareTypes.put(ShareMethod.Type.ADD_BOOKMARK, new AddBookmark(context));
|
||||
shareTypes.put(ShareMethod.Type.ADD_TO_READING_LIST, new AddToReadingList(context));
|
||||
shareTypes.put(ShareMethod.Type.SEND_TAB, new SendTab(context));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void handleShare(final Intent intent) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
// Fish the parameters out of the Intent.
|
||||
final String url = extras.getString(OverlayConstants.EXTRA_URL);
|
||||
final String title = extras.getString(OverlayConstants.EXTRA_TITLE);
|
||||
final Parcelable extra = extras.getParcelable(OverlayConstants.EXTRA_PARAMETERS);
|
||||
|
||||
if (url == null) {
|
||||
Log.e(LOGTAG, "Null url passed to handleShare!");
|
||||
ShareData shareData;
|
||||
try {
|
||||
shareData = ShareData.fromIntent(intent);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(LOGTAG, "Error parsing share intent: ", e);
|
||||
return;
|
||||
}
|
||||
|
||||
ShareMethod.Type shareMethodType = (ShareMethod.Type) extras.get(EXTRA_SHARE_METHOD);
|
||||
ShareMethod shareMethod = shareTypes.get(shareMethodType);
|
||||
ShareMethod shareMethod = shareTypes.get(shareData.shareMethodType);
|
||||
|
||||
final ShareMethod.Result result = shareMethod.handle(title, url, extra);
|
||||
final ShareMethod.Result result = shareMethod.handle(shareData);
|
||||
// Dispatch the share to the targeted ShareMethod.
|
||||
switch (result) {
|
||||
case SUCCESS:
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.overlays.service;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import org.mozilla.gecko.overlays.OverlayConstants;
|
||||
import org.mozilla.gecko.overlays.service.sharemethods.ShareMethod;
|
||||
|
||||
import static org.mozilla.gecko.overlays.OverlayConstants.EXTRA_SHARE_METHOD;
|
||||
|
||||
/**
|
||||
* Class to hold information related to a particular request to perform a share.
|
||||
*/
|
||||
public class ShareData {
|
||||
private static final String LOGTAG = "GeckoShareRequest";
|
||||
|
||||
public final String url;
|
||||
public final String title;
|
||||
public final Parcelable extra;
|
||||
public final ShareMethod.Type shareMethodType;
|
||||
|
||||
public ShareData(String url, String title, Parcelable extra, ShareMethod.Type shareMethodType) {
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("Null url passed to ShareData!");
|
||||
}
|
||||
|
||||
this.url = url;
|
||||
this.title = title;
|
||||
this.extra = extra;
|
||||
this.shareMethodType = shareMethodType;
|
||||
}
|
||||
|
||||
public static ShareData fromIntent(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
// Fish the parameters out of the Intent.
|
||||
final String url = extras.getString(OverlayConstants.EXTRA_URL);
|
||||
final String title = extras.getString(OverlayConstants.EXTRA_TITLE);
|
||||
final Parcelable extra = extras.getParcelable(OverlayConstants.EXTRA_PARAMETERS);
|
||||
ShareMethod.Type shareMethodType = (ShareMethod.Type) extras.get(EXTRA_SHARE_METHOD);
|
||||
|
||||
return new ShareData(url, title, extra, shareMethodType);
|
||||
}
|
||||
}
|
|
@ -6,20 +6,20 @@ package org.mozilla.gecko.overlays.service.sharemethods;
|
|||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.LocalBrowserDB;
|
||||
import org.mozilla.gecko.overlays.service.ShareData;
|
||||
|
||||
public class AddBookmark extends ShareMethod {
|
||||
private static final String LOGTAG = "GeckoAddBookmark";
|
||||
|
||||
@Override
|
||||
public Result handle(String title, String url, Parcelable unused) {
|
||||
public Result handle(ShareData shareData) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
LocalBrowserDB browserDB = new LocalBrowserDB(GeckoProfile.DEFAULT_PROFILE);
|
||||
browserDB.addBookmark(resolver, title, url);
|
||||
browserDB.addBookmark(resolver, shareData.title, shareData.url);
|
||||
|
||||
return Result.SUCCESS;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@ package org.mozilla.gecko.overlays.service.sharemethods;
|
|||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.LocalBrowserDB;
|
||||
import org.mozilla.gecko.overlays.service.ShareData;
|
||||
|
||||
import static org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
|
||||
|
@ -25,14 +25,14 @@ public class AddToReadingList extends ShareMethod {
|
|||
private static final String LOGTAG = "GeckoAddToReadingList";
|
||||
|
||||
@Override
|
||||
public Result handle(String title, String url, Parcelable unused) {
|
||||
public Result handle(ShareData shareData) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
LocalBrowserDB browserDB = new LocalBrowserDB(GeckoProfile.DEFAULT_PROFILE);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Bookmarks.TITLE, title);
|
||||
values.put(Bookmarks.URL, url);
|
||||
values.put(Bookmarks.TITLE, shareData.title);
|
||||
values.put(Bookmarks.URL, shareData.url);
|
||||
|
||||
browserDB.addReadingListItem(resolver, values);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
|
|||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.overlays.OverlayConstants;
|
||||
import org.mozilla.gecko.overlays.service.ShareData;
|
||||
import org.mozilla.gecko.sync.CommandProcessor;
|
||||
import org.mozilla.gecko.sync.CommandRunner;
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
|
@ -66,15 +67,15 @@ public class SendTab extends ShareMethod {
|
|||
private TabSender tabSender;
|
||||
|
||||
@Override
|
||||
public Result handle(String title, String url, Parcelable extra) {
|
||||
if (extra == null) {
|
||||
public Result handle(ShareData shareData) {
|
||||
if (shareData.extra == null) {
|
||||
Log.e(LOGTAG, "No target devices specified!");
|
||||
|
||||
// Retrying with an identical lack of devices ain't gonna fix it...
|
||||
return Result.PERMANENT_FAILURE;
|
||||
}
|
||||
|
||||
String[] targetGUIDs = ((Bundle) extra).getStringArray(SEND_TAB_TARGET_DEVICES);
|
||||
String[] targetGUIDs = ((Bundle) shareData.extra).getStringArray(SEND_TAB_TARGET_DEVICES);
|
||||
|
||||
// Ensure all target GUIDs are devices we actually know about.
|
||||
if (!validGUIDs.containsAll(Arrays.asList(targetGUIDs))) {
|
||||
|
@ -108,7 +109,7 @@ public class SendTab extends ShareMethod {
|
|||
// Remember that ShareMethod.handle is always run on the background thread, so the database
|
||||
// access here is of no concern.
|
||||
for (int i = 0; i < targetGUIDs.length; i++) {
|
||||
processor.sendURIToClientForDisplay(url, targetGUIDs[i], title, accountGUID, context);
|
||||
processor.sendURIToClientForDisplay(shareData.url, targetGUIDs[i], shareData.title, accountGUID, context);
|
||||
}
|
||||
|
||||
// Request an immediate sync to push these new commands to the network ASAP.
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.overlays.service.sharemethods;
|
|||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import org.mozilla.gecko.overlays.service.ShareData;
|
||||
|
||||
/**
|
||||
* Represents a method of sharing a URL/title. Add a bookmark? Send to a device? Add to reading list?
|
||||
|
@ -30,14 +31,7 @@ public abstract class ShareMethod {
|
|||
* the ShareMethod (such as the device to share to in the case of SendTab).
|
||||
* @return true if the attempt to share was a success. False in the event of an error.
|
||||
*/
|
||||
public abstract Result handle(String title, String url, Parcelable extra);
|
||||
|
||||
/**
|
||||
* Convenience method for calling handlers on objects that don't require extra data.
|
||||
*/
|
||||
public Result handle(String title, String url) {
|
||||
return handle(title, url, null);
|
||||
}
|
||||
public abstract Result handle(ShareData shareData);
|
||||
|
||||
public abstract String getSuccessMesssage();
|
||||
public abstract String getFailureMessage();
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.overlays.ui;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* A button in the share overlay, such as the "Add to Reading List" button.
|
||||
* Has an associated icon and label, and two states: enabled and disabled.
|
||||
*
|
||||
* When disabled, tapping results in a "pop" animation causing the icon to pulse. When enabled,
|
||||
* tapping calls the OnClickListener set by the consumer in the usual way.
|
||||
*/
|
||||
public class OverlayDialogButton extends LinearLayout {
|
||||
private static final String LOGTAG = "GeckoOverlayDialogButton";
|
||||
|
||||
// The views making up this button.
|
||||
private ImageView icon;
|
||||
private TextView label;
|
||||
|
||||
// Label/icon used when enabled.
|
||||
private String enabledLabel;
|
||||
private Drawable enabledIcon;
|
||||
|
||||
// Label/icon used when disabled.
|
||||
private String disabledLabel;
|
||||
private Drawable disabledIcon;
|
||||
|
||||
// Click listeners used when enabled/disabled. Currently, disabledOnClickListener is set
|
||||
// intenally to something that causes the icon to pulse.
|
||||
private OnClickListener enabledOnClickListener;
|
||||
private OnClickListener disabledOnClickListener;
|
||||
|
||||
private boolean isEnabled = true;
|
||||
|
||||
public OverlayDialogButton(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public OverlayDialogButton(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public OverlayDialogButton(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
setOrientation(HORIZONTAL);
|
||||
setPadding(0, 0, 0, 0);
|
||||
setBackgroundResource(R.drawable.overlay_share_button_background);
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.overlay_share_button, this);
|
||||
icon = (ImageView) findViewById(R.id.overlaybtn_icon);
|
||||
label = (TextView) findViewById(R.id.overlaybtn_label);
|
||||
}
|
||||
|
||||
public void setEnabledLabelAndIcon(String s, Drawable d) {
|
||||
enabledLabel = s;
|
||||
enabledIcon = d;
|
||||
|
||||
if (isEnabled) {
|
||||
updateViews();
|
||||
}
|
||||
}
|
||||
|
||||
public void setDisabledLabelAndIcon(String s, Drawable d) {
|
||||
disabledLabel = s;
|
||||
disabledIcon = d;
|
||||
|
||||
if (!isEnabled) {
|
||||
updateViews();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign the appropriate label and icon to the views, and update the onClickListener for this
|
||||
* view to the correct one (based on current enabledness state).
|
||||
*/
|
||||
private void updateViews() {
|
||||
label.setEnabled(isEnabled);
|
||||
if (isEnabled) {
|
||||
label.setText(enabledLabel);
|
||||
icon.setImageDrawable(enabledIcon);
|
||||
super.setOnClickListener(enabledOnClickListener);
|
||||
} else {
|
||||
label.setText(disabledLabel);
|
||||
icon.setImageDrawable(disabledIcon);
|
||||
super.setOnClickListener(getPopListener());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to lazily-initialise disabledOnClickListener to a listener that performs the
|
||||
* "pop" animation on the icon.
|
||||
* updateViews handles making this the actual onClickListener for this view.
|
||||
*/
|
||||
private OnClickListener getPopListener() {
|
||||
if (disabledOnClickListener == null) {
|
||||
disabledOnClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.overlay_pop);
|
||||
icon.startAnimation(anim);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return disabledOnClickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener l) {
|
||||
enabledOnClickListener = l;
|
||||
|
||||
if (isEnabled) {
|
||||
updateViews();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the enabledness state of this view. We don't call super.setEnabled, as we want to remain
|
||||
* clickable even in the disabled state (but with a different click listener).
|
||||
*/
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (enabled == isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isEnabled = enabled;
|
||||
updateViews();
|
||||
}
|
||||
}
|
|
@ -6,9 +6,12 @@
|
|||
package org.mozilla.gecko.overlays.ui;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
@ -22,7 +25,9 @@ import android.widget.TextView;
|
|||
import android.widget.Toast;
|
||||
|
||||
import org.mozilla.gecko.Assert;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.LocalBrowserDB;
|
||||
import org.mozilla.gecko.overlays.OverlayConstants;
|
||||
import org.mozilla.gecko.overlays.service.OverlayActionService;
|
||||
import org.mozilla.gecko.overlays.service.sharemethods.ParcelableClientRecord;
|
||||
|
@ -30,6 +35,8 @@ import org.mozilla.gecko.overlays.service.sharemethods.SendTab;
|
|||
import org.mozilla.gecko.overlays.service.sharemethods.ShareMethod;
|
||||
import org.mozilla.gecko.LocaleAware;
|
||||
import org.mozilla.gecko.sync.setup.activities.WebURLFinder;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UIAsyncTask;
|
||||
|
||||
/**
|
||||
* A transparent activity that displays the share overlay.
|
||||
|
@ -91,17 +98,18 @@ public class ShareDialog extends LocaleAware.LocaleAwareActivity implements Send
|
|||
getWindow().setWindowAnimations(0);
|
||||
|
||||
Intent intent = getIntent();
|
||||
final Resources resources = getResources();
|
||||
|
||||
// The URL is usually hiding somewhere in the extra text. Extract it.
|
||||
String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
String pageUrl = new WebURLFinder(extraText).bestWebURL();
|
||||
final String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
final String pageUrl = new WebURLFinder(extraText).bestWebURL();
|
||||
|
||||
if (TextUtils.isEmpty(pageUrl)) {
|
||||
Log.e(LOGTAG, "Unable to process shared intent. No URL found!");
|
||||
|
||||
// Display toast notifying the user of failure (most likely a developer who screwed up
|
||||
// trying to send a share intent).
|
||||
Toast toast = Toast.makeText(this, getResources().getText(R.string.overlay_share_no_url), Toast.LENGTH_SHORT);
|
||||
Toast toast = Toast.makeText(this, resources.getText(R.string.overlay_share_no_url), Toast.LENGTH_SHORT);
|
||||
toast.show();
|
||||
finish();
|
||||
|
||||
|
@ -110,8 +118,9 @@ public class ShareDialog extends LocaleAware.LocaleAwareActivity implements Send
|
|||
|
||||
setContentView(R.layout.overlay_share_dialog);
|
||||
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(uiEventListener,
|
||||
new IntentFilter(OverlayConstants.SHARE_METHOD_UI_EVENT));
|
||||
new IntentFilter(OverlayConstants.SHARE_METHOD_UI_EVENT));
|
||||
|
||||
// Have the service start any initialisation work that's necessary for us to show the correct
|
||||
// UI. The results of such work will come in via the BroadcastListener.
|
||||
|
@ -143,16 +152,35 @@ public class ShareDialog extends LocaleAware.LocaleAwareActivity implements Send
|
|||
Animation anim = AnimationUtils.loadAnimation(this, R.anim.overlay_slide_up);
|
||||
findViewById(R.id.sharedialog).startAnimation(anim);
|
||||
|
||||
// Add button event listeners.
|
||||
// Configure buttons.
|
||||
final OverlayDialogButton bookmarkBtn = (OverlayDialogButton) findViewById(R.id.overlay_share_bookmark_btn);
|
||||
|
||||
findViewById(R.id.overlay_share_bookmark_btn).setOnClickListener(new View.OnClickListener() {
|
||||
final String bookmarkEnabledLabel = resources.getString(R.string.overlay_share_bookmark_btn_label);
|
||||
final Drawable bookmarkEnabledIcon = resources.getDrawable(R.drawable.overlay_bookmark_icon);
|
||||
bookmarkBtn.setEnabledLabelAndIcon(bookmarkEnabledLabel, bookmarkEnabledIcon);
|
||||
|
||||
final String bookmarkDisabledLabel = resources.getString(R.string.overlay_share_bookmark_btn_label_already);
|
||||
final Drawable bookmarkDisabledIcon = resources.getDrawable(R.drawable.overlay_bookmarked_already_icon);
|
||||
bookmarkBtn.setDisabledLabelAndIcon(bookmarkDisabledLabel, bookmarkDisabledIcon);
|
||||
|
||||
bookmarkBtn.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
addBookmark();
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.overlay_share_reading_list_btn).setOnClickListener(new View.OnClickListener() {
|
||||
final OverlayDialogButton readinglistBtn = (OverlayDialogButton) findViewById(R.id.overlay_share_reading_list_btn);
|
||||
|
||||
final String readingListEnabledLabel = resources.getString(R.string.overlay_share_reading_list_btn_label);
|
||||
final Drawable readingListEnabledIcon = resources.getDrawable(R.drawable.overlay_readinglist_icon);
|
||||
readinglistBtn.setEnabledLabelAndIcon(readingListEnabledLabel, readingListEnabledIcon);
|
||||
|
||||
final String readingListDisabledLabel = resources.getString(R.string.overlay_share_reading_list_btn_label_already);
|
||||
final Drawable readingListDisabledIcon = resources.getDrawable(R.drawable.overlay_readinglist_already_icon);
|
||||
readinglistBtn.setDisabledLabelAndIcon(readingListDisabledLabel, readingListDisabledIcon);
|
||||
|
||||
readinglistBtn.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
addToReadingList();
|
||||
|
@ -168,6 +196,42 @@ public class ShareDialog extends LocaleAware.LocaleAwareActivity implements Send
|
|||
sendTabList.setSendTabTargetSelectedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
LocalBrowserDB browserDB = new LocalBrowserDB(getCurrentProfile());
|
||||
disableButtonsIfAlreadyAdded(url, browserDB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the bookmark/reading list buttons if the given URL is already in the corresponding
|
||||
* list.
|
||||
*/
|
||||
private void disableButtonsIfAlreadyAdded(final String pageURL, final LocalBrowserDB browserDB) {
|
||||
new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
|
||||
// Flags to hold the result
|
||||
boolean isBookmark;
|
||||
boolean isReadingListItem;
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
final ContentResolver contentResolver = getApplicationContext().getContentResolver();
|
||||
|
||||
isBookmark = browserDB.isBookmark(contentResolver, pageURL);
|
||||
isReadingListItem = browserDB.isReadingListItem(contentResolver, pageURL);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
findViewById(R.id.overlay_share_bookmark_btn).setEnabled(!isBookmark);
|
||||
findViewById(R.id.overlay_share_reading_list_btn).setEnabled(!isReadingListItem);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get an overlay service intent populated with the data held in this dialog.
|
||||
*/
|
||||
|
@ -236,6 +300,10 @@ public class ShareDialog extends LocaleAware.LocaleAwareActivity implements Send
|
|||
slideOut();
|
||||
}
|
||||
|
||||
private String getCurrentProfile() {
|
||||
return GeckoProfile.DEFAULT_PROFILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide the overlay down off the screen and destroy it.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fillAfter="true" >
|
||||
|
||||
<scale
|
||||
android:duration="300"
|
||||
android:fromXScale="1"
|
||||
android:fromYScale="1"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toXScale="2"
|
||||
android:toYScale="2" >
|
||||
</scale>
|
||||
|
||||
<scale
|
||||
android:duration="300"
|
||||
android:fromXScale="1"
|
||||
android:fromYScale="1"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toXScale="0.5"
|
||||
android:toYScale="0.5" >
|
||||
</scale>
|
||||
|
||||
</set>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_enabled="false" android:color="@color/text_color_overlaybtn_disabled" />
|
||||
<item android:color="@color/text_color_overlaybtn" />
|
||||
|
||||
</selector>
|
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 652 B После Ширина: | Высота: | Размер: 4.4 KiB |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/overlay_bookmarked_already_icon.png
Normal file
Двоичные данные
mobile/android/base/resources/drawable-hdpi/overlay_bookmarked_already_icon.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 951 B |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/overlay_readinglist_already_icon.png
Normal file
Двоичные данные
mobile/android/base/resources/drawable-hdpi/overlay_readinglist_already_icon.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 3.5 KiB |
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче