merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2014-04-03 12:46:58 +02:00
commit b24c75eef3
76 changed files with 1453 additions and 313 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 988168 requires a clobber because of the location change for the linked libxul.
Bug 991256 requires a clobber because it renames generated mobile/android/base/widget files.

View File

@ -1208,6 +1208,9 @@ pref("devtools.shadereditor.enabled", false);
// Enable the Canvas Debugger.
pref("devtools.canvasdebugger.enabled", false);
// Enable the Web Audio Editor
pref("devtools.webaudioeditor.enabled", false);
// Default theme ("dark" or "light")
pref("devtools.theme", "light");

View File

@ -7,7 +7,6 @@ let gSyncUI = {
DEFAULT_EOL_URL: "https://www.mozilla.org/firefox/?utm_source=synceol",
_obs: ["weave:service:sync:start",
"weave:service:sync:delayed",
"weave:service:quota:remaining",
"weave:service:setup-complete",
"weave:service:login:start",
@ -85,8 +84,6 @@ let gSyncUI = {
Services.obs.removeObserver(this, "weave:notification:added");
},
_wasDelayed: false,
_needsSetup: function SUI__needsSetup() {
// We want to treat "account needs verification" as "needs setup". So
// "reach in" to Weave.Status._authManager to check whether we the signed-in
@ -163,21 +160,6 @@ let gSyncUI = {
});
},
onSyncDelay: function SUI_onSyncDelay() {
// basically, we want to just inform users that stuff is going to take a while
let title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
let description = this._stringBundle.GetStringFromName("error.sync.no_node_found");
let buttons = [new Weave.NotificationButton(
this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
function() { gSyncUI.openServerStatus(); return true; }
)];
let notification = new Weave.Notification(
title, description, null, Weave.Notifications.PRIORITY_INFO, buttons);
Weave.Notifications.replaceTitle(notification);
this._wasDelayed = true;
},
onLoginFinish: function SUI_onLoginFinish() {
// Clear out any login failure notifications
let title = this._stringBundle.GetStringFromName("error.login.title");
@ -410,12 +392,6 @@ let gSyncUI = {
// Clear out sync failures on a successful sync
this.clearError(title);
if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
this.clearError(title);
this._wasDelayed = false;
}
},
onSyncError: function SUI_onSyncError() {
@ -487,12 +463,6 @@ let gSyncUI = {
new Weave.Notification(title, description, null, priority, buttons);
Weave.Notifications.replaceTitle(notification);
if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
Weave.Notifications.removeAll(title);
this._wasDelayed = false;
}
this.updateUI();
},
@ -519,9 +489,6 @@ let gSyncUI = {
case "weave:ui:sync:error":
this.onSyncError();
break;
case "weave:service:sync:delayed":
this.onSyncDelay();
break;
case "weave:service:quota:remaining":
this.onQuotaNotice();
break;

View File

@ -14,7 +14,6 @@ BRANDING_FILES := \
branding.nsi \
appname.bmp \
bgintro.bmp \
bgplain.bmp \
clock.bmp \
particles.bmp \
pencil.bmp \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

View File

@ -37,8 +37,8 @@
!define FOOTER_CONTROL_TEXT_COLOR_FADED 0x999999
!define FOOTER_BKGRD_COLOR 0xFFFFFF
!define INTRO_BLURB_TEXT_COLOR 0xFFFFFF
!define OPTIONS_TEXT_COLOR_NORMAL 0xFFFFFF
!define OPTIONS_TEXT_COLOR_FADED 0xA1AAB3
!define OPTIONS_BKGRD_COLOR 0x0F1B26
!define INSTALL_BLURB_TEXT_COLOR 0xFFFFFF
!define INSTALL_PROGRESS_TEXT_COLOR_NORMAL 0xFFFFFF
!define COMMON_TEXT_COLOR_NORMAL 0xFFFFFF
!define COMMON_TEXT_COLOR_FADED 0xA1AAB3
!define COMMON_BKGRD_COLOR 0x0F1B26

View File

@ -14,7 +14,6 @@ BRANDING_FILES := \
branding.nsi \
appname.bmp \
bgintro.bmp \
bgplain.bmp \
clock.bmp \
particles.bmp \
pencil.bmp \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

View File

@ -37,8 +37,8 @@
!define FOOTER_CONTROL_TEXT_COLOR_FADED 0x999999
!define FOOTER_BKGRD_COLOR 0xFFFFFF
!define INTRO_BLURB_TEXT_COLOR 0xFFFFFF
!define OPTIONS_TEXT_COLOR_NORMAL 0xFFFFFF
!define OPTIONS_TEXT_COLOR_FADED 0xA1AAB3
!define OPTIONS_BKGRD_COLOR 0x0F1B26
!define INSTALL_BLURB_TEXT_COLOR 0xFFFFFF
!define INSTALL_PROGRESS_TEXT_COLOR_NORMAL 0xFFFFFF
!define COMMON_TEXT_COLOR_NORMAL 0xFFFFFF
!define COMMON_TEXT_COLOR_FADED 0xA1AAB3
!define COMMON_BKGRD_COLOR 0x0F1B26

View File

@ -14,7 +14,6 @@ BRANDING_FILES := \
branding.nsi \
appname.bmp \
bgintro.bmp \
bgplain.bmp \
clock.bmp \
particles.bmp \
pencil.bmp \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

View File

@ -42,8 +42,8 @@
!define FOOTER_CONTROL_TEXT_COLOR_FADED 0x666666
!define FOOTER_BKGRD_COLOR 0xFFFFFF
!define INTRO_BLURB_TEXT_COLOR 0x666666
!define OPTIONS_TEXT_COLOR_NORMAL 0x000000
!define OPTIONS_TEXT_COLOR_FADED 0x666666
!define OPTIONS_BKGRD_COLOR 0xF0F0F0
!define INSTALL_BLURB_TEXT_COLOR 0x666666
!define INSTALL_PROGRESS_TEXT_COLOR_NORMAL 0x666666
!define COMMON_TEXT_COLOR_NORMAL 0x000000
!define COMMON_TEXT_COLOR_FADED 0x666666
!define COMMON_BKGRD_COLOR 0xF0F0F0

View File

@ -14,7 +14,6 @@ BRANDING_FILES := \
branding.nsi \
appname.bmp \
bgintro.bmp \
bgplain.bmp \
clock.bmp \
particles.bmp \
pencil.bmp \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

View File

@ -37,8 +37,8 @@
!define FOOTER_CONTROL_TEXT_COLOR_FADED 0x999999
!define FOOTER_BKGRD_COLOR 0xFFFFFF
!define INTRO_BLURB_TEXT_COLOR 0xFFFFFF
!define OPTIONS_TEXT_COLOR_NORMAL 0xFFFFFF
!define OPTIONS_TEXT_COLOR_FADED 0xA1AAB3
!define OPTIONS_BKGRD_COLOR 0x0F1B26
!define INSTALL_BLURB_TEXT_COLOR 0xFFFFFF
!define INSTALL_PROGRESS_TEXT_COLOR_NORMAL 0xFFFFFF
!define COMMON_TEXT_COLOR_NORMAL 0xFFFFFF
!define COMMON_TEXT_COLOR_FADED 0xA1AAB3
!define COMMON_BKGRD_COLOR 0x0F1B26

View File

@ -24,6 +24,7 @@ DIRS += [
'styleeditor',
'styleinspector',
'tilt',
'webaudioeditor',
'webconsole',
]
@ -32,4 +33,4 @@ EXTRA_COMPONENTS += [
'devtools-clhandler.manifest',
]
JAR_MANIFESTS += ['jar.mn']
JAR_MANIFESTS += ['jar.mn']

View File

@ -0,0 +1,12 @@
# 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/.
TEST_DIRS += ['test']
JS_MODULES_PATH = 'modules/devtools/webaudioeditor'
EXTRA_JS_MODULES += [
]

View File

@ -0,0 +1,12 @@
[DEFAULT]
support-files =
doc_simple-context.html
doc_complex-context.html
doc_simple-node-creation.html
head.js
[browser_webaudio-actor-simple.js]
[browser_audionode-actor-get-set-param.js]
[browser_audionode-actor-is-source.js]
[browser_audionode-actor-get-type.js]

View File

@ -0,0 +1,51 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test AudioNode#getParam() / AudioNode#setParam()
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
front.setup({ reload: true }),
get3(front, "create-node")
]);
let freq = yield oscNode.getParam("frequency");
info(typeof freq);
ise(freq, 440, "AudioNode:getParam correctly fetches AudioParam");
let type = yield oscNode.getParam("type");
ise(type, "sine", "AudioNode:getParam correctly fetches non-AudioParam");
let type = yield oscNode.getParam("not-a-valid-param");
is(type, undefined, "AudioNode:getParam correctly returns false for invalid param");
let resSuccess = yield oscNode.setParam("frequency", 220);
let freq = yield oscNode.getParam("frequency");
ise(freq, 220, "AudioNode:setParam correctly sets a `number` AudioParam");
is(resSuccess, undefined, "AudioNode:setParam returns undefined for correctly set AudioParam");
resSuccess = yield oscNode.setParam("type", "square");
let type = yield oscNode.getParam("type");
ise(type, "square", "AudioNode:setParam correctly sets a `string` non-AudioParam");
is(resSuccess, undefined, "AudioNode:setParam returns undefined for correctly set AudioParam");
resSuccess = yield oscNode.setParam("type", "\"triangle\"");
type = yield oscNode.getParam("type");
ise(type, "triangle", "AudioNode:setParam correctly removes quotes in `string` non-AudioParam");
try {
yield oscNode.setParam("frequency", "hello");
ok(false, "setParam with invalid types should throw");
} catch (e) {
ok(/is not a finite floating-point/.test(e.message), "AudioNode:setParam returns error with correct message when attempting an invalid assignment");
is(e.type, "TypeError", "AudioNode:setParam returns error with correct type when attempting an invalid assignment");
freq = yield oscNode.getParam("frequency");
ise(freq, 220, "AudioNode:setParam does not modify value when an error occurs");
}
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,29 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test AudioNode#getType()
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
let [_, nodes] = yield Promise.all([
front.setup({ reload: true }),
getN(front, "create-node", 14)
]);
let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
let expectedTypes = [
"AudioDestinationNode",
"AudioBufferSourceNode", "ScriptProcessorNode", "AnalyserNode", "GainNode",
"DelayNode", "BiquadFilterNode", "WaveShaperNode", "PannerNode", "ConvolverNode",
"ChannelSplitterNode", "ChannelMergerNode", "DynamicsCompressorNode", "OscillatorNode"
];
expectedTypes.forEach((type, i) => {
is(actualTypes[i], type, type + " successfully created with correct type");
});
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,28 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test AudioNode#isSource()
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
let [_, nodes] = yield Promise.all([
front.setup({ reload: true }),
getN(front, "create-node", 14)
]);
let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
let isSourceResult = yield Promise.all(nodes.map(node => node.isSource()));
actualTypes.forEach((type, i) => {
let shouldBeSource = type === "AudioBufferSourceNode" || type === "OscillatorNode";
if (shouldBeSource)
is(isSourceResult[i], true, type + "'s isSource() yields into `true`");
else
is(isSourceResult[i], false, type + "'s isSource() yields into `false`");
});
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,35 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test basic communication of Web Audio actor
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
let [_, __, [destNode, oscNode, gainNode], [connect1, connect2]] = yield Promise.all([
front.setup({ reload: true }),
once(front, "start-context"),
get3(front, "create-node"),
get2(front, "connect-node")
]);
let destType = yield destNode.getType();
let oscType = yield oscNode.getType();
let gainType = yield gainNode.getType();
is(destType, "AudioDestinationNode", "WebAudioActor:create-node returns AudioNodeActor for AudioDestination");
is(oscType, "OscillatorNode", "WebAudioActor:create-node returns AudioNodeActor");
is(gainType, "GainNode", "WebAudioActor:create-node returns AudioNodeActor");
let { source, dest } = connect1;
is(source.actorID, oscNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on source (osc->gain)");
is(dest.actorID, gainNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on dest (osc->gain)");
let { source, dest } = connect2;
is(source.actorID, gainNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on source (gain->dest)");
is(dest.actorID, destNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on dest (gain->dest)");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,44 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Web Audio Editor test page</title>
</head>
<body>
<script type="text/javascript;version=1.8">
"use strict";
/*
↱ proc
osc → gain →
osc → gain → destination
buffer →↳ filter →
*/
let ctx = new AudioContext();
let osc1 = ctx.createOscillator();
let gain1 = ctx.createGain();
let proc = ctx.createScriptProcessor();
osc1.connect(gain1);
osc1.connect(proc);
gain1.connect(ctx.destination);
let osc2 = ctx.createOscillator();
let gain2 = ctx.createGain();
osc2.connect(gain2);
gain2.connect(ctx.destination);
let buf = ctx.createBufferSource();
let filter = ctx.createBiquadFilter();
buf.connect(filter);
osc2.connect(filter);
filter.connect(ctx.destination);
</script>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Web Audio Editor test page</title>
</head>
<body>
<script type="text/javascript;version=1.8">
"use strict";
let ctx = new AudioContext();
let osc = ctx.createOscillator();
let gain = ctx.createGain();
gain.gain.value = 0;
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(0);
</script>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Web Audio Editor test page</title>
</head>
<body>
<script type="text/javascript;version=1.8">
"use strict";
let ctx = new AudioContext();
let NODE_CREATION_METHODS = [
"createBufferSource", "createScriptProcessor", "createAnalyser",
"createGain", "createDelay", "createBiquadFilter", "createWaveShaper",
"createPanner", "createConvolver", "createChannelSplitter", "createChannelMerger",
"createDynamicsCompressor", "createOscillator"
];
let nodes = NODE_CREATION_METHODS.map(method => ctx[method]());
</script>
</body>
</html>

View File

@ -0,0 +1,160 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
// Enable logging for all the tests. Both the debugger server and frontend will
// be affected by this pref.
let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Services.prefs.setBoolPref("devtools.debugger.log", true);
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio");
let TargetFactory = devtools.TargetFactory;
let Toolbox = devtools.Toolbox;
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/";
const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
// All tests are asynchronous.
waitForExplicitFinish();
let gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
registerCleanupFunction(() => {
info("finish() was called, cleaning up...");
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", gToolEnabled);
Cu.forceGC();
});
function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
let deferred = Promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
let linkedBrowser = tab.linkedBrowser;
linkedBrowser.addEventListener("load", function onLoad() {
linkedBrowser.removeEventListener("load", onLoad, true);
info("Tab added and finished loading: " + aUrl);
deferred.resolve(tab);
}, true);
return deferred.promise;
}
function removeTab(aTab, aWindow) {
info("Removing tab.");
let deferred = Promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
let tabContainer = targetBrowser.tabContainer;
tabContainer.addEventListener("TabClose", function onClose(aEvent) {
tabContainer.removeEventListener("TabClose", onClose, false);
info("Tab removed and finished closing.");
deferred.resolve();
}, false);
targetBrowser.removeTab(aTab);
return deferred.promise;
}
function handleError(aError) {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
finish();
}
function once(aTarget, aEventName, aUseCapture = false) {
info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
let deferred = Promise.defer();
for (let [add, remove] of [
["on", "off"], // Use event emitter before DOM events for consistency
["addEventListener", "removeEventListener"],
["addListener", "removeListener"]
]) {
if ((add in aTarget) && (remove in aTarget)) {
aTarget[add](aEventName, function onEvent(...aArgs) {
aTarget[remove](aEventName, onEvent, aUseCapture);
deferred.resolve(...aArgs);
}, aUseCapture);
break;
}
}
return deferred.promise;
}
function test () {
Task.spawn(spawnTest).then(finish, handleError);
}
function initBackend(aUrl) {
info("Initializing a web audio editor front.");
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
return Task.spawn(function*() {
let tab = yield addTab(aUrl);
let target = TargetFactory.forTab(tab);
let debuggee = target.window.wrappedJSObject;
yield target.makeRemote();
let front = new WebAudioFront(target.client, target.form);
return [target, debuggee, front];
});
}
// Due to web audio will fire most events synchronously back-to-back,
// and we can't yield them in a chain without missing actors, this allows
// us to listen for `n` events and return a promise resolving to them.
//
// Takes a `front` object that is an event emitter, the number of
// programs that should be listened to and waited on, and an optional
// `onAdd` function that calls with the entire actors array on program link
function getN (front, eventName, count, spread) {
let actors = [];
let deferred = Promise.defer();
front.on(eventName, function onEvent (...args) {
let actor = args[0];
if (actors.length !== count) {
actors.push(spread ? args : actor);
}
if (actors.length === count) {
front.off(eventName, onEvent);
deferred.resolve(actors);
}
});
return deferred.promise;
}
function get (front, eventName) { return getN(front, eventName, 1); }
function get2 (front, eventName) { return getN(front, eventName, 2); }
function get3 (front, eventName) { return getN(front, eventName, 3); }
function getSpread (front, eventName) { return getN(front, eventName, 1, true); }
function get2Spread (front, eventName) { return getN(front, eventName, 2, true); }
function get3Spread (front, eventName) { return getN(front, eventName, 3, true); }
function getNSpread (front, eventName, count) { return getN(front, eventName, count, true); }

View File

@ -0,0 +1,6 @@
# 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/.
BROWSER_CHROME_MANIFESTS += ['browser.ini']

View File

@ -28,7 +28,6 @@ BRANDING_FILES = \
branding.nsi \
appname.bmp \
bgintro.bmp \
bgplain.bmp \
clock.bmp \
particles.bmp \
pencil.bmp \

View File

@ -422,7 +422,6 @@ Function .onInit
InitPluginsDir
File /oname=$PLUGINSDIR\bgintro.bmp "bgintro.bmp"
File /oname=$PLUGINSDIR\bgplain.bmp "bgplain.bmp"
File /oname=$PLUGINSDIR\appname.bmp "appname.bmp"
File /oname=$PLUGINSDIR\clock.bmp "clock.bmp"
File /oname=$PLUGINSDIR\particles.bmp "particles.bmp"
@ -866,12 +865,12 @@ Function createOptions
Pop $Dialog
; Since the text color for controls is set in this Dialog the foreground and
; background colors of the Dialog must also be hardcoded.
SetCtlColors $Dialog ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $Dialog ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
${NSD_CreateLabel} ${OPTIONS_ITEM_EDGE_DU} 18u ${OPTIONS_ITEM_WIDTH_DU} \
12u "$(CREATE_SHORTCUTS)"
Pop $0
SetCtlColors $0 ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $0 ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $0 ${WM_SETFONT} $FontNormal 0
${If} ${AtLeastWin7}
@ -885,7 +884,7 @@ Function createOptions
; The uxtheme must be disabled on checkboxes in order to override the system
; font color.
System::Call 'uxtheme::SetWindowTheme(i $CheckboxShortcutOnBar, w " ", w " ")'
SetCtlColors $CheckboxShortcutOnBar ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $CheckboxShortcutOnBar ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $CheckboxShortcutOnBar ${WM_SETFONT} $FontNormal 0
${NSD_Check} $CheckboxShortcutOnBar
@ -895,7 +894,7 @@ Function createOptions
; The uxtheme must be disabled on checkboxes in order to override the system
; font color.
System::Call 'uxtheme::SetWindowTheme(i $CheckboxShortcutInStartMenu, w " ", w " ")'
SetCtlColors $CheckboxShortcutInStartMenu ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $CheckboxShortcutInStartMenu ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $CheckboxShortcutInStartMenu ${WM_SETFONT} $FontNormal 0
${NSD_Check} $CheckboxShortcutInStartMenu
@ -905,19 +904,19 @@ Function createOptions
; The uxtheme must be disabled on checkboxes in order to override the system
; font color.
System::Call 'uxtheme::SetWindowTheme(i $CheckboxShortcutOnDesktop, w " ", w " ")'
SetCtlColors $CheckboxShortcutOnDesktop ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $CheckboxShortcutOnDesktop ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $CheckboxShortcutOnDesktop ${WM_SETFONT} $FontNormal 0
${NSD_Check} $CheckboxShortcutOnDesktop
${NSD_CreateLabel} ${OPTIONS_ITEM_EDGE_DU} 100u ${OPTIONS_ITEM_WIDTH_DU} \
12u "$(DEST_FOLDER)"
Pop $0
SetCtlColors $0 ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $0 ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $0 ${WM_SETFONT} $FontNormal 0
${NSD_CreateDirRequest} ${OPTIONS_SUBITEM_EDGE_DU} 116u 159u 14u "$INSTDIR"
Pop $DirRequest
SetCtlColors $DirRequest ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $DirRequest ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $DirRequest ${WM_SETFONT} $FontNormal 0
System::Call shlwapi::SHAutoComplete(i $DirRequest, i ${SHACF_FILESYSTEM})
${NSD_OnChange} $DirRequest OnChange_DirRequest
@ -933,7 +932,7 @@ Function createOptions
${NSD_CreateBrowseButton} 280u 116u 50u 14u "$(BROWSE_BUTTON)"
Pop $ButtonBrowse
SetCtlColors $ButtonBrowse "" ${OPTIONS_BKGRD_COLOR}
SetCtlColors $ButtonBrowse "" ${COMMON_BKGRD_COLOR}
${NSD_OnClick} $ButtonBrowse OnClick_ButtonBrowse
; Get the number of pixels from the left of the Dialog to the right side of
@ -954,13 +953,13 @@ Function createOptions
; Make both controls the same width as the widest control
${NSD_CreateLabelCenter} ${OPTIONS_SUBITEM_EDGE_DU} 134u $0 $ControlHeightPX "$(SPACE_REQUIRED)"
Pop $5
SetCtlColors $5 ${OPTIONS_TEXT_COLOR_FADED} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $5 ${COMMON_TEXT_COLOR_FADED} ${COMMON_BKGRD_COLOR}
SendMessage $5 ${WM_SETFONT} $FontItalic 0
IntOp $2 $2 + 8 ; Add padding to the control's width
${NSD_CreateLabelCenter} ${OPTIONS_SUBITEM_EDGE_DU} 145u $2 $ControlHeightPX "$(SPACE_AVAILABLE)"
Pop $6
SetCtlColors $6 ${OPTIONS_TEXT_COLOR_FADED} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $6 ${COMMON_TEXT_COLOR_FADED} ${COMMON_BKGRD_COLOR}
SendMessage $6 ${WM_SETFONT} $FontItalic 0
; Use the widest label for aligning the labels next to them
@ -975,14 +974,14 @@ Function createOptions
${NSD_CreateLabel} $ControlRightPX 134u 100% $ControlHeightPX \
"${APPROXIMATE_REQUIRED_SPACE_MB} $(MEGA)$(BYTE)"
Pop $7
SetCtlColors $7 ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $7 ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $7 ${WM_SETFONT} $FontNormal 0
; Create the free space label with an empty string and update it by calling
; UpdateFreeSpaceLabel
${NSD_CreateLabel} $ControlRightPX 145u 100% $ControlHeightPX " "
Pop $LabelFreeSpace
SetCtlColors $LabelFreeSpace ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $LabelFreeSpace ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $LabelFreeSpace ${WM_SETFONT} $FontNormal 0
Call UpdateFreeSpaceLabel
@ -993,7 +992,7 @@ Function createOptions
; The uxtheme must be disabled on checkboxes in order to override the system
; font color.
System::Call 'uxtheme::SetWindowTheme(i $CheckboxSendPing, w " ", w " ")'
SetCtlColors $CheckboxSendPing ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $CheckboxSendPing ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $CheckboxSendPing ${WM_SETFONT} $FontNormal 0
${NSD_Check} $CheckboxSendPing
@ -1018,7 +1017,7 @@ Function createOptions
12u "$(INSTALL_MAINT_SERVICE)"
Pop $CheckboxInstallMaintSvc
System::Call 'uxtheme::SetWindowTheme(i $CheckboxInstallMaintSvc, w " ", w " ")'
SetCtlColors $CheckboxInstallMaintSvc ${OPTIONS_TEXT_COLOR_NORMAL} ${OPTIONS_BKGRD_COLOR}
SetCtlColors $CheckboxInstallMaintSvc ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
SendMessage $CheckboxInstallMaintSvc ${WM_SETFONT} $FontNormal 0
${NSD_Check} $CheckboxInstallMaintSvc
${EndIf}
@ -1105,6 +1104,9 @@ FunctionEnd
Function createInstall
nsDialogs::Create /NOUNLOAD 1018
Pop $Dialog
; Since the text color for controls is set in this Dialog the foreground and
; background colors of the Dialog must also be hardcoded.
SetCtlColors $Dialog ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
${NSD_CreateLabel} 0 0 49u 64u ""
Pop $0
@ -1197,10 +1199,6 @@ Function createInstall
Pop $2
${SetStretchedTransparentImage} $2 $PLUGINSDIR\appname.bmp $0
${NSD_CreateBitmap} 0 0 100% 100% ""
Pop $3
${NSD_SetStretchedImage} $3 $PLUGINSDIR\bgplain.bmp $1
GetDlgItem $0 $HWNDPARENT 1 ; Install button
EnableWindow $0 0
ShowWindow $0 ${SW_HIDE}
@ -1280,7 +1278,6 @@ Function createInstall
nsDialogs::Show
${NSD_FreeImage} $0
${NSD_FreeImage} $1
${NSD_FreeImage} $HwndBitmapBlurb1
${NSD_FreeImage} $HwndBitmapBlurb2
${NSD_FreeImage} $HWndBitmapBlurb3

View File

@ -2703,6 +2703,7 @@ abstract public class BrowserApp extends GeckoApp
final String appLocale,
final SessionInformation previousSession) {
return new BrowserHealthRecorder(context,
GeckoSharedPrefs.forApp(context),
profilePath,
dispatcher,
osLocale,

View File

@ -5,14 +5,14 @@
package org.mozilla.gecko;
import org.mozilla.gecko.widget.GeckoEditText;
import org.mozilla.gecko.widget.ThemedEditText;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
public class CustomEditText extends GeckoEditText {
public class CustomEditText extends ThemedEditText {
private OnKeyPreImeListener mOnKeyPreImeListener;
private OnSelectionChangedListener mOnSelectionChangedListener;
private OnWindowFocusChangeListener mOnWindowFocusChangeListener;

View File

@ -175,7 +175,6 @@ public abstract class GeckoApp
private View mCameraView;
private OrientationEventListener mCameraOrientationEventListener;
public List<GeckoAppShell.AppStateListener> mAppStateListeners;
private static volatile GeckoApp sAppContext;
protected MenuPanel mMenuPanel;
protected Menu mMenu;
protected GeckoProfile mProfile;
@ -234,20 +233,12 @@ public abstract class GeckoApp
@Override
public Context getContext() {
return sAppContext;
return this;
}
@Override
public SharedPreferences getSharedPreferences() {
return GeckoApp.getAppSharedPreferences();
}
public static SharedPreferences getAppSharedPreferences() {
if (sAppContext == null) {
return null;
}
return GeckoSharedPrefs.forApp(sAppContext);
return GeckoSharedPrefs.forApp(this);
}
public Activity getActivity() {
@ -674,7 +665,7 @@ public abstract class GeckoApp
Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number");
}
} else if (event.equals("Intent:GetHandlers")) {
Intent intent = GeckoAppShell.getOpenURIIntent(sAppContext, message.optString("url"),
Intent intent = GeckoAppShell.getOpenURIIntent((Context) this, message.optString("url"),
message.optString("mime"), message.optString("action"), message.optString("title"));
String[] handlers = GeckoAppShell.getHandlersForIntent(intent);
List<String> appList = Arrays.asList(handlers);
@ -1031,16 +1022,16 @@ public abstract class GeckoApp
intent.setData(Uri.parse(path));
// Removes the image from storage once the chooser activity ends.
ActivityHandlerHelper.startIntentForActivity(this,
Intent.createChooser(intent, sAppContext.getString(R.string.set_image_chooser_title)),
new ActivityResultHandler() {
@Override
public void onActivityResult (int resultCode, Intent data) {
getContentResolver().delete(intent.getData(), null, null);
}
});
Intent chooser = Intent.createChooser(intent, getString(R.string.set_image_chooser_title));
ActivityResultHandler handler = new ActivityResultHandler() {
@Override
public void onActivityResult (int resultCode, Intent data) {
getContentResolver().delete(intent.getData(), null, null);
}
};
ActivityHandlerHelper.startIntentForActivity(this, chooser, handler);
} else {
Toast.makeText(sAppContext, R.string.set_image_fail, Toast.LENGTH_SHORT).show();
Toast.makeText((Context) this, R.string.set_image_fail, Toast.LENGTH_SHORT).show();
}
} catch(OutOfMemoryError ome) {
Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
@ -1179,10 +1170,16 @@ public abstract class GeckoApp
mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
Intent intent = getIntent();
String args = intent.getStringExtra("args");
final Intent intent = getIntent();
final String args = intent.getStringExtra("args");
earlyStartJavaSampler(intent);
// GeckoLoader wants to dig some environment variables out of the
// incoming intent, so pass it in here. GeckoLoader will do its
// business later and dispose of the reference.
GeckoLoader.setLastIntent(intent);
if (mProfile == null) {
String profileName = null;
String profilePath = null;
@ -1232,9 +1229,15 @@ public abstract class GeckoApp
MemoryMonitor.getInstance().init(getApplicationContext());
sAppContext = this;
// GeckoAppShell is tightly coupled to us, rather than
// the app context, because various parts of Fennec (e.g.,
// GeckoScreenOrientation) use GAS to access the Activity in
// the guise of fetching a Context.
// When that's fixed, `this` can change to
// `(GeckoApplication) getApplication()` here.
GeckoAppShell.setContextGetter(this);
GeckoAppShell.setGeckoInterface(this);
ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
Tabs.getInstance().attachToContext(this);
@ -1284,7 +1287,7 @@ public abstract class GeckoApp
// only intended to be used internally via Robocop, so a boolean
// is read from a private shared pref to prevent other apps from
// injecting states.
SharedPreferences prefs = getAppSharedPreferences();
final SharedPreferences prefs = getSharedPreferences();
if (prefs.getBoolean(PREFS_ALLOW_STATE_BUNDLE, false)) {
Log.i(LOGTAG, "Restoring state from intent bundle");
prefs.edit().remove(PREFS_ALLOW_STATE_BUNDLE).commit();
@ -1324,12 +1327,11 @@ public abstract class GeckoApp
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
final SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
// Wait until now to set this, because we'd rather throw an exception than
// have a caller of LocaleManager regress startup.
LocaleManager.setContextGetter(GeckoApp.this);
LocaleManager.initialize();
LocaleManager.initialize(getApplicationContext());
SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
if (previousSession.wasKilled()) {
@ -1353,7 +1355,7 @@ public abstract class GeckoApp
Log.i(LOGTAG, "Creating HealthRecorder.");
final String osLocale = Locale.getDefault().toString();
String appLocale = LocaleManager.getAndApplyPersistedLocale();
String appLocale = LocaleManager.getAndApplyPersistedLocale(GeckoApp.this);
Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
if (appLocale == null) {
@ -1743,7 +1745,7 @@ public abstract class GeckoApp
* @return Whether to restore
*/
protected boolean getSessionRestoreState(Bundle savedInstanceState) {
final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
final SharedPreferences prefs = getSharedPreferences();
boolean shouldRestore = false;
final int versionCode = getVersionCode();
@ -1770,7 +1772,7 @@ public abstract class GeckoApp
}
private String getSessionRestorePreference() {
return getAppSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit");
return getSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit");
}
private boolean getRestartFromIntent() {
@ -2002,7 +2004,7 @@ public abstract class GeckoApp
// so it can benefit from a single near-startup prefs commit.
SessionInformation currentSession = new SessionInformation(now, realTime);
SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
currentSession.recordBegin(editor);
@ -2040,7 +2042,7 @@ public abstract class GeckoApp
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
if (rec != null) {
@ -2079,7 +2081,7 @@ public abstract class GeckoApp
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
editor.commit();
@ -2181,7 +2183,7 @@ public abstract class GeckoApp
// Get a temporary directory, may return null
public static File getTempDirectory() {
File dir = sAppContext.getExternalFilesDir("temp");
File dir = GeckoApplication.get().getExternalFilesDir("temp");
return dir;
}
@ -2201,7 +2203,7 @@ public abstract class GeckoApp
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
LocaleManager.correctLocale(getResources(), newConfig);
LocaleManager.correctLocale(this, getResources(), newConfig);
// onConfigurationChanged is not called for 180 degree orientation changes,
// we will miss such rotations and the screen orientation will not be
@ -2273,8 +2275,6 @@ public abstract class GeckoApp
final File profileDir = getProfile().getDir();
if (profileDir != null) {
final GeckoApp app = GeckoApp.sAppContext;
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
@ -2295,7 +2295,7 @@ public abstract class GeckoApp
@Override
public void run() {
long cleanupVersion = getAppSharedPreferences().getInt(CLEANUP_VERSION, 0);
long cleanupVersion = getSharedPreferences().getInt(CLEANUP_VERSION, 0);
if (cleanupVersion < 1) {
// Reduce device storage footprint by removing .ttf files from
@ -2321,7 +2321,7 @@ public abstract class GeckoApp
// Additional cleanup needed for future versions would go here
if (cleanupVersion != CURRENT_CLEANUP_VERSION) {
SharedPreferences.Editor editor = getAppSharedPreferences().edit();
SharedPreferences.Editor editor = GeckoApp.this.getSharedPreferences().edit();
editor.putInt(CLEANUP_VERSION, CURRENT_CLEANUP_VERSION);
editor.commit();
}
@ -2802,7 +2802,7 @@ public abstract class GeckoApp
if (locale == null) {
return;
}
final String resultant = LocaleManager.setSelectedLocale(locale);
final String resultant = LocaleManager.setSelectedLocale(this, locale);
if (resultant == null) {
return;
}

View File

@ -14,17 +14,40 @@ import org.mozilla.gecko.util.ThreadUtils;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.util.Log;
public class GeckoApplication extends Application {
public class GeckoApplication extends Application
implements ContextGetter {
private static final String LOG_TAG = "GeckoApplication";
private static volatile GeckoApplication instance;
private boolean mInBackground;
private boolean mPausedGecko;
private LightweightTheme mLightweightTheme;
public GeckoApplication() {
super();
instance = this;
}
public static GeckoApplication get() {
return instance;
}
@Override
public Context getContext() {
return this;
}
@Override
public SharedPreferences getSharedPreferences() {
return GeckoSharedPrefs.forApp(this);
}
/**
* We need to do locale work here, because we need to intercept
* each hit to onConfigurationChanged.
@ -44,7 +67,7 @@ public class GeckoApplication extends Application {
// Otherwise, correct the locale. This catches some cases that GeckoApp
// doesn't get a chance to.
try {
LocaleManager.correctLocale(getResources(), config);
LocaleManager.correctLocale(this, getResources(), config);
} catch (IllegalStateException ex) {
// GeckoApp hasn't started, so we have no ContextGetter in LocaleManager.
Log.w(LOG_TAG, "Couldn't correct locale.", ex);

View File

@ -59,9 +59,14 @@ public class GeckoNetworkManager extends BroadcastReceiver {
static private final ConnectionType kDefaultConnectionType = ConnectionType.NONE;
private Context mApplicationContext;
private static Context getApplicationContext() {
Context context = GeckoAppShell.getContext();
if (null == context)
return null;
return context.getApplicationContext();
}
private ConnectionType mConnectionType = ConnectionType.NONE;
private final IntentFilter mNetworkFilter = new IntentFilter();
private final IntentFilter mNetworkFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
// Whether the manager should be listening to Network Information changes.
private boolean mShouldBeListening = false;
@ -81,9 +86,7 @@ public class GeckoNetworkManager extends BroadcastReceiver {
public void start(final Context context) {
// Note that this initialization clause only runs once.
if (mApplicationContext == null) {
mApplicationContext = context.getApplicationContext();
mNetworkFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
if (mConnectionType == ConnectionType.NONE) {
mConnectionType = getConnectionType();
}
@ -96,7 +99,8 @@ public class GeckoNetworkManager extends BroadcastReceiver {
}
private void startListening() {
mApplicationContext.registerReceiver(sInstance, mNetworkFilter);
if (null !=getApplicationContext())
getApplicationContext().registerReceiver(sInstance, mNetworkFilter);
}
public void stop() {
@ -108,15 +112,21 @@ public class GeckoNetworkManager extends BroadcastReceiver {
}
private void stopListening() {
mApplicationContext.unregisterReceiver(sInstance);
if (null != getApplicationContext())
getApplicationContext().unregisterReceiver(sInstance);
}
private int wifiDhcpGatewayAddress() {
if (mConnectionType != ConnectionType.WIFI) {
return 0;
}
if (null == getApplicationContext()) {
return 0;
}
try {
WifiManager mgr = (WifiManager)sInstance.mApplicationContext.getSystemService(Context.WIFI_SERVICE);
WifiManager mgr = (WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE);
DhcpInfo d = mgr.getDhcpInfo();
if (d == null) {
return 0;
@ -173,8 +183,12 @@ public class GeckoNetworkManager extends BroadcastReceiver {
}
private static ConnectionType getConnectionType() {
if (null == getApplicationContext()) {
return ConnectionType.NONE;
}
ConnectivityManager cm =
(ConnectivityManager)sInstance.mApplicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
(ConnectivityManager)getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
Log.e(LOGTAG, "Connectivity service does not exist");
return ConnectionType.NONE;
@ -206,7 +220,10 @@ public class GeckoNetworkManager extends BroadcastReceiver {
}
private static int getNetworkOperator(InfoType type) {
TelephonyManager tel = (TelephonyManager)sInstance.mApplicationContext.getSystemService(Context.TELEPHONY_SERVICE);
if (null == getApplicationContext())
return -1;
TelephonyManager tel = (TelephonyManager)getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
if (tel == null) {
Log.e(LOGTAG, "Telephony service does not exist");
return -1;

View File

@ -19,10 +19,8 @@ import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.app.Activity;
import java.io.IOException;
import java.util.Locale;
public class GeckoThread extends Thread implements GeckoEventListener {
@ -97,7 +95,7 @@ public class GeckoThread extends Thread implements GeckoEventListener {
Locale.setDefault(locale);
}
Context app = GeckoAppShell.getContext();
Context context = GeckoAppShell.getContext();
String resourcePath = "";
Resources res = null;
String[] pluginDirs = null;
@ -106,16 +104,14 @@ public class GeckoThread extends Thread implements GeckoEventListener {
} catch (Exception e) {
Log.w(LOGTAG, "Caught exception getting plugin dirs.", e);
}
if (app instanceof Activity) {
Activity activity = (Activity)app;
resourcePath = activity.getApplication().getPackageResourcePath();
res = activity.getBaseContext().getResources();
GeckoLoader.setupGeckoEnvironment(activity, pluginDirs, app.getFilesDir().getPath());
}
GeckoLoader.loadSQLiteLibs(app, resourcePath);
GeckoLoader.loadNSSLibs(app, resourcePath);
GeckoLoader.loadGeckoLibs(app, resourcePath);
resourcePath = context.getPackageResourcePath();
res = context.getResources();
GeckoLoader.setupGeckoEnvironment(context, pluginDirs, context.getFilesDir().getPath());
GeckoLoader.loadSQLiteLibs(context, resourcePath);
GeckoLoader.loadNSSLibs(context, resourcePath);
GeckoLoader.loadGeckoLibs(context, resourcePath);
GeckoJavaSampler.setLibsLoaded();
Locale.setDefault(locale);

View File

@ -33,22 +33,24 @@ import java.util.Locale;
*/
public class LocaleManager {
private static final String LOG_TAG = "GeckoLocales";
private static final String PREF_LOCALE = "locale";
// These are both volatile because we don't impose restrictions
// This is volatile because we don't impose restrictions
// over which thread calls our methods.
private static volatile ContextGetter getter = null;
private static volatile Locale currentLocale = null;
private static volatile boolean inited = false;
private static boolean systemLocaleDidChange = false;
private static BroadcastReceiver receiver;
public static void setContextGetter(ContextGetter getter) {
Log.d(LOG_TAG, "Calling setContextGetter: " + getter);
LocaleManager.getter = getter;
}
public static void initialize() {
/**
* Ensure that you call this early in your application startup,
* and with a context that's sufficiently long-lived (typically
* the application context).
*
* Calling multiple times is harmless.
*/
public static void initialize(final Context context) {
if (inited) {
return;
}
@ -59,7 +61,7 @@ public class LocaleManager {
systemLocaleDidChange = true;
}
};
getContext().registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
inited = true;
}
@ -67,26 +69,12 @@ public class LocaleManager {
return systemLocaleDidChange;
}
private static Context getContext() {
if (getter == null) {
throw new IllegalStateException("No ContextGetter; cannot fetch context.");
}
return getter.getContext();
}
private static SharedPreferences getSharedPreferences() {
if (getter == null) {
throw new IllegalStateException("No ContextGetter; cannot fetch prefs.", new RuntimeException("No prefs."));
}
return getter.getSharedPreferences();
}
/**
* Every time the system gives us a new configuration, it
* carries the external locale. Fix it.
*/
public static void correctLocale(Resources res, Configuration config) {
Locale current = getCurrentLocale();
public static void correctLocale(Context context, Resources res, Configuration config) {
final Locale current = getCurrentLocale(context);
if (current == null) {
return;
}
@ -152,12 +140,12 @@ public class LocaleManager {
return language + "-" + country;
}
public static Locale getCurrentLocale() {
public static Locale getCurrentLocale(Context context) {
if (currentLocale != null) {
return currentLocale;
}
final String current = getPersistedLocale();
final String current = getPersistedLocale(context);
if (current == null) {
return null;
}
@ -171,7 +159,7 @@ public class LocaleManager {
*
* Does not notify Gecko.
*/
private static String updateLocale(String localeCode) {
private static String updateLocale(Context context, String localeCode) {
// Fast path.
final Locale defaultLocale = Locale.getDefault();
if (defaultLocale.toString().equals(localeCode)) {
@ -189,7 +177,7 @@ public class LocaleManager {
currentLocale = locale;
// Update resources.
Resources res = getContext().getResources();
Resources res = context.getResources();
Configuration config = res.getConfiguration();
config.locale = locale;
res.updateConfiguration(config, res.getDisplayMetrics());
@ -203,17 +191,9 @@ public class LocaleManager {
GeckoAppShell.sendEventToGecko(ev);
}
private static String getPrefName() {
return getContext().getPackageName() + ".locale";
}
public static String getPersistedLocale() {
final SharedPreferences settings = getSharedPreferences();
// N.B., it is expected that any per-profile settings will be
// implemented via SharedPreferences multiplexing in ContextGetter, not
// via profile-annotated preference names.
final String locale = settings.getString(getPrefName(), "");
public static String getPersistedLocale(Context context) {
final SharedPreferences settings = getSharedPreferences(context);
final String locale = settings.getString(PREF_LOCALE, "");
if ("".equals(locale)) {
return null;
@ -221,21 +201,26 @@ public class LocaleManager {
return locale;
}
private static void persistLocale(String localeCode) {
final SharedPreferences settings = getSharedPreferences();
settings.edit().putString(getPrefName(), localeCode).commit();
private static void persistLocale(Context context, String localeCode) {
final SharedPreferences settings = getSharedPreferences(context);
settings.edit().putString(PREF_LOCALE, localeCode).commit();
}
public static String getAndApplyPersistedLocale() {
private static SharedPreferences getSharedPreferences(Context context) {
// TODO: this should be per-profile, but we don't want to pay the price.
return GeckoSharedPrefs.forApp(context);
}
public static String getAndApplyPersistedLocale(Context context) {
final long t1 = android.os.SystemClock.uptimeMillis();
final String localeCode = getPersistedLocale();
final String localeCode = getPersistedLocale(context);
if (localeCode == null) {
return null;
}
// Note that we don't tell Gecko about this. We notify Gecko when the
// locale is set, not when we update Java.
final String resultant = updateLocale(localeCode);
final String resultant = updateLocale(context, localeCode);
final long t2 = android.os.SystemClock.uptimeMillis();
Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
@ -247,16 +232,16 @@ public class LocaleManager {
*
* Always persists and notifies Gecko.
*/
public static String setSelectedLocale(String localeCode) {
final String resultant = updateLocale(localeCode);
public static String setSelectedLocale(Context context, String localeCode) {
final String resultant = updateLocale(context, localeCode);
// We always persist and notify Gecko, even if nothing seemed to
// change. This might happen if you're picking a locale that's the same
// as the current OS locale. The OS locale might change next time we
// launch, and we need the Gecko pref and persisted locale to have been
// set by the time that happens.
persistLocale(localeCode);
notifyGeckoOfLocaleChange(getCurrentLocale());
persistLocale(context, localeCode);
notifyGeckoOfLocaleChange(getCurrentLocale(context));
return resultant;
}
}

View File

@ -68,6 +68,7 @@ public class Tab {
private ErrorType mErrorType = ErrorType.NONE;
private static final int MAX_HISTORY_LIST_SIZE = 50;
private volatile int mLoadProgress;
private volatile int mRecordingCount = 0;
public static final int STATE_DELAYED = 0;
public static final int STATE_LOADING = 1;
@ -814,4 +815,16 @@ public class Tab {
public int getLoadProgress() {
return mLoadProgress;
}
public void setRecording(boolean isRecording) {
if (isRecording) {
mRecordingCount++;
} else {
mRecordingCount--;
}
}
public boolean isRecording() {
return mRecordingCount > 0;
}
}

View File

@ -104,6 +104,9 @@ public class Tabs implements GeckoEventListener {
registerEventListener("Link:OpenSearch");
registerEventListener("DesktopMode:Changed");
registerEventListener("Tab:ViewportMetadata");
registerEventListener("Tab:StreamStart");
registerEventListener("Tab:StreamStop");
}
public synchronized void attachToContext(Context context) {
@ -379,7 +382,6 @@ public class Tabs implements GeckoEventListener {
}
// GeckoEventListener implementation
@Override
public void handleMessage(String event, JSONObject message) {
Log.d(LOGTAG, "handleMessage: " + event);
@ -486,7 +488,14 @@ public class Tabs implements GeckoEventListener {
tab.setZoomConstraints(new ZoomConstraints(message));
tab.setIsRTL(message.getBoolean("isRTL"));
notifyListeners(tab, TabEvents.VIEWPORT_CHANGE);
} else if (event.equals("Tab:StreamStart")) {
tab.setRecording(true);
notifyListeners(tab, TabEvents.RECORDING_CHANGE);
} else if (event.equals("Tab:StreamStop")) {
tab.setRecording(false);
notifyListeners(tab, TabEvents.RECORDING_CHANGE);
}
} catch (Exception e) {
Log.w(LOGTAG, "handleMessage threw for " + event, e);
}
@ -557,7 +566,8 @@ public class Tabs implements GeckoEventListener {
SECURITY_CHANGE,
READER_ENABLED,
DESKTOP_MODE_CHANGE,
VIEWPORT_CHANGE
VIEWPORT_CHANGE,
RECORDING_CHANGE
}
public void notifyListeners(Tab tab, TabEvents msg) {

View File

@ -12,6 +12,7 @@ import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.PropertyAnimator.Property;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.widget.TwoWayView;
import org.mozilla.gecko.widget.TabThumbnailWrapper;
import android.content.Context;
import android.content.res.TypedArray;
@ -31,7 +32,7 @@ import android.widget.ImageView;
import android.widget.TextView;
public class TabsTray extends TwoWayView
implements TabsPanel.PanelView {
implements TabsPanel.PanelView {
private static final String LOGTAG = "GeckoTabsTray";
private Context mContext;
@ -122,12 +123,14 @@ public class TabsTray extends TwoWayView
ImageView thumbnail;
ImageButton close;
ViewGroup info;
TabThumbnailWrapper thumbnailWrapper;
public TabRow(View view) {
info = (ViewGroup) view;
title = (TextView) view.findViewById(R.id.title);
thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
close = (ImageButton) view.findViewById(R.id.close);
thumbnailWrapper = (TabThumbnailWrapper) view.findViewById(R.id.wrapper);
}
}
@ -173,6 +176,7 @@ public class TabsTray extends TwoWayView
// We just need to update the style for the unselected tab...
case THUMBNAIL:
case TITLE:
case RECORDING_CHANGE:
View view = TabsTray.this.getChildAt(getPositionForTab(tab) - TabsTray.this.getFirstVisiblePosition());
if (view == null)
return;
@ -270,7 +274,9 @@ public class TabsTray extends TwoWayView
} else {
row.thumbnail.setImageResource(R.drawable.tab_thumbnail_default);
}
if (row.thumbnailWrapper != null) {
row.thumbnailWrapper.setRecording(tab.isRecording());
}
row.title.setText(tab.getDisplayTitle());
row.close.setTag(row);
}

View File

@ -92,8 +92,7 @@ public class EnvironmentBuilder {
// Corresponds to Gecko pref "extensions.blocklist.enabled".
e.isBlocklistEnabled = (info.isBlocklistEnabled() ? 1 : 0);
// Corresponds to one of two Gecko telemetry prefs. We reflect these into
// GeckoPreferences as "datareporting.telemetry.enabled".
// Corresponds to Gecko pref "toolkit.telemetry.enabled".
e.isTelemetryEnabled = (info.isTelemetryEnabled() ? 1 : 0);
e.extensionCount = 0;

View File

@ -15,6 +15,9 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.util.GeckoJarReader;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.ThumbnailHelper;
import android.content.Context;
import android.content.res.Resources;
@ -71,6 +74,11 @@ public final class BitmapUtils {
return;
}
if (data.startsWith("thumbnail:")) {
getThumbnailDrawable(context, data, loader);
return;
}
if (data.startsWith("jar:") || data.startsWith("file://")) {
(new UiAsyncTask<Void, Void, Drawable>(ThreadUtils.getBackgroundHandler()) {
@Override
@ -131,6 +139,21 @@ public final class BitmapUtils {
runOnBitmapFoundOnUiThread(loader, null);
}
public static void getThumbnailDrawable(final Context context, final String data, final BitmapLoader loader) {
int id = Integer.parseInt(data.substring(10), 10);
final Tab tab = Tabs.getInstance().getTab(id);
runOnBitmapFoundOnUiThread(loader, tab.getThumbnail());
Tabs.registerOnTabsChangedListener(new Tabs.OnTabsChangedListener() {
public void onTabChanged(Tab t, Tabs.TabEvents msg, Object data) {
if (tab == t && msg == Tabs.TabEvents.THUMBNAIL) {
Tabs.unregisterOnTabsChangedListener(this);
runOnBitmapFoundOnUiThread(loader, t.getThumbnail());
}
}
});
ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab);
}
public static Bitmap decodeByteArray(byte[] bytes) {
return decodeByteArray(bytes, null);
}

View File

@ -25,7 +25,6 @@ import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Distribution;
import org.mozilla.gecko.Distribution.DistributionDescriptor;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
@ -91,6 +90,7 @@ public class BrowserHealthRecorder implements HealthRecorder, GeckoEventListener
private volatile HealthReportDatabaseStorage storage;
private final ProfileInformationCache profileCache;
private final EventDispatcher dispatcher;
private final SharedPreferences prefs;
// We track previousSession to avoid order-of-initialization confusion. We
// accept it in the constructor, and process it after init.
@ -114,20 +114,13 @@ public class BrowserHealthRecorder implements HealthRecorder, GeckoEventListener
this.session.setTimedJavaStartup(duration);
}
/**
* Persist the opaque identifier for the current Firefox Health Report environment.
* This changes in certain circumstances; be sure to use the current value when recording data.
*/
private void setHealthEnvironment(final int env) {
this.env = env;
}
/**
* This constructor does IO. Run it on a background thread.
*
* appLocale can be null, which indicates that it will be provided later.
*/
public BrowserHealthRecorder(final Context context,
final SharedPreferences appPrefs,
final String profilePath,
final EventDispatcher dispatcher,
final String osLocale,
@ -161,6 +154,8 @@ public class BrowserHealthRecorder implements HealthRecorder, GeckoEventListener
} catch (Exception e) {
Log.e(LOG_TAG, "Exception initializing.", e);
}
this.prefs = appPrefs;
}
public boolean isEnabled() {
@ -544,8 +539,7 @@ public class BrowserHealthRecorder implements HealthRecorder, GeckoEventListener
return;
}
final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
final SharedPreferences.Editor editor = prefs.edit();
final SharedPreferences.Editor editor = this.prefs.edit();
recordSessionEnd(sessionEndReason, editor, prev);

View File

@ -403,6 +403,7 @@ gbjar.sources += [
'widget/IconTabWidget.java',
'widget/SquaredImageView.java',
'widget/TabRow.java',
'widget/TabThumbnailWrapper.java',
'widget/ThumbnailView.java',
'widget/TwoWayView.java',
'ZoomConstraints.java',
@ -423,13 +424,13 @@ gbjar.generated_sources += [ android_package_dir + f for f in [
gbjar.generated_sources += [
'org/mozilla/gecko/AppConstants.java',
'org/mozilla/gecko/SysInfo.java',
'org/mozilla/gecko/widget/GeckoEditText.java',
'org/mozilla/gecko/widget/GeckoImageButton.java',
'org/mozilla/gecko/widget/GeckoImageView.java',
'org/mozilla/gecko/widget/GeckoLinearLayout.java',
'org/mozilla/gecko/widget/GeckoRelativeLayout.java',
'org/mozilla/gecko/widget/GeckoTextSwitcher.java',
'org/mozilla/gecko/widget/GeckoTextView.java',
'org/mozilla/gecko/widget/ThemedEditText.java',
'org/mozilla/gecko/widget/ThemedImageButton.java',
'org/mozilla/gecko/widget/ThemedImageView.java',
'org/mozilla/gecko/widget/ThemedLinearLayout.java',
'org/mozilla/gecko/widget/ThemedRelativeLayout.java',
'org/mozilla/gecko/widget/ThemedTextSwitcher.java',
'org/mozilla/gecko/widget/ThemedTextView.java',
]
if CONFIG['MOZ_CRASHREPORTER']:
gbjar.sources += [ 'CrashReporter.java' ]

View File

@ -5,7 +5,6 @@
package org.mozilla.gecko.mozglue;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
@ -21,6 +20,7 @@ import java.util.Locale;
public final class GeckoLoader {
private static final String LOGTAG = "GeckoLoader";
private static volatile Intent sIntent;
private static File sCacheFile;
private static File sGREDir;
@ -119,10 +119,14 @@ public final class GeckoLoader {
return tmpDir;
}
public static void setupGeckoEnvironment(Activity context, String[] pluginDirs, String profilePath) {
public static void setLastIntent(Intent intent) {
sIntent = intent;
}
public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) {
// if we have an intent (we're being launched by an activity)
// read in any environmental variables from it here
Intent intent = context.getIntent();
final Intent intent = sIntent;
if (intent != null) {
String env = intent.getStringExtra("env0");
Log.d(LOGTAG, "Gecko environment env0: " + env);
@ -181,6 +185,9 @@ public final class GeckoLoader {
}
setupLocaleEnvironment();
// We don't need this any more.
sIntent = null;
}
private static void loadLibsSetup(Context context) {

View File

@ -53,12 +53,15 @@ public class PromptListItem {
isParent = aObject.optBoolean("isParent") || aObject.optBoolean("menu");
}
BitmapUtils.getDrawable(GeckoAppShell.getContext(), aObject.optString("icon"), new BitmapUtils.BitmapLoader() {
@Override
public void onBitmapFound(Drawable d) {
mIcon = d;
}
});
final String iconStr = aObject.optString("icon");
if (iconStr != null) {
BitmapUtils.getDrawable(GeckoAppShell.getContext(), iconStr, new BitmapUtils.BitmapLoader() {
@Override
public void onBitmapFound(Drawable d) {
mIcon = d;
}
});
}
}
public void setIntent(Intent i) {

View File

@ -3,7 +3,8 @@
- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<item android:state_focused="true">
@ -18,6 +19,15 @@
<shape android:shape="rectangle">
<solid android:color="#B3FF9500"/>
<corners android:radius="3dp"/>
</shape>
</item>
<item gecko:state_recording="true">
<shape android:shape="rectangle">
<solid android:color="#FFFF0000"/>
<corners android:radius="3dp"/>
</shape>
@ -25,7 +35,8 @@
<item android:state_focused="false"
android:state_pressed="false"
android:state_checked="true">
android:state_checked="true"
gecko:state_recording="false">
<shape android:shape="rectangle">
<solid android:color="#FFFF9500"/>

View File

@ -82,21 +82,21 @@
android:layout_toLeftOf="@id/menu"
android:layout_alignWithParentIfMissing="true"/>
<org.mozilla.gecko.widget.GeckoImageButton android:id="@+id/menu"
style="@style/UrlBar.ImageButton"
android:layout_width="56dip"
android:layout_alignParentRight="true"
android:contentDescription="@string/menu"
android:background="@drawable/action_bar_button"
android:visibility="gone"/>
<org.mozilla.gecko.widget.ThemedImageButton android:id="@+id/menu"
style="@style/UrlBar.ImageButton"
android:layout_width="56dip"
android:layout_alignParentRight="true"
android:contentDescription="@string/menu"
android:background="@drawable/action_bar_button"
android:visibility="gone"/>
<org.mozilla.gecko.widget.GeckoImageView android:id="@+id/menu_icon"
style="@style/UrlBar.ImageButton"
android:layout_alignLeft="@id/menu"
android:layout_alignRight="@id/menu"
android:gravity="center_vertical"
android:src="@drawable/menu_level"
android:visibility="gone"/>
<org.mozilla.gecko.widget.ThemedImageView android:id="@+id/menu_icon"
style="@style/UrlBar.ImageButton"
android:layout_alignLeft="@id/menu"
android:layout_alignRight="@id/menu"
android:gravity="center_vertical"
android:src="@drawable/menu_level"
android:visibility="gone"/>
<ImageView android:id="@+id/shadow"
android:layout_width="fill_parent"

View File

@ -52,13 +52,13 @@
android:background="@drawable/shaped_button"
android:visibility="gone"/>
<org.mozilla.gecko.widget.GeckoImageView android:id="@+id/menu_icon"
style="@style/UrlBar.ImageButton"
android:layout_alignLeft="@id/menu"
android:layout_alignRight="@id/menu"
android:gravity="center_vertical"
android:src="@drawable/menu_level"
android:visibility="gone"/>
<org.mozilla.gecko.widget.ThemedImageView android:id="@+id/menu_icon"
style="@style/UrlBar.ImageButton"
android:layout_alignLeft="@id/menu"
android:layout_alignRight="@id/menu"
android:gravity="center_vertical"
android:src="@drawable/menu_level"
android:visibility="gone"/>
<org.mozilla.gecko.toolbar.ShapedButton android:id="@+id/tabs"
style="@style/UrlBar.ImageButton"

View File

@ -3,15 +3,15 @@
- 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/. -->
<org.mozilla.gecko.widget.GeckoTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="24dip"
android:layout_height="24dip"
android:layout_margin="12dip"
android:paddingTop="2dip"
android:paddingLeft="4dip"
android:background="@drawable/tabs_count_foreground"
android:textAppearance="@style/TextAppearance.Micro"
android:textColor="#FF43484E"
android:textStyle="bold"
android:duplicateParentState="true"
android:gravity="center"/>
<org.mozilla.gecko.widget.ThemedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="24dip"
android:layout_height="24dip"
android:layout_margin="12dip"
android:paddingTop="2dip"
android:paddingLeft="4dip"
android:background="@drawable/tabs_count_foreground"
android:textAppearance="@style/TextAppearance.Micro"
android:textColor="#FF43484E"
android:textStyle="bold"
android:duplicateParentState="true"
android:gravity="center"/>

View File

@ -14,7 +14,9 @@
android:paddingBottom="6dip"
android:background="@drawable/tab_row">
<LinearLayout android:layout_width="wrap_content"
<org.mozilla.gecko.widget.TabThumbnailWrapper
android:id="@+id/wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dip"
android:background="@drawable/tab_thumbnail"
@ -25,7 +27,7 @@
android:layout_height="@dimen/tab_thumbnail_height"
android:src="@drawable/tab_thumbnail_default"/>
</LinearLayout>
</org.mozilla.gecko.widget.TabThumbnailWrapper>
<TextView android:id="@+id/title"
android:layout_width="0dip"

View File

@ -25,19 +25,19 @@
android:contentDescription="@string/site_security"
android:visibility="gone"/>
<org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
style="@style/UrlBar.Button"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:singleLine="true"
android:paddingRight="8dp"
android:textColor="@color/url_bar_title"
android:textColorHint="@color/url_bar_title_hint"
android:gravity="center_vertical|left"
android:hint="@string/url_bar_default_text"
android:layout_gravity="center_vertical"
gecko:autoUpdateTheme="false"/>
<org.mozilla.gecko.widget.ThemedTextView android:id="@+id/url_bar_title"
style="@style/UrlBar.Button"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:singleLine="true"
android:paddingRight="8dp"
android:textColor="@color/url_bar_title"
android:textColorHint="@color/url_bar_title_hint"
android:gravity="center_vertical|left"
android:hint="@string/url_bar_default_text"
android:layout_gravity="center_vertical"
gecko:autoUpdateTheme="false"/>
<org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
android:layout_width="wrap_content"

View File

@ -74,6 +74,10 @@
<attr name="state_more" format="boolean"/>
</declare-styleable>
<declare-styleable name="TabThumbnailWrapper">
<attr name="state_recording" format="boolean"/>
</declare-styleable>
<declare-styleable name="FlowLayout">
<attr name="spacing" format="dimension"/>
</declare-styleable>

View File

@ -31,9 +31,9 @@ import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.MenuUtils;
import org.mozilla.gecko.widget.GeckoImageButton;
import org.mozilla.gecko.widget.GeckoImageView;
import org.mozilla.gecko.widget.GeckoRelativeLayout;
import org.mozilla.gecko.widget.ThemedImageButton;
import org.mozilla.gecko.widget.ThemedImageView;
import org.mozilla.gecko.widget.ThemedRelativeLayout;
import android.content.Context;
import android.content.res.Resources;
@ -75,7 +75,7 @@ import android.widget.PopupWindow;
* as a set of listeners to allow {@code BrowserToolbar} users to react
* to state changes accordingly.
*/
public class BrowserToolbar extends GeckoRelativeLayout
public class BrowserToolbar extends ThemedRelativeLayout
implements Tabs.OnTabsChangedListener,
GeckoMenu.ActionItemBarPresenter,
GeckoEventListener {
@ -126,8 +126,8 @@ public class BrowserToolbar extends GeckoRelativeLayout
private ToolbarProgressView mProgressBar;
private TabCounter mTabsCounter;
private GeckoImageButton mMenu;
private GeckoImageView mMenuIcon;
private ThemedImageButton mMenu;
private ThemedImageView mMenuIcon;
private LinearLayout mActionItemBar;
private MenuPopup mMenuPopup;
private List<View> mFocusOrder;
@ -202,8 +202,8 @@ public class BrowserToolbar extends GeckoRelativeLayout
mForward = (ImageButton) findViewById(R.id.forward);
setButtonEnabled(mForward, false);
mMenu = (GeckoImageButton) findViewById(R.id.menu);
mMenuIcon = (GeckoImageView) findViewById(R.id.menu_icon);
mMenu = (ThemedImageButton) findViewById(R.id.menu);
mMenuIcon = (ThemedImageView) findViewById(R.id.menu_icon);
mActionItemBar = (LinearLayout) findViewById(R.id.menu_items);
mHasSoftMenuButton = !HardwareUtils.hasMenuButton();

View File

@ -8,7 +8,7 @@ import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.LightweightThemeDrawable;
import org.mozilla.gecko.R;
import org.mozilla.gecko.widget.GeckoImageButton;
import org.mozilla.gecko.widget.ThemedImageButton;
import android.content.Context;
import android.content.res.TypedArray;
@ -19,7 +19,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.util.AttributeSet;
public class ShapedButton extends GeckoImageButton
public class ShapedButton extends ThemedImageButton
implements CanvasDelegate.DrawManager {
protected final LightweightTheme mTheme;

View File

@ -7,7 +7,7 @@ package org.mozilla.gecko.toolbar;
import org.mozilla.gecko.animation.Rotate3DAnimation;
import org.mozilla.gecko.R;
import org.mozilla.gecko.widget.GeckoTextSwitcher;
import org.mozilla.gecko.widget.ThemedTextSwitcher;
import android.content.Context;
import android.os.Build;
@ -20,7 +20,7 @@ import android.view.View;
import android.util.AttributeSet;
import android.widget.ViewSwitcher;
public class TabCounter extends GeckoTextSwitcher
public class TabCounter extends ThemedTextSwitcher
implements ViewSwitcher.ViewFactory {
private static final float CENTER_X = 0.5f;

View File

@ -16,8 +16,8 @@ import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.toolbar.BrowserToolbar.ForwardButtonAnimation;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.widget.GeckoLinearLayout;
import org.mozilla.gecko.widget.GeckoTextView;
import org.mozilla.gecko.widget.ThemedLinearLayout;
import org.mozilla.gecko.widget.ThemedTextView;
import org.json.JSONObject;
@ -62,7 +62,7 @@ import java.util.List;
* {@code ToolbarDisplayLayout} is meant to be owned by {@code BrowserToolbar}
* which is the main event bus for the toolbar subsystem.
*/
public class ToolbarDisplayLayout extends GeckoLinearLayout
public class ToolbarDisplayLayout extends ThemedLinearLayout
implements Animation.AnimationListener {
private static final String LOGTAG = "GeckoToolbarDisplayLayout";
@ -99,7 +99,7 @@ public class ToolbarDisplayLayout extends GeckoLinearLayout
private UIMode mUiMode;
private GeckoTextView mTitle;
private ThemedTextView mTitle;
private int mTitlePadding;
private ToolbarTitlePrefs mTitlePrefs;
private OnTitleChangeListener mTitleChangeListener;
@ -139,7 +139,7 @@ public class ToolbarDisplayLayout extends GeckoLinearLayout
LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this);
mTitle = (GeckoTextView) findViewById(R.id.url_bar_title);
mTitle = (ThemedTextView) findViewById(R.id.url_bar_title);
mTitlePadding = mTitle.getPaddingRight();
final Resources res = getResources();

View File

@ -14,7 +14,7 @@ import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
import org.mozilla.gecko.toolbar.ToolbarEditText.OnTextTypeChangeListener;
import org.mozilla.gecko.toolbar.ToolbarEditText.TextType;
import org.mozilla.gecko.widget.GeckoLinearLayout;
import org.mozilla.gecko.widget.ThemedLinearLayout;
import android.content.Context;
import android.util.AttributeSet;
@ -31,7 +31,7 @@ import android.widget.ImageButton;
* and its matching 'go' button which changes depending on the
* current type of text in the entry.
*/
public class ToolbarEditLayout extends GeckoLinearLayout {
public class ToolbarEditLayout extends ThemedLinearLayout {
private final ToolbarEditText mEditText;
private final ImageButton mGo;

View File

@ -0,0 +1,42 @@
package org.mozilla.gecko.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import org.mozilla.gecko.R;
public class TabThumbnailWrapper extends FrameLayout {
private boolean mRecording = false;
private static final int[] STATE_RECORDING = { R.attr.state_recording };
public TabThumbnailWrapper(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TabThumbnailWrapper(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TabThumbnailWrapper(Context context) {
super(context);
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (mRecording) {
mergeDrawableStates(drawableState, STATE_RECORDING);
}
return drawableState;
}
public void setRecording(boolean recording) {
if (mRecording != recording) {
mRecording = recording;
refreshDrawableState();
}
}
}

View File

@ -1,3 +1,3 @@
#filter substitution
#define VIEWTYPE EditText
#include GeckoView.java.frag
#include ThemedView.java.frag

View File

@ -1,3 +1,3 @@
#filter substitution
#define VIEWTYPE ImageButton
#include GeckoView.java.frag
#include ThemedView.java.frag

View File

@ -1,3 +1,3 @@
#filter substitution
#define VIEWTYPE ImageView
#include GeckoView.java.frag
#include ThemedView.java.frag

View File

@ -1,3 +1,3 @@
#filter substitution
#define VIEWTYPE LinearLayout
#include GeckoView.java.frag
#include ThemedView.java.frag

View File

@ -1,3 +1,3 @@
#filter substitution
#define VIEWTYPE RelativeLayout
#include GeckoView.java.frag
#include ThemedView.java.frag

View File

@ -1,3 +1,3 @@
#filter substitution
#define VIEWTYPE TextSwitcher
#include GeckoView.java.frag
#include ThemedView.java.frag

View File

@ -1,3 +1,3 @@
#filter substitution
#define VIEWTYPE TextView
#include GeckoView.java.frag
#include ThemedView.java.frag

View File

@ -14,8 +14,8 @@ import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.widget.@VIEWTYPE@;
public class Gecko@VIEWTYPE@ extends @VIEWTYPE@
implements LightweightTheme.OnChangeListener {
public class Themed@VIEWTYPE@ extends @VIEWTYPE@
implements LightweightTheme.OnChangeListener {
private final LightweightTheme mTheme;
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
@ -31,7 +31,7 @@ public class Gecko@VIEWTYPE@ extends @VIEWTYPE@
private boolean mIsDark = false;
private boolean mAutoUpdateTheme = true;
public Gecko@VIEWTYPE@(Context context, AttributeSet attrs) {
public Themed@VIEWTYPE@(Context context, AttributeSet attrs) {
super(context, attrs);
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();

View File

@ -1,3 +1,9 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"
const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
@ -31,7 +37,15 @@ TabSource.prototype = {
title: title,
window: null
}).setSingleChoiceItems(tabs.map(function(tab) {
return { label: tab.browser.contentTitle || tab.browser.contentURI.spec }
let label;
if (tab.browser.contentTitle)
label = tab.browser.contentTitle;
else if (tab.browser.contentURI && tab.browser.contentURI.spec)
label = tab.browser.contentURI.spec;
else
label = tab.originalURI;
return { label: label,
icon: "thumbnail:" + tab.id }
}));
let result = null;
@ -56,7 +70,7 @@ TabSource.prototype = {
let tabs = app.tabs;
for (var i in tabs) {
if (tabs[i].browser.contentWindow == window) {
sendMessageToJava({ type: "Tab:Streaming", tabID: tabs[i].id });
sendMessageToJava({ type: "Tab:StreamStart", tabID: tabs[i].id });
}
}
},
@ -66,7 +80,7 @@ TabSource.prototype = {
let tabs = app.tabs;
for (let i in tabs) {
if (tabs[i].browser.contentWindow == window) {
sendMessageToJava({ type: "Tab:NotStreaming", tabID: tabs[i].id });
sendMessageToJava({ type: "Tab:StreamStop", tabID: tabs[i].id });
}
}
}

View File

@ -1,4 +1,5 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"
@ -36,7 +37,7 @@ function Prompt(aOptions) {
if ("hint" in aOptions && aOptions.hint != null)
this.msg.hint = aOptions.hint;
let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
}
Prompt.prototype = {
@ -178,6 +179,9 @@ Prompt.prototype = {
obj.label = item.label;
if (item.icon)
obj.icon = item.icon;
if (item.disabled)
obj.disabled = true;

View File

@ -25,8 +25,6 @@ error.logout.description = Sync encountered an error while connecting. It's pro
error.sync.title = Error While Syncing
error.sync.description = Sync encountered an error while syncing: %1$S. Sync will automatically retry this action.
error.sync.prolonged_failure = Sync has not been able to complete during the last %1$S days. Please check your network settings.
error.sync.no_node_found = The Sync server is a little busy right now, but you don't need to do anything about it. We'll start syncing your data as soon as we can!
error.sync.no_node_found.title = Sync Delay
error.sync.serverStatusButton.label = Server Status
error.sync.serverStatusButton.accesskey = V
error.sync.needUpdate.description = You need to update Firefox Sync to continue syncing your data.

View File

@ -690,7 +690,6 @@ Sync11Service.prototype = {
// to succeed, since that probably means we just don't have storage.
if (this.clusterURL == "" && !this._clusterManager.setCluster()) {
this.status.sync = NO_SYNC_NODE_FOUND;
Svc.Obs.notify("weave:service:sync:delayed");
return true;
}

View File

@ -79,7 +79,7 @@ let FunctionCallActor = protocol.ActorClass({
name: name,
stack: stack,
args: args,
return: result
result: result
};
this.meta = {

View File

@ -0,0 +1,431 @@
/* 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 {Cc, Ci, Cu, Cr} = require("chrome");
const Services = require("Services");
const events = require("sdk/event/core");
const protocol = require("devtools/server/protocol");
const { CallWatcherActor, CallWatcherFront } = require("devtools/server/actors/call-watcher");
const { on, once, off, emit } = events;
const { method, Arg, Option, RetVal } = protocol;
exports.register = function(handle) {
handle.addTabActor(WebAudioActor, "webaudioActor");
};
exports.unregister = function(handle) {
handle.removeTabActor(WebAudioActor);
};
const AUDIO_GLOBALS = [
"AudioContext", "AudioNode"
];
const NODE_CREATION_METHODS = [
"createBufferSource", "createMediaElementSource", "createMediaStreamSource",
"createMediaStreamDestination", "createScriptProcessor", "createAnalyser",
"createGain", "createDelay", "createBiquadFilter", "createWaveShaper",
"createPanner", "createConvolver", "createChannelSplitter", "createChannelMerger",
"createDynamicsCompressor", "createOscillator"
];
const NODE_ROUTING_METHODS = [
"connect", "disconnect"
];
/**
* An Audio Node actor allowing communication to a specific audio node in the
* Audio Context graph.
*/
let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
typeName: "audionode",
/**
* Create the Audio Node actor.
*
* @param DebuggerServerConnection conn
* The server connection.
* @param AudioNode node
* The AudioNode that was created.
*/
initialize: function (conn, node) {
protocol.Actor.prototype.initialize.call(this, conn);
this.node = unwrap(node);
try {
this.type = this.node.toString().match(/\[object (.*)\]$/)[1];
} catch (e) {
this.type = "";
}
},
/**
* Returns the name of the audio type.
* Examples: "OscillatorNode", "MediaElementAudioSourceNode"
*/
getType: method(function () {
return this.type;
}, {
response: { type: RetVal("string") }
}),
/**
* Returns a boolean indicating if the node is a source node,
* like BufferSourceNode, MediaElementAudioSourceNode, OscillatorNode, etc.
*/
isSource: method(function () {
return !!~this.type.indexOf("Source") || this.type === "OscillatorNode";
}, {
response: { source: RetVal("boolean") }
}),
/**
* Changes a param on the audio node. Responds with a `string` that's either
* an empty string `""` on success, or a description of the error upon
* param set failure.
*
* @param String param
* Name of the AudioParam to change.
* @param String value
* Value to change AudioParam to.
*/
setParam: method(function (param, value) {
// Strip quotes because sometimes UIs include that for strings
if (typeof value === "string") {
value = value.replace(/[\'\"]*/g, "");
}
try {
if (isAudioParam(this.node, param))
this.node[param].value = value;
else
this.node[param] = value;
return undefined;
} catch (e) {
return constructError(e);
}
}, {
request: {
param: Arg(0, "string"),
value: Arg(1, "nullable:primitive")
},
response: { error: RetVal("nullable:json") }
}),
/**
* Gets a param on the audio node.
*
* @param String param
* Name of the AudioParam to fetch.
*/
getParam: method(function (param) {
// If property does not exist, just return "undefined"
if (!this.node[param])
return undefined;
let value = isAudioParam(this.node, param) ? this.node[param].value : this.node[param];
let type = typeof type;
return value;
return { type: type, value: value };
}, {
request: {
param: Arg(0, "string")
},
response: { text: RetVal("nullable:primitive") }
}),
});
/**
* The corresponding Front object for the AudioNodeActor.
*/
let AudioNodeFront = protocol.FrontClass(AudioNodeActor, {
initialize: function (client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
client.addActorPool(this);
this.manage(this);
}
});
/**
* The Web Audio Actor handles simple interaction with an AudioContext
* high-level methods. After instantiating this actor, you'll need to set it
* up by calling setup().
*/
let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
typeName: "webaudio",
initialize: function(conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
this._onContentFunctionCall = this._onContentFunctionCall.bind(this);
},
destroy: function(conn) {
protocol.Actor.prototype.destroy.call(this, conn);
this.finalize();
},
/**
* Starts waiting for the current tab actor's document global to be
* created, in order to instrument the Canvas context and become
* aware of everything the content does with Web Audio.
*
* See ContentObserver and WebAudioInstrumenter for more details.
*/
setup: method(function({ reload }) {
if (this._initialized) {
return;
}
this._initialized = true;
// Weak map mapping audio nodes to their corresponding actors
this._nodeActors = new Map();
this._callWatcher = new CallWatcherActor(this.conn, this.tabActor);
this._callWatcher.onCall = this._onContentFunctionCall;
this._callWatcher.setup({
tracedGlobals: AUDIO_GLOBALS,
startRecording: true,
performReload: reload
});
// Used to track when something is happening with the web audio API
// the first time, to ultimately fire `start-context` event
this._firstNodeCreated = false;
}, {
request: { reload: Option(0, "boolean") },
oneway: true
}),
/**
* Invoked whenever an instrumented function is called, like an AudioContext
* method or an AudioNode method.
*/
_onContentFunctionCall: function(functionCall) {
let { name } = functionCall.details;
// All Web Audio nodes inherit from AudioNode's prototype, so
// hook into the `connect` and `disconnect` methods
if (WebAudioFront.NODE_ROUTING_METHODS.has(name)) {
this._handleRoutingCall(functionCall);
}
else if (WebAudioFront.NODE_CREATION_METHODS.has(name)) {
this._handleCreationCall(functionCall);
}
},
_handleRoutingCall: function(functionCall) {
let { caller, args, window, name } = functionCall.details;
let source = unwrap(caller);
let dest = unwrap(args[0]);
let isAudioParam = dest instanceof unwrap(window.AudioParam);
// audionode.connect(param)
if (name === "connect" && isAudioParam) {
this._onConnectParam(source, dest);
}
// audionode.connect(node)
else if (name === "connect") {
this._onConnectNode(source, dest);
}
// audionode.disconnect()
else if (name === "disconnect") {
this._onDisconnectNode(source);
}
},
_handleCreationCall: function (functionCall) {
let { caller, result } = functionCall.details;
// Keep track of the first node created, so we can alert
// the front end that an audio context is being used since
// we're not hooking into the constructor itself, just its
// instance's methods.
if (!this._firstNodeCreated) {
// Fire the start-up event if this is the first node created
// and trigger a `create-node` event for the context destination
this._onStartContext();
this._onCreateNode(unwrap(caller.destination));
this._firstNodeCreated = true;
}
this._onCreateNode(result);
},
/**
* Stops listening for document global changes and puts this actor
* to hibernation. This method is called automatically just before the
* actor is destroyed.
*/
finalize: method(function() {
if (!this._initialized) {
return;
}
this._initialized = false;
this._callWatcher.eraseRecording();
this._callWatcher.finalize();
this._callWatcher = null;
}, {
oneway: true
}),
/**
* Events emitted by this actor.
*/
events: {
"start-context": {
type: "startContext"
},
"connect-node": {
type: "connectNode",
source: Option(0, "audionode"),
dest: Option(0, "audionode")
},
"disconnect-node": {
type: "disconnectNode",
source: Arg(0, "audionode")
},
"connect-param": {
type: "connectParam",
source: Arg(0, "audionode"),
param: Arg(1, "string")
},
"change-param": {
type: "changeParam",
source: Option(0, "audionode"),
param: Option(0, "string"),
value: Option(0, "string")
},
"create-node": {
type: "createNode",
source: Arg(0, "audionode")
}
},
/**
* Helper for constructing an AudioNodeActor, assigning to
* internal weak map, and tracking via `manage` so it is assigned
* an `actorID`.
*/
_constructAudioNode: function (node) {
let actor = new AudioNodeActor(this.conn, node);
this.manage(actor);
this._nodeActors.set(node, actor);
return actor;
},
/**
* Takes an AudioNode and returns the stored actor for it.
* In some cases, we won't have an actor stored (for example,
* connecting to an AudioDestinationNode, since it's implicitly
* created), so make a new actor and store that.
*/
_actorFor: function (node) {
let actor = this._nodeActors.get(node);
if (!actor) {
actor = this._constructAudioNode(node);
}
return actor;
},
/**
* Called on first audio node creation, signifying audio context usage
*/
_onStartContext: function () {
events.emit(this, "start-context");
},
/**
* Called when one audio node is connected to another.
*/
_onConnectNode: function (source, dest) {
let sourceActor = this._actorFor(source);
let destActor = this._actorFor(dest);
events.emit(this, "connect-node", {
source: sourceActor,
dest: destActor
});
},
/**
* Called when an audio node is connected to an audio param.
* Implement in bug 986705
*/
_onConnectParam: function (source, dest) {
// TODO bug 986705
},
/**
* Called when an audio node is disconnected.
*/
_onDisconnectNode: function (node) {
let actor = this._actorFor(node);
events.emit(this, "disconnect-node", actor);
},
/**
* Called when a parameter changes on an audio node
*/
_onParamChange: function (node, param, value) {
let actor = this._actorFor(node);
events.emit(this, "param-change", {
source: actor,
param: param,
value: value
});
},
/**
* Called on node creation.
*/
_onCreateNode: function (node) {
let actor = this._constructAudioNode(node);
events.emit(this, "create-node", actor);
}
});
/**
* The corresponding Front object for the WebAudioActor.
*/
let WebAudioFront = exports.WebAudioFront = protocol.FrontClass(WebAudioActor, {
initialize: function(client, { webaudioActor }) {
protocol.Front.prototype.initialize.call(this, client, { actor: webaudioActor });
client.addActorPool(this);
this.manage(this);
}
});
WebAudioFront.NODE_CREATION_METHODS = new Set(NODE_CREATION_METHODS);
WebAudioFront.NODE_ROUTING_METHODS = new Set(NODE_ROUTING_METHODS);
/**
* Determines whether or not property is an AudioParam.
*
* @param AudioNode node
* An AudioNode.
* @param String prop
* Property of `node` to evaluate to see if it's an AudioParam.
* @return Boolean
*/
function isAudioParam (node, prop) {
return /AudioParam/.test(node[prop].toString());
}
/**
* Takes an `Error` object and constructs a JSON-able response
*
* @param Error err
* A TypeError, RangeError, etc.
* @return Object
*/
function constructError (err) {
return {
message: err.message,
type: err.constructor.name
};
}
function unwrap (obj) {
return XPCNativeWrapper.unwrap(obj);
}

View File

@ -24,12 +24,14 @@ let chromeGlobal = this;
// time we load child.js
DebuggerServer.addChildActors();
let conn;
let onConnect = DevToolsUtils.makeInfallible(function (msg) {
removeMessageListener("debug:connect", onConnect);
let mm = msg.target;
let conn = DebuggerServer.connectToParent(msg.data.prefix, mm);
conn = DebuggerServer.connectToParent(msg.data.prefix, mm);
let actor = new DebuggerServer.ContentActor(conn, chromeGlobal);
let actorPool = new ActorPool(conn);
@ -40,4 +42,15 @@ let chromeGlobal = this;
});
addMessageListener("debug:connect", onConnect);
let onDisconnect = DevToolsUtils.makeInfallible(function (msg) {
removeMessageListener("debug:disconnect", onDisconnect);
// Call DebuggerServerConnection.close to destroy all child actors
// (It should end up calling DebuggerServerConnection.onClosed
// that would actually cleanup all actor pools)
conn.close();
conn = null;
});
addMessageListener("debug:disconnect", onDisconnect);
})();

View File

@ -59,6 +59,7 @@ function loadSubScript(aURL)
}
}
let events = require("sdk/event/core");
let {defer, resolve, reject, promised, all} = require("sdk/core/promise");
this.defer = defer;
this.resolve = resolve;
@ -396,6 +397,7 @@ var DebuggerServer = {
this.registerModule("devtools/server/actors/call-watcher");
this.registerModule("devtools/server/actors/canvas");
this.registerModule("devtools/server/actors/webgl");
this.registerModule("devtools/server/actors/webaudio");
this.registerModule("devtools/server/actors/stylesheets");
this.registerModule("devtools/server/actors/styleeditor");
this.registerModule("devtools/server/actors/storage");
@ -571,6 +573,7 @@ var DebuggerServer = {
// If we have a child transport, the actor has already
// been created. We need to stop using this message manager.
childTransport.close();
childTransport = null;
aConnection.cancelForwarding(prefix);
} else {
// Otherwise, the app has been closed before the actor
@ -594,6 +597,20 @@ var DebuggerServer = {
Services.obs.addObserver(onMessageManagerDisconnect,
"message-manager-disconnect", false);
events.once(aConnection, "closed", () => {
if (childTransport) {
// When the client disconnects, we have to unplug the dedicated
// ChildDebuggerTransport...
childTransport.close();
childTransport = null;
aConnection.cancelForwarding(prefix);
// ... and notify the child process to clean the tab actors.
mm.sendAsyncMessage("debug:disconnect");
}
Services.obs.removeObserver(onMessageManagerDisconnect, "message-manager-disconnect");
});
mm.sendAsyncMessage("debug:connect", { prefix: prefix });
return deferred.promise;
@ -1156,6 +1173,11 @@ DebuggerServerConnection.prototype = {
*/
onClosed: function DSC_onClosed(aStatus) {
dumpn("Cleaning up connection.");
if (!this._actorPool) {
// Ignore this call if the connection is already closed.
return;
}
events.emit(this, "closed", aStatus);
this._actorPool.cleanup();
this._actorPool = null;

View File

@ -43,3 +43,4 @@ support-files =
[test_inspector_getImageData.html]
[test_memory.html]
[test_preference.html]
[test_connectToChild.html]

View File

@ -0,0 +1,127 @@
<SDOCTYPv HTM.>
<html>
<!--
Bug 966991 - Test DebuggerServer.connectToChild
-->
<head>
<meta charset="utf-8">
<title>Mozilla Bug</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script type="application/javascript;version=1.8">
let Cu = Components.utils;
let Cc = Components.classes;
let Ci = Components.interfaces;
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
// Always log packets when running tests.
SpecialPowers.pushPrefEnv({
"set": [
["devtools.debugger.log", true]
]
}, runTests);
}
function runTests() {
// Create a minimal iframe with a message manager
let iframe = document.createElement("iframe");
iframe.setAttribute("mozbrowser", "true");
document.body.appendChild(iframe);
let mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
// Register a test actor in the child process so that we can know if and when
// this fake actor is disconnected.
mm.loadFrameScript("data:text/javascript,new " + function FrameScriptScope() {
const {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
if (!DebuggerServer.initialized) {
DebuggerServer.init();
}
function TestActor() {dump("instanciate test actor");}
TestActor.prototype = {
actorPrefix: "test",
disconnect: function () {
sendAsyncMessage("test-actor-disconnected", null);
},
hello: function () {
return {msg:"world"};
}
};
TestActor.prototype.requestTypes = {
"hello": TestActor.prototype.hello
};
DebuggerServer.addTabActor(TestActor, "testActor");
}, false);
// Instantiate a minimal server
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
function firstClient() {
// Fake a first connection to an iframe
let transport = DebuggerServer.connectPipe();
let conn = transport._serverConnection;
let client = new DebuggerClient(transport);
DebuggerServer.connectToChild(conn, mm).then(actor => {
ok(actor.testActor, "Got the test actor");
// Ensure sending at least one request to our actor,
// otherwise it won't be instanciated, nor be disconnected...
client.request({
to: actor.testActor,
type: "hello",
}, function (response) {
// Then close the client. That should end up cleaning our test actor
client.close();
// Ensure that our test actor got cleaned up;
// its disconnect method should be called
mm.addMessageListener("test-actor-disconnected", function listener() {
mm.removeMessageListener("test-actor-disconnected", listener);
ok(true, "Actor is cleaned up");
secondClient(actor.testActor);
});
});
});
}
function secondClient(firstActor) {
// Then fake a second one, that should spawn a new set of tab actors
let transport = DebuggerServer.connectPipe();
let conn = transport._serverConnection;
let client = new DebuggerClient(transport);
DebuggerServer.connectToChild(conn, mm).then(actor => {
ok(actor.testActor, "Got a test actor for the second connection");
isnot(actor.testActor, firstActor, "We get different actor instances between two connections");
client.close(cleanup);
});
}
function cleanup() {
DebuggerServer.destroy();
iframe.remove();
SimpleTest.finish()
}
firstClient();
}
</script>
</pre>
</body>
</html>