merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2014-03-27 14:14:32 +01:00
commit a3d3c8dcb5
49 changed files with 1276 additions and 172 deletions

View File

@ -1003,6 +1003,11 @@ let BookmarkingUI = {
return this.notifier = document.getElementById("bookmarked-notification-anchor");
},
get dropmarkerNotifier() {
delete this.dropmarkerNotifier;
return this.dropmarkerNotifier = document.getElementById("bookmarked-notification-dropmarker-anchor");
},
get broadcaster() {
delete this.broadcaster;
let broadcaster = document.getElementById("bookmarkThisPageBroadcaster");
@ -1332,42 +1337,49 @@ let BookmarkingUI = {
},
_showBookmarkedNotification: function BUI_showBookmarkedNotification() {
/*
* We're dynamically setting pointer-events to none here for the duration
* of the bookmark menu button's dropmarker animation in order to avoid
* having it end up in the overflow menu. This happens because it gaining
* focus triggers a style change which triggers an overflow event, even
* though this does not happen if no focus change occurs. The core issue
* is tracked in https://bugzilla.mozilla.org/show_bug.cgi?id=981637
*/
let onDropmarkerAnimationEnd = () => {
this.button.removeEventListener("animationend", onDropmarkerAnimationEnd);
this.button.style.removeProperty("pointer-events");
};
let onDropmarkerAnimationStart = () => {
this.button.removeEventListener("animationstart", onDropmarkerAnimationStart);
this.button.style.pointerEvents = 'none';
};
function getCenteringTransformForRects(rectToPosition, referenceRect) {
let topDiff = referenceRect.top - rectToPosition.top;
let leftDiff = referenceRect.left - rectToPosition.left;
let heightDiff = referenceRect.height - rectToPosition.height;
let widthDiff = referenceRect.width - rectToPosition.width;
return [(leftDiff + .5 * widthDiff) + "px", (topDiff + .5 * heightDiff) + "px"];
}
if (this._notificationTimeout) {
clearTimeout(this._notificationTimeout);
}
if (this.notifier.style.transform == '') {
// Get all the relevant nodes and computed style objects
let dropmarker = document.getAnonymousElementByAttribute(this.button, "anonid", "dropmarker");
let dropmarkerIcon = document.getAnonymousElementByAttribute(dropmarker, "class", "dropmarker-icon");
let dropmarkerStyle = getComputedStyle(dropmarkerIcon);
// Check for RTL and get bounds
let isRTL = getComputedStyle(this.button).direction == "rtl";
let buttonRect = this.button.getBoundingClientRect();
let notifierRect = this.notifier.getBoundingClientRect();
let topDiff = buttonRect.top - notifierRect.top;
let leftDiff = buttonRect.left - notifierRect.left;
let heightDiff = buttonRect.height - notifierRect.height;
let widthDiff = buttonRect.width - notifierRect.width;
let translateX = (leftDiff + .5 * widthDiff) + "px";
let translateY = (topDiff + .5 * heightDiff) + "px";
let transform = "translate(" + translateX + ", " + translateY + ")";
let dropmarkerRect = dropmarkerIcon.getBoundingClientRect();
let dropmarkerNotifierRect = this.dropmarkerNotifier.getBoundingClientRect();
// Compute, but do not set, transform for star icon
let [translateX, translateY] = getCenteringTransformForRects(notifierRect, buttonRect);
let starIconTransform = "translate(" + translateX + ", " + translateY + ")";
if (isRTL) {
transform += " scaleX(-1)";
starIconTransform += " scaleX(-1)";
}
this.notifier.style.transform = transform;
// Compute, but do not set, transform for dropmarker
[translateX, translateY] = getCenteringTransformForRects(dropmarkerNotifierRect, dropmarkerRect);
let dropmarkerTransform = "translate(" + translateX + ", " + translateY + ")";
// Do all layout invalidation in one go:
this.notifier.style.transform = starIconTransform;
this.dropmarkerNotifier.style.transform = dropmarkerTransform;
let dropmarkerAnimationNode = this.dropmarkerNotifier.firstChild;
dropmarkerAnimationNode.style.MozImageRegion = dropmarkerStyle.MozImageRegion;
dropmarkerAnimationNode.style.listStyleImage = dropmarkerStyle.listStyleImage;
}
let isInBookmarksToolbar = this.button.classList.contains("bookmark-item");
@ -1378,16 +1390,17 @@ let BookmarkingUI = {
if (!isInOverflowPanel) {
this.notifier.setAttribute("notification", "finish");
this.button.setAttribute("notification", "finish");
this.button.addEventListener('animationstart', onDropmarkerAnimationStart);
this.button.addEventListener("animationend", onDropmarkerAnimationEnd);
this.dropmarkerNotifier.setAttribute("notification", "finish");
}
this._notificationTimeout = setTimeout( () => {
this.notifier.removeAttribute("notification");
this.notifier.removeAttribute("in-bookmarks-toolbar");
this.notifier.removeAttribute("notification");
this.dropmarkerNotifier.removeAttribute("notification");
this.button.removeAttribute("notification");
this.dropmarkerNotifier.style.transform = '';
this.notifier.style.transform = '';
this.button.style.removeProperty("pointer-events");
}, 1000);
},

View File

@ -472,6 +472,9 @@
<vbox id="bookmarked-notification-anchor">
<vbox id="bookmarked-notification"/>
</vbox>
<vbox id="bookmarked-notification-dropmarker-anchor">
<image id="bookmarked-notification-dropmarker-icon"/>
</vbox>
</hbox>
<tooltip id="dynamic-shortcut-tooltip"

View File

@ -2041,6 +2041,13 @@ let CustomizableUIInternal = {
destroyWidget: function(aWidgetId) {
let widget = gPalette.get(aWidgetId);
if (!widget) {
gGroupWrapperCache.delete(aWidgetId);
for (let [window, ] of gBuildWindows) {
let windowCache = gSingleWrapperCache.get(window);
if (windowCache) {
windowCache.delete(aWidgetId);
}
}
return;
}
@ -3430,7 +3437,7 @@ function XULWidgetGroupWrapper(aWidgetId) {
instance = aWindow.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
}
let wrapper = new XULWidgetSingleWrapper(aWidgetId, instance);
let wrapper = new XULWidgetSingleWrapper(aWidgetId, instance, aWindow.document);
wrapperMap.set(aWidgetId, wrapper);
return wrapper;
};
@ -3456,14 +3463,47 @@ function XULWidgetGroupWrapper(aWidgetId) {
* A XULWidgetSingleWrapper is a wrapper around a single instance of a XUL
* widget in a particular window.
*/
function XULWidgetSingleWrapper(aWidgetId, aNode) {
function XULWidgetSingleWrapper(aWidgetId, aNode, aDocument) {
this.isGroup = false;
this.id = aWidgetId;
this.type = "custom";
this.provider = CustomizableUI.PROVIDER_XUL;
this.node = aNode;
let weakDoc = Cu.getWeakReference(aDocument);
// If we keep a strong ref, the weak ref will never die, so null it out:
aDocument = null;
this.__defineGetter__("node", function() {
// If we've set this to null (further down), we're sure there's nothing to
// be gotten here, so bail out early:
if (!weakDoc) {
return null;
}
if (aNode) {
// Return the last known node if it's still in the DOM...
if (aNode.ownerDocument.contains(aNode)) {
return aNode;
}
// ... or the toolbox
let toolbox = aNode.ownerDocument.defaultView.gNavToolbox;
if (toolbox && toolbox.palette && aNode.parentNode == toolbox.palette) {
return aNode;
}
// If it isn't, clear the cached value and fall through to the "slow" case:
aNode = null;
}
let doc = weakDoc.get();
if (doc) {
// Store locally so we can cache the result:
aNode = CustomizableUIInternal.findWidgetInWindow(aWidgetId, doc.defaultView);
return aNode;
}
// The weakref to the document is dead, we're done here forever more:
weakDoc = null;
return null;
});
this.__defineGetter__("anchor", function() {
let anchorId;
@ -3472,16 +3512,21 @@ function XULWidgetSingleWrapper(aWidgetId, aNode) {
if (placement) {
anchorId = gAreas.get(placement.area).get("anchor");
}
if (!anchorId) {
anchorId = aNode.getAttribute("cui-anchorid");
let node = this.node;
if (!anchorId && node) {
anchorId = node.getAttribute("cui-anchorid");
}
return anchorId ? aNode.ownerDocument.getElementById(anchorId)
: aNode;
return (anchorId && node) ? node.ownerDocument.getElementById(anchorId) : node;
});
this.__defineGetter__("overflowed", function() {
return aNode.getAttribute("overflowedItem") == "true";
let node = this.node;
if (!node) {
return false;
}
return node.getAttribute("overflowedItem") == "true";
});
Object.freeze(this);

View File

@ -90,4 +90,6 @@ skip-if = os == "linux"
[browser_981418-widget-onbeforecreated-handler.js]
[browser_985815_propagate_setToolbarVisibility.js]
[browser_981305_separator_insertion.js]
[browser_987177_destroyWidget_xul.js]
[browser_987177_xul_wrapper_updating.js]
[browser_panel_toggle.js]

View File

@ -0,0 +1,33 @@
/* 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 BUTTONID = "test-XUL-wrapper-destroyWidget";
add_task(function() {
let btn = createDummyXULButton(BUTTONID, "XUL btn");
gNavToolbox.palette.appendChild(btn);
let firstWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window);
ok(firstWrapper, "Should get a wrapper");
ok(firstWrapper.node, "Node should be there on first wrapper.");
btn.remove();
CustomizableUI.destroyWidget(BUTTONID);
let secondWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window);
isnot(firstWrapper, secondWrapper, "Wrappers should be different after destroyWidget call.");
ok(!firstWrapper.node, "No node should be there on old wrapper.");
ok(!secondWrapper.node, "No node should be there on new wrapper.");
btn = createDummyXULButton(BUTTONID, "XUL btn");
gNavToolbox.palette.appendChild(btn);
let thirdWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window);
ok(thirdWrapper, "Should get a wrapper");
is(secondWrapper, thirdWrapper, "Should get the second wrapper again.");
ok(firstWrapper.node, "Node should be there on old wrapper.");
ok(secondWrapper.node, "Node should be there on second wrapper.");
ok(thirdWrapper.node, "Node should be there on third wrapper.");
});

View File

@ -0,0 +1,74 @@
/* 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 BUTTONID = "test-XUL-wrapper-widget";
add_task(function() {
let btn = createDummyXULButton(BUTTONID, "XUL btn");
gNavToolbox.palette.appendChild(btn);
let groupWrapper = CustomizableUI.getWidget(BUTTONID);
ok(groupWrapper, "Should get a group wrapper");
let singleWrapper = groupWrapper.forWindow(window);
ok(singleWrapper, "Should get a single wrapper");
is(singleWrapper.node, btn, "Node should be in the wrapper");
is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR);
let otherSingleWrapper = groupWrapper.forWindow(window);
is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after adding the node to the navbar.");
is(singleWrapper.node, btn, "Node should be in the wrapper");
is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
CustomizableUI.removeWidgetFromArea(BUTTONID);
otherSingleWrapper = groupWrapper.forWindow(window);
isnot(singleWrapper, otherSingleWrapper, "Shouldn't get the same wrapper after removing it from the navbar.");
singleWrapper = otherSingleWrapper;
is(singleWrapper.node, btn, "Node should be in the wrapper");
is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
btn.remove();
otherSingleWrapper = groupWrapper.forWindow(window);
is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after physically removing the node.");
is(singleWrapper.node, null, "Wrapper's node should be null now that it's left the DOM.");
is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
is(groupWrapper.instances[0].node, null, "That instance should be null.");
btn = createDummyXULButton(BUTTONID, "XUL btn");
gNavToolbox.palette.appendChild(btn);
otherSingleWrapper = groupWrapper.forWindow(window);
is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after readding the node.");
is(singleWrapper.node, btn, "Node should be in the wrapper");
is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR);
otherSingleWrapper = groupWrapper.forWindow(window);
is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after adding the node to the navbar.");
is(singleWrapper.node, btn, "Node should be in the wrapper");
is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
CustomizableUI.removeWidgetFromArea(BUTTONID);
otherSingleWrapper = groupWrapper.forWindow(window);
isnot(singleWrapper, otherSingleWrapper, "Shouldn't get the same wrapper after removing it from the navbar.");
singleWrapper = otherSingleWrapper;
is(singleWrapper.node, btn, "Node should be in the wrapper");
is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
btn.remove();
otherSingleWrapper = groupWrapper.forWindow(window);
is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after physically removing the node.");
is(singleWrapper.node, null, "Wrapper's node should be null now that it's left the DOM.");
is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
is(groupWrapper.instances[0].node, null, "That instance should be null.");
});

View File

@ -191,13 +191,15 @@ let DebuggerController = {
this._connection = startedDebugging.promise;
let target = this._target;
let { client, form: { chromeDebugger, traceActor } } = target;
let { client, form: { chromeDebugger, traceActor, addonActor } } = target;
target.on("close", this._onTabDetached);
target.on("navigate", this._onTabNavigated);
target.on("will-navigate", this._onTabNavigated);
this.client = client;
if (target.chrome) {
if (addonActor) {
this._startAddonDebugging(addonActor, startedDebugging.resolve);
} else if (target.chrome) {
this._startChromeDebugging(chromeDebugger, startedDebugging.resolve);
} else {
this._startDebuggingTab(startedDebugging.resolve);
@ -309,6 +311,20 @@ let DebuggerController = {
});
},
/**
* Sets up an addon debugging session.
*
* @param object aAddonActor
* The actor for the addon that is being debugged.
* @param function aCallback
* A function to invoke once the client attaches to the active thread.
*/
_startAddonDebugging: function(aAddonActor, aCallback) {
this.client.attachAddon(aAddonActor, (aResponse) => {
return this._startChromeDebugging(aResponse.threadActor, aCallback);
});
},
/**
* Sets up a chrome debugging session.
*
@ -2159,6 +2175,22 @@ Object.defineProperties(window, {
}
});
/**
* Helper method for parsing a resource URI, like
* `resource://gre/modules/commonjs/sdk/tabs.js`, and pulling out `sdk/tabs.js`
* if it's in the SDK, or `null` otherwise.
*
* @param string url
* @return string|null
*/
function getSDKModuleName(url) {
let match = (url || "").match(/^resource:\/\/gre\/modules\/commonjs\/(.*)/);
if (match) {
return match[1];
}
return null;
}
/**
* Helper method for debugging.
* @param string

View File

@ -126,10 +126,17 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
* - staged: true to stage the item to be appended later
*/
addSource: function(aSource, aOptions = {}) {
let url = aSource.url;
let label = SourceUtils.getSourceLabel(url.split(" -> ").pop());
let group = SourceUtils.getSourceGroup(url.split(" -> ").pop());
let unicodeUrl = NetworkHelper.convertToUnicode(unescape(url));
let fullUrl = aSource.url;
let url = fullUrl.split(" -> ").pop();
let label = SourceUtils.getSourceLabel(url);
let group = SourceUtils.getSourceGroup(url);
let unicodeUrl = NetworkHelper.convertToUnicode(unescape(fullUrl));
let sdkModuleName = getSDKModuleName(url);
if (sdkModuleName) {
label = sdkModuleName;
group = "Add-on SDK";
}
let contents = document.createElement("label");
contents.className = "plain dbg-source-item";
@ -139,7 +146,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
contents.setAttribute("tooltiptext", unicodeUrl);
// Append a source item to this container.
this.push([contents, url], {
this.push([contents, fullUrl], {
staged: aOptions.staged, /* stage the item to be appended later? */
attachment: {
label: label,

View File

@ -0,0 +1,23 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { interfaces: Ci, classes: Cc } = Components;
function startup(aParams, aReason) {
Components.utils.import("resource://gre/modules/Services.jsm");
let res = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
res.setSubstitution("browser_dbg_addon4", aParams.resourceURI);
// Load a JS module
Components.utils.import("resource://browser_dbg_addon4/test.jsm");
}
function shutdown(aParams, aReason) {
// Unload the JS module
Components.utils.unload("resource://browser_dbg_addon4/test.jsm");
let res = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
res.setSubstitution("browser_dbg_addon4", null);
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>browser_dbg_addon4@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>Test add-on with JS Modules</em:name>
<em:bootstrap>true</em:bootstrap>
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const EXPORTED_SYMBOLS = ["Foo"];
const Foo = {};

View File

@ -0,0 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const EXPORTED_SYMBOLS = ["Bar"];
const Bar = {};

Binary file not shown.

View File

@ -3,6 +3,7 @@ support-files =
addon1.xpi
addon2.xpi
addon3.xpi
addon4.xpi
code_binary_search.coffee
code_binary_search.js
code_binary_search.map
@ -82,6 +83,8 @@ support-files =
[browser_dbg_aaa_run_first_leaktest.js]
[browser_dbg_addonactor.js]
[browser_dbg_addon-sources.js]
[browser_dbg_addon-modules.js]
[browser_dbg_auto-pretty-print-01.js]
[browser_dbg_auto-pretty-print-02.js]
[browser_dbg_bfcache.js]

View File

@ -0,0 +1,114 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Make sure the add-on actor can see loaded JS Modules from an add-on
const ADDON4_URL = EXAMPLE_URL + "addon4.xpi";
let gAddon, gClient, gThreadClient, gDebugger, gSources;
function test() {
Task.spawn(function () {
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
gBrowser.selectedTab = gBrowser.addTab();
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
let connected = promise.defer();
gClient.connect(connected.resolve);
yield connected.promise;
yield installAddon();
let debuggerPanel = yield initAddonDebugger(gClient, ADDON4_URL, iframe);
gDebugger = debuggerPanel.panelWin;
gThreadClient = gDebugger.gThreadClient;
gSources = gDebugger.DebuggerView.Sources;
yield testSources(false);
Cu.import("resource://browser_dbg_addon4/test2.jsm", {});
yield testSources(true);
Cu.unload("resource://browser_dbg_addon4/test2.jsm");
yield uninstallAddon();
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
finish();
});
}
function installAddon () {
return addAddon(ADDON4_URL).then(aAddon => {
gAddon = aAddon;
});
}
function testSources(expectSecondModule) {
let deferred = promise.defer();
let foundAddonModule = false;
let foundAddonModule2 = false;
let foundAddonBootstrap = false;
gThreadClient.getSources(({sources}) => {
ok(sources.length, "retrieved sources");
sources.forEach(source => {
let url = source.url.split(" -> ").pop();
let { label, group } = gSources.getItemByValue(source.url).attachment;
if (url.indexOf("resource://browser_dbg_addon4/test.jsm") === 0) {
is(label, "test.jsm", "correct label for addon code");
is(group, "resource://browser_dbg_addon4", "addon module is in its own group");
foundAddonModule = true;
} else if (url.indexOf("resource://browser_dbg_addon4/test2.jsm") === 0) {
is(label, "test2.jsm", "correct label for addon code");
is(group, "resource://browser_dbg_addon4", "addon module is in its own group");
foundAddonModule2 = true;
} else if (url.endsWith("/browser_dbg_addon4@tests.mozilla.org.xpi!/bootstrap.js")) {
is(label, "bootstrap.js", "correct label for bootstrap code");
is(group, "jar:", "addon bootstrap script is in its own group");
foundAddonBootstrap = true;
} else {
ok(false, "Saw an unexpected source: " + url);
}
});
ok(foundAddonModule, "found JS module for the addon in the list");
is(foundAddonModule2, expectSecondModule, "saw the second addon module");
ok(foundAddonBootstrap, "found bootstrap script for the addon in the list");
deferred.resolve();
});
return deferred.promise;
}
function uninstallAddon() {
return removeAddon(gAddon);
}
function closeConnection () {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gAddon = null;
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1)
gBrowser.removeCurrentTab();
});

View File

@ -0,0 +1,115 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Ensure that the sources listed when debugging an addon are either from the
// addon itself, or the SDK, with proper groups and labels.
const ADDON3_URL = EXAMPLE_URL + "addon3.xpi";
let gAddon, gClient, gThreadClient, gDebugger, gSources;
function test() {
Task.spawn(function () {
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
gBrowser.selectedTab = gBrowser.addTab();
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
let connected = promise.defer();
gClient.connect(connected.resolve);
yield connected.promise;
yield installAddon();
let debuggerPanel = yield initAddonDebugger(gClient, ADDON3_URL, iframe);
gDebugger = debuggerPanel.panelWin;
gThreadClient = gDebugger.gThreadClient;
gSources = gDebugger.DebuggerView.Sources;
yield testSources();
yield uninstallAddon();
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
finish();
});
}
function installAddon () {
return addAddon(ADDON3_URL).then(aAddon => {
gAddon = aAddon;
});
}
function testSources() {
let deferred = promise.defer();
let foundAddonModule = false;
let foundSDKModule = 0;
let foundAddonBootstrap = false;
gThreadClient.getSources(({sources}) => {
ok(sources.length, "retrieved sources");
sources.forEach(source => {
let url = source.url.split(" -> ").pop();
info(source.url + "\n\n\n" + url);
let { label, group } = gSources.getItemByValue(source.url).attachment;
if (url.indexOf("resource://gre/modules/commonjs/") === 0) {
is(label, url.substring(32), "correct truncated label");
is(group, "Add-on SDK", "correct SDK group");
foundSDKModule++;
} else if (url.indexOf("resource://gre/modules/commonjs/method") === 0) {
is(label.indexOf("method/"), 0, "correct truncated label");
is(group, "Add-on SDK", "correct SDK group");
foundSDKModule++;
} else if (url.indexOf("resource://jid1-ami3akps3baaeg-at-jetpack") === 0) {
is(label, "main.js", "correct label for addon code");
is(group, "resource://jid1-ami3akps3baaeg-at-jetpack", "addon code is in its own group");
foundAddonModule = true;
} else if (url.endsWith("/jid1-ami3akps3baaeg@jetpack.xpi!/bootstrap.js")) {
is(label, "bootstrap.js", "correct label for bootstrap script");
is(group, "jar:", "bootstrap script is in its own group");
foundAddonBootstrap = true;
} else {
ok(false, "Saw an unexpected source: " + url);
}
});
ok(foundAddonModule, "found code for the addon in the list");
ok(foundAddonBootstrap, "found bootstrap for the addon in the list");
// Be flexible in this number, as SDK changes could change the exact number of
// built-in browser SDK modules
ok(foundSDKModule > 10, "SDK modules are listed");
deferred.resolve();
});
return deferred.promise;
}
function uninstallAddon() {
return removeAddon(gAddon);
}
function closeConnection () {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gAddon = null;
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1)
gBrowser.removeCurrentTab();
});

View File

@ -164,6 +164,7 @@ function getAddonActorForUrl(aClient, aUrl) {
aClient.listAddons(aResponse => {
let addonActor = aResponse.addons.filter(aGrip => aGrip.url == aUrl).pop();
info("got addon actor for URL: " + addonActor.actor);
deferred.resolve(addonActor);
});
@ -500,6 +501,34 @@ function initDebugger(aTarget, aWindow) {
});
}
function initAddonDebugger(aClient, aUrl, aFrame) {
info("Initializing an addon debugger panel.");
return getAddonActorForUrl(aClient, aUrl).then(({actor}) => {
let targetOptions = {
form: { addonActor: actor },
client: aClient,
chrome: true
};
let toolboxOptions = {
customIframe: aFrame
};
let target = devtools.TargetFactory.forTab(targetOptions);
return gDevTools.showToolbox(target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions);
}).then(aToolbox => {
info("Addon debugger panel shown successfully.");
let debuggerPanel = aToolbox.getCurrentPanel();
// Wait for the initial resume...
return waitForClientEvents(debuggerPanel, "resumed")
.then(() => prepareDebugger(debuggerPanel))
.then(() => debuggerPanel);
});
}
function initChromeDebugger(aOnClose) {
info("Initializing a chrome debugger process.");

View File

@ -26,10 +26,22 @@ this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
* A function called when the process stops running.
* @param function aOnRun [optional]
* A function called when the process starts running.
* @param object aOptions [optional]
* An object with properties for configuring BrowserToolboxProcess.
*/
this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun) {
this._closeCallback = aOnClose;
this._runCallback = aOnRun;
this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun, aOptions) {
// If first argument is an object, use those properties instead of
// all three arguments
if (typeof aOnClose === "object") {
this._closeCallback = aOnClose.onClose;
this._runCallback = aOnClose.onRun;
this._options = aOnClose;
} else {
this._closeCallback = aOnClose;
this._runCallback = aOnRun;
this._options = aOptions || {};
}
this._telemetry = new Telemetry();
this.close = this.close.bind(this);
@ -43,8 +55,8 @@ this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun) {
* Initializes and starts a chrome toolbox process.
* @return object
*/
BrowserToolboxProcess.init = function(aOnClose, aOnRun) {
return new BrowserToolboxProcess(aOnClose, aOnRun);
BrowserToolboxProcess.init = function(aOnClose, aOnRun, aOptions) {
return new BrowserToolboxProcess(aOnClose, aOnRun, aOptions);
};
BrowserToolboxProcess.prototype = {
@ -143,8 +155,15 @@ BrowserToolboxProcess.prototype = {
let process = this._dbgProcess = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile));
let xulURI = DBG_XUL;
if (this._options.addonID) {
xulURI += "?addonID=" + this._options.addonID;
}
dumpn("Running chrome debugging process.");
let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", DBG_XUL];
let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", xulURI];
process.runwAsync(args, args.length, { observe: () => this.close() });
this._telemetry.toolOpened("jsbrowserdebugger");

View File

@ -32,7 +32,16 @@ function connect() {
);
gClient = new DebuggerClient(transport);
gClient.connect(() => {
gClient.listTabs(openToolbox);
let addonID = getParameterByName("addonID");
if (addonID) {
gClient.listAddons(({addons}) => {
let addonActor = addons.filter(addon => addon.id === addonID).pop();
openToolbox({ addonActor: addonActor.actor });
});
} else {
gClient.listTabs(openToolbox);
}
});
}
@ -106,3 +115,10 @@ function quitApp() {
Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
}
}
function getParameterByName (name) {
let name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
let regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
let results = regex.exec(window.location.search);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

View File

@ -165,6 +165,17 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
opacity: 0;
}
#bookmarked-notification-dropmarker-anchor {
z-index: -1;
position: relative;
}
#bookmarked-notification-dropmarker-icon {
width: 18px;
height: 18px;
visibility: hidden;
}
#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
animation: animation-bookmarkAdded 800ms;
@ -176,6 +187,11 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
}
#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
list-style-image: none !important;
}
#bookmarked-notification-dropmarker-anchor[notification="finish"] > #bookmarked-notification-dropmarker-icon {
visibility: visible;
animation: animation-bookmarkPulse 300ms;
animation-delay: 600ms;
animation-timing-function: ease-out;

View File

@ -397,6 +397,17 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
opacity: 0;
}
#bookmarked-notification-dropmarker-anchor {
z-index: -1;
position: relative;
}
#bookmarked-notification-dropmarker-icon {
width: 18px;
height: 18px;
visibility: hidden;
}
#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
animation: animation-bookmarkAdded 800ms;
@ -410,6 +421,11 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
}
#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
list-style-image: none !important;
}
#bookmarked-notification-dropmarker-anchor[notification="finish"] > #bookmarked-notification-dropmarker-icon {
visibility: visible;
animation: animation-bookmarkPulse 300ms;
animation-delay: 600ms;
animation-timing-function: ease-out;

View File

@ -419,6 +419,17 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
opacity: 0;
}
#bookmarked-notification-dropmarker-anchor {
z-index: -1;
position: relative;
}
#bookmarked-notification-dropmarker-icon {
width: 18px;
height: 18px;
visibility: hidden;
}
#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
animation: animation-bookmarkAdded 800ms;
@ -426,6 +437,11 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
}
#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
list-style-image: none !important;
}
#bookmarked-notification-dropmarker-anchor[notification="finish"] > #bookmarked-notification-dropmarker-icon {
visibility: visible;
animation: animation-bookmarkPulse 300ms;
animation-delay: 600ms;
animation-timing-function: ease-out;

View File

@ -2287,8 +2287,13 @@ public class GeckoAppShell
}
@WrapElementForJNI(stubName = "HandleGeckoMessageWrapper")
public static void handleGeckoMessage(String message) {
sEventDispatcher.dispatchEvent(message);
public static void handleGeckoMessage(final String message) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
sEventDispatcher.dispatchEvent(message);
}
});
}
@WrapElementForJNI

View File

@ -7,9 +7,12 @@ package org.mozilla.gecko;
import org.mozilla.gecko.gfx.InputConnectionHandler;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
import org.json.JSONObject;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@ -75,7 +78,7 @@ interface GeckoEditableListener {
*/
final class GeckoEditable
implements InvocationHandler, Editable,
GeckoEditableClient, GeckoEditableListener {
GeckoEditableClient, GeckoEditableListener, GeckoEventListener {
private static final boolean DEBUG = false;
private static final String LOGTAG = "GeckoEditable";
@ -101,6 +104,7 @@ final class GeckoEditable
private int mLastIcUpdateSeqno;
private boolean mUpdateGecko;
private boolean mFocused;
private volatile boolean mSuppressCompositions;
private volatile boolean mSuppressKeyUp;
/* An action that alters the Editable
@ -396,7 +400,10 @@ final class GeckoEditable
private void icUpdateGecko(boolean force) {
if (!force && mIcUpdateSeqno == mLastIcUpdateSeqno) {
// Skip if receiving a repeated request, or
// if suppressing compositions during text selection.
if ((!force && mIcUpdateSeqno == mLastIcUpdateSeqno) ||
mSuppressCompositions) {
if (DEBUG) {
Log.d(LOGTAG, "icUpdateGecko() skipped");
}
@ -750,6 +757,17 @@ final class GeckoEditable
mListener.notifyIME(type);
}
});
// Register/unregister Gecko-side text selection listeners
if (type == NOTIFY_IME_OF_BLUR) {
mSuppressCompositions = false;
GeckoAppShell.getEventDispatcher().
unregisterEventListener("TextSelection:IMECompositions", this);
} else if (type == NOTIFY_IME_OF_FOCUS) {
mSuppressCompositions = false;
GeckoAppShell.getEventDispatcher().
registerEventListener("TextSelection:IMECompositions", this);
}
}
@Override
@ -1199,5 +1217,16 @@ final class GeckoEditable
public String toString() {
throw new UnsupportedOperationException("method must be called through mProxy");
}
// GeckoEventListener implementation
@Override
public void handleMessage(String event, JSONObject message) {
if (!"TextSelection:IMECompositions".equals(event)) {
return;
}
mSuppressCompositions = message.optBoolean("suppress", false);
}
}

View File

@ -51,6 +51,8 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
protected Preference needsPasswordPreference;
protected Preference needsUpgradePreference;
protected Preference needsVerificationPreference;
protected Preference needsMasterSyncAutomaticallyEnabledPreference;
protected Preference needsAccountEnabledPreference;
protected PreferenceCategory syncCategory;
@ -87,6 +89,8 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
needsPasswordPreference = ensureFindPreference("needs_credentials");
needsUpgradePreference = ensureFindPreference("needs_upgrade");
needsVerificationPreference = ensureFindPreference("needs_verification");
needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled");
needsAccountEnabledPreference = ensureFindPreference("needs_account_enabled");
syncCategory = (PreferenceCategory) ensureFindPreference("sync_category");
@ -103,6 +107,7 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
needsPasswordPreference.setOnPreferenceClickListener(this);
needsVerificationPreference.setOnPreferenceClickListener(this);
needsAccountEnabledPreference.setOnPreferenceClickListener(this);
bookmarksPreference.setOnPreferenceClickListener(this);
historyPreference.setOnPreferenceClickListener(this);
@ -143,6 +148,13 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
return true;
}
if (preference == needsAccountEnabledPreference) {
fxAccount.enableSyncing();
refresh();
return true;
}
if (preference == bookmarksPreference ||
preference == historyPreference ||
preference == passwordsPreference ||
@ -171,7 +183,10 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
final Preference[] errorPreferences = new Preference[] {
this.needsPasswordPreference,
this.needsUpgradePreference,
this.needsVerificationPreference };
this.needsVerificationPreference,
this.needsMasterSyncAutomaticallyEnabledPreference,
this.needsAccountEnabledPreference,
};
for (Preference errorPreference : errorPreferences) {
final boolean currentlyShown = null != findPreference(errorPreference.getKey());
final boolean shouldBeShown = errorPreference == errorPreferenceToShow;
@ -204,6 +219,18 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
setCheckboxesEnabled(false);
}
protected void showNeedsMasterSyncAutomaticallyEnabled() {
syncCategory.setTitle(R.string.fxaccount_status_sync);
showOnlyOneErrorPreference(needsMasterSyncAutomaticallyEnabledPreference);
setCheckboxesEnabled(false);
}
protected void showNeedsAccountEnabled() {
syncCategory.setTitle(R.string.fxaccount_status_sync);
showOnlyOneErrorPreference(needsAccountEnabledPreference);
setCheckboxesEnabled(false);
}
protected void showConnected() {
syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
showOnlyOneErrorPreference(null);
@ -245,23 +272,49 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
emailPreference.setTitle(fxAccount.getEmail());
// Interrogate the Firefox Account's state.
State state = fxAccount.getState();
switch (state.getNeededAction()) {
case NeedsUpgrade:
showNeedsUpgrade();
break;
case NeedsPassword:
showNeedsPassword();
break;
case NeedsVerification:
showNeedsVerification();
break;
default:
showConnected();
}
try {
// There are error states determined by Android, not the login state
// machine, and we have a chance to present these states here. We handle
// them specially, since we can't surface these states as part of syncing,
// because they generally stop syncs from happening regularly.
updateSelectedEngines();
// The action to enable syncing the Firefox Account doesn't require
// leaving this activity, so let's present it first.
final boolean isSyncing = fxAccount.isSyncing();
if (!isSyncing) {
showNeedsAccountEnabled();
return;
}
// Interrogate the Firefox Account's state.
State state = fxAccount.getState();
switch (state.getNeededAction()) {
case NeedsUpgrade:
showNeedsUpgrade();
break;
case NeedsPassword:
showNeedsPassword();
break;
case NeedsVerification:
showNeedsVerification();
break;
default:
showConnected();
}
// We check for the master setting last, since it is not strictly
// necessary for the user to address this error state: it's really a
// warning state. We surface it for the user's convenience, and to prevent
// confused folks wondering why Sync is not working at all.
final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
if (!masterSyncAutomatically) {
showNeedsMasterSyncAutomaticallyEnabled();
return;
}
} finally {
// No matter our state, we should update the checkboxes.
updateSelectedEngines();
}
}
/**

View File

@ -97,7 +97,7 @@ public class AccountPickler {
o.put(KEY_PROFILE, account.getProfile());
o.put(KEY_IDP_SERVER_URI, account.getAccountServerURI());
o.put(KEY_TOKEN_SERVER_URI, account.getTokenServerURI());
o.put(KEY_IS_SYNCING_ENABLED, account.isSyncingEnabled());
o.put(KEY_IS_SYNCING_ENABLED, account.isSyncing());
// TODO: If prefs version changes under us, SyncPrefsPath will change, "clearing" prefs.

View File

@ -380,21 +380,25 @@ public class AndroidFxAccount {
getSyncPrefs().edit().clear().commit();
}
public boolean isSyncingEnabled() {
// TODO: Authority will be static in PR 426.
final int result = ContentResolver.getIsSyncable(account, BrowserContract.AUTHORITY);
if (result > 0) {
return true;
} else if (result == 0) {
return false;
} else {
// This should not happen.
throw new IllegalStateException("Sync enabled state unknown.");
/**
* Return true if the underlying Android account is currently set to sync automatically.
* <p>
* This is, confusingly, not the same thing as "being syncable": that refers
* to whether this account can be synced, ever; this refers to whether Android
* will try to sync the account at appropriate times.
*
* @return true if the account is set to sync automatically.
*/
public boolean isSyncing() {
boolean isSyncEnabled = true;
for (String authority : new String[] { BrowserContract.AUTHORITY }) {
isSyncEnabled &= ContentResolver.getSyncAutomatically(account, authority);
}
return isSyncEnabled;
}
public void enableSyncing() {
Logger.info(LOG_TAG, "Disabling sync for account named like " + Utils.obfuscateEmail(getEmail()));
Logger.info(LOG_TAG, "Enabling sync for account named like " + Utils.obfuscateEmail(getEmail()));
for (String authority : new String[] { BrowserContract.AUTHORITY }) {
ContentResolver.setSyncAutomatically(account, authority, true);
ContentResolver.setIsSyncable(account, authority, 1);

View File

@ -178,6 +178,8 @@
<!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
<!ENTITY fxaccount_status_needs_credentials 'Cannot connect. Tap to sign in.'>
<!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
<!ENTITY fxaccount_status_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings &gt; Data Usage.'>
<!ENTITY fxaccount_status_needs_account_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Tap to start syncing.'>
<!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
<!ENTITY fxaccount_status_history 'History'>
<!ENTITY fxaccount_status_passwords 'Passwords'>

View File

@ -35,6 +35,20 @@
android:layout="@layout/fxaccount_status_error_preference"
android:persistent="false"
android:title="@string/fxaccount_status_needs_verification" />
<Preference
android:editable="false"
android:icon="@drawable/fxaccount_sync_error"
android:key="needs_master_sync_automatically_enabled"
android:layout="@layout/fxaccount_status_error_preference"
android:persistent="false"
android:title="@string/fxaccount_status_needs_master_sync_automatically_enabled" />
<Preference
android:editable="false"
android:icon="@drawable/fxaccount_sync_error"
android:key="needs_account_enabled"
android:layout="@layout/fxaccount_status_error_preference"
android:persistent="false"
android:title="@string/fxaccount_status_needs_account_enabled" />
<CheckBoxPreference
android:key="bookmarks"

View File

@ -12,15 +12,18 @@ import java.util.Map.Entry;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.sync.CommandProcessor;
import org.mozilla.gecko.sync.CommandRunner;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConstants;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.setup.Constants;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
@ -28,6 +31,8 @@ import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
@ -38,10 +43,79 @@ import android.widget.TextView;
import android.widget.Toast;
public class SendTabActivity extends Activity {
private interface TabSender {
static final String[] CLIENTS_STAGE = new String[] { SyncClientsEngineStage.COLLECTION_NAME };
/**
* @return Return null if the account isn't correctly initialized. Return
* the account GUID otherwise.
*/
String getAccountGUID();
/**
* Sync this account, specifying only clients as the engine to sync.
*/
void syncClientsStage();
}
public class FxAccountTabSender implements TabSender {
private final AndroidFxAccount account;
public FxAccountTabSender(Context context, Account account) {
this.account = new AndroidFxAccount(context, account);
}
@Override
public String getAccountGUID() {
try {
final SharedPreferences prefs = this.account.getSyncPrefs();
return prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
} catch (Exception e) {
Logger.warn(LOG_TAG, "Could not get Firefox Account parameters or preferences; aborting.");
return null;
}
}
@Override
public void syncClientsStage() {
final Bundle extras = new Bundle();
Utils.putStageNamesToSync(extras, CLIENTS_STAGE, null);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
this.account.requestSync(extras);
}
}
private static class Sync11TabSender implements TabSender {
private final Account account;
private final AccountManager accountManager;
private final Context context;
private Sync11TabSender(Context context, Account syncAccount, AccountManager accountManager) {
this.context = context;
this.account = syncAccount;
this.accountManager = accountManager;
}
@Override
public String getAccountGUID() {
try {
final SharedPreferences prefs = SyncAccounts.blockingPrefsFromDefaultProfileV0(this.context, this.accountManager, this.account);
return prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
} catch (Exception e) {
Logger.warn(LOG_TAG, "Could not get Sync account parameters or preferences; aborting.");
return null;
}
}
@Override
public void syncClientsStage() {
SyncAdapter.requestImmediateSync(this.account, CLIENTS_STAGE);
}
}
public static final String LOG_TAG = "SendTabActivity";
private ClientRecordArrayAdapter arrayAdapter;
private AccountManager accountManager;
private Account localAccount;
private TabSender tabSender;
private SendTabData sendTabData;
@Override
@ -142,8 +216,35 @@ public class SendTabActivity extends Activity {
Logger.info(LOG_TAG, "Called SendTabActivity.onResume.");
super.onResume();
redirectIfNoSyncAccount();
registerDisplayURICommand();
/*
* First, decide if we are able to send anything.
*/
final Context applicationContext = getApplicationContext();
final AccountManager accountManager = AccountManager.get(applicationContext);
final Account[] fxAccounts = accountManager.getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
if (fxAccounts.length > 0) {
this.tabSender = new FxAccountTabSender(applicationContext, fxAccounts[0]);
Logger.info(LOG_TAG, "Allowing tab send for Firefox Account.");
registerDisplayURICommand();
return;
}
final Account[] syncAccounts = accountManager.getAccountsByType(SyncConstants.ACCOUNTTYPE_SYNC);
if (syncAccounts.length > 0) {
this.tabSender = new Sync11TabSender(applicationContext, syncAccounts[0], accountManager);
Logger.info(LOG_TAG, "Allowing tab send for Sync account.");
registerDisplayURICommand();
return;
}
// Offer to set up a Firefox Account, and finish this activity.
final Intent intent = new Intent(applicationContext, FxAccountGetStartedActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
}
private static void registerDisplayURICommand() {
@ -156,41 +257,6 @@ public class SendTabActivity extends Activity {
});
}
private void redirectIfNoSyncAccount() {
accountManager = AccountManager.get(getApplicationContext());
Account[] accts = accountManager.getAccountsByType(SyncConstants.ACCOUNTTYPE_SYNC);
// A Sync account exists.
if (accts.length > 0) {
localAccount = accts[0];
return;
}
Intent intent = new Intent(this, RedirectToSetupActivity.class);
intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
startActivity(intent);
finish();
}
/**
* @return Return null if there is no account set up. Return the account GUID otherwise.
*/
private String getAccountGUID() {
if (localAccount == null) {
Logger.warn(LOG_TAG, "Null local account; aborting.");
return null;
}
SharedPreferences prefs;
try {
prefs = SyncAccounts.blockingPrefsFromDefaultProfileV0(this, accountManager, localAccount);
return prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
} catch (Exception e) {
Logger.warn(LOG_TAG, "Could not get Sync account parameters or preferences; aborting.");
return null;
}
}
public void sendClickHandler(View view) {
Logger.info(LOG_TAG, "Send was clicked.");
final List<String> remoteClientGuids = arrayAdapter.getCheckedGUIDs();
@ -202,6 +268,14 @@ public class SendTabActivity extends Activity {
return;
}
final TabSender sender = this.tabSender;
if (sender == null) {
// This should never happen.
Logger.warn(LOG_TAG, "tabSender was null; aborting without sending tab.");
notifyAndFinish(false);
return;
}
// Fetching local client GUID hits the DB, and we want to update the UI
// afterward, so we perform the tab sending on another thread.
new AsyncTask<Void, Void, Boolean>() {
@ -210,7 +284,7 @@ public class SendTabActivity extends Activity {
protected Boolean doInBackground(Void... params) {
final CommandProcessor processor = CommandProcessor.getProcessor();
final String accountGUID = getAccountGUID();
final String accountGUID = sender.getAccountGUID();
Logger.debug(LOG_TAG, "Retrieved local account GUID '" + accountGUID + "'.");
if (accountGUID == null) {
return false;
@ -221,7 +295,7 @@ public class SendTabActivity extends Activity {
}
Logger.info(LOG_TAG, "Requesting immediate clients stage sync.");
SyncAdapter.requestImmediateSync(localAccount, new String[] { SyncClientsEngineStage.COLLECTION_NAME });
sender.syncClientsStage();
return true;
}
@ -286,7 +360,12 @@ public class SendTabActivity extends Activity {
return new ArrayList<ClientRecord>(0);
}
final String ourGUID = getAccountGUID();
if (this.tabSender == null) {
Logger.warn(LOG_TAG, "No tab sender when fetching other client IDs.");
return new ArrayList<ClientRecord>(0);
}
final String ourGUID = this.tabSender.getAccountGUID();
if (ourGUID == null) {
return all.values();
}

View File

@ -20,7 +20,7 @@ var SelectionHandler = {
// stored here are relative to the _contentWindow window.
_cache: null,
_activeType: 0, // TYPE_NONE
_ignoreSelectionChanges: false, // True while user drags text selection handles
_draggingHandles: false, // True while user drags text selection handles
_ignoreCompositionChanges: false, // Persist caret during IME composition updates
// The window that holds the selection (can be a sub-frame)
@ -119,9 +119,9 @@ var SelectionHandler = {
case "TextSelection:Move": {
let data = JSON.parse(aData);
if (this._activeType == this.TYPE_SELECTION) {
// Ignore selectionChange notifications when handle movement starts
this._ignoreSelectionChanges = true;
this._startDraggingHandles();
this._moveSelection(data.handleType == this.HANDLE_TYPE_START, data.x, data.y);
} else if (this._activeType == this.TYPE_CURSOR) {
// Ignore IMM composition notifications when caret movement starts
this._ignoreCompositionChanges = true;
@ -136,11 +136,10 @@ var SelectionHandler = {
}
case "TextSelection:Position": {
if (this._activeType == this.TYPE_SELECTION) {
// Ignore selectionChange notifications when handle movement starts
this._ignoreSelectionChanges = true;
this._startDraggingHandles();
// Check to see if the handles should be reversed.
let isStartHandle = JSON.parse(aData).handleType == this.HANDLE_TYPE_START;
try {
let selectionReversed = this._updateCacheForSelection(isStartHandle);
if (selectionReversed) {
@ -157,8 +156,7 @@ var SelectionHandler = {
break;
}
// Act on selectionChange notifications after handle movement ends
this._ignoreSelectionChanges = false;
this._stopDraggingHandles();
this._positionHandles();
} else if (this._activeType == this.TYPE_CURSOR) {
@ -183,6 +181,24 @@ var SelectionHandler = {
}
},
// Ignore selectionChange notifications during handle dragging, disable dynamic
// IME text compositions (autoSuggest, autoCorrect, etc)
_startDraggingHandles: function sh_startDraggingHandles() {
if (!this._draggingHandles) {
this._draggingHandles = true;
sendMessageToJava({ type: "TextSelection:IMECompositions", suppress: true });
}
},
// Act on selectionChange notifications when not dragging handles, allow dynamic
// IME text compositions (autoSuggest, autoCorrect, etc)
_stopDraggingHandles: function sh_stopDraggingHandles() {
if (this._draggingHandles) {
this._draggingHandles = false;
sendMessageToJava({ type: "TextSelection:IMECompositions", suppress: false });
}
},
handleEvent: function sh_handleEvent(aEvent) {
switch (aEvent.type) {
case "pagehide":
@ -225,7 +241,7 @@ var SelectionHandler = {
notifySelectionChanged: function sh_notifySelectionChanged(aDocument, aSelection, aReason) {
// Ignore selectionChange notifications during handle movements
if (this._ignoreSelectionChanges) {
if (this._draggingHandles) {
return;
}
@ -588,6 +604,7 @@ var SelectionHandler = {
aElement.focus();
}
this._stopDraggingHandles();
this._contentWindow = aElement.ownerDocument.defaultView;
this._isRTL = (this._contentWindow.getComputedStyle(aElement, "").direction == "rtl");
@ -840,6 +857,7 @@ var SelectionHandler = {
},
_deactivate: function sh_deactivate() {
this._stopDraggingHandles();
sendMessageToJava({ type: "TextSelection:HideHandles" });
this._removeObservers();
@ -856,7 +874,6 @@ var SelectionHandler = {
this._targetElement = null;
this._isRTL = false;
this._cache = null;
this._ignoreSelectionChanges = false;
this._ignoreCompositionChanges = false;
this._activeType = this.TYPE_NONE;

View File

@ -172,6 +172,8 @@
<string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
<string name="fxaccount_status_needs_credentials">&fxaccount_status_needs_credentials;</string>
<string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
<string name="fxaccount_status_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
<string name="fxaccount_status_needs_account_enabled">&fxaccount_status_needs_account_enabled;</string>
<string name="fxaccount_status_bookmarks">&fxaccount_status_bookmarks;</string>
<string name="fxaccount_status_history">&fxaccount_status_history;</string>
<string name="fxaccount_status_passwords">&fxaccount_status_passwords;</string>

View File

@ -4,12 +4,12 @@
package org.mozilla.gecko.background.fxa.authenticator;
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
import org.mozilla.gecko.background.sync.TestSyncAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.Separated;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.background.sync.TestSyncAccounts;
import org.mozilla.gecko.sync.Utils;
import android.accounts.Account;
@ -34,6 +34,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
this.accountManager = AccountManager.get(context);
}
@Override
public void tearDown() {
if (this.account != null) {
deleteAccount(this, this.accountManager, this.account);
@ -104,7 +105,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
assertEquals(expected.getAudience(), actual.getAudience());
assertEquals(expected.getTokenServerURI(), actual.getTokenServerURI());
assertEquals(expected.getSyncPrefsPath(), actual.getSyncPrefsPath());
assertEquals(expected.isSyncingEnabled(), actual.isSyncingEnabled());
assertEquals(expected.isSyncing(), actual.isSyncing());
assertEquals(expected.getEmail(), actual.getEmail());
assertStateEquals(expected.getState(), actual.getState());
}

View File

@ -565,6 +565,9 @@ pref("devtools.debugger.prompt-connection", true);
// Block tools from seeing / interacting with certified apps
pref("devtools.debugger.forbid-certified-apps", true);
// Disable add-on debugging
pref("devtools.debugger.addon-enabled", false);
// DevTools default color unit
pref("devtools.defaultColorUnit", "hex");

View File

@ -4635,6 +4635,8 @@ update(ChromeDebuggerActor.prototype, {
function AddonThreadActor(aConnect, aHooks, aAddonID) {
this.addonID = aAddonID;
this.addonManager = Cc["@mozilla.org/addons/integration;1"].
getService(Ci.amIAddonManager);
ThreadActor.call(this, aHooks);
}
@ -4651,7 +4653,17 @@ update(AddonThreadActor.prototype, {
* sure every script and source with a URL is stored when debugging
* add-ons.
*/
_allowSource: (aSourceURL) => !!aSourceURL,
_allowSource: function(aSourceURL) {
// Hide eval scripts
if (!aSourceURL)
return false;
// XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it
if (aSourceURL == "resource://gre/modules/addons/XPIProvider.jsm")
return false;
return true;
},
/**
* An object that will be used by ThreadActors to tailor their
@ -4695,14 +4707,30 @@ update(AddonThreadActor.prototype, {
* @param aGlobal Debugger.Object
*/
_checkGlobal: function ADA_checkGlobal(aGlobal) {
let metadata;
try {
// This will fail for non-Sandbox objects, hence the try-catch block.
metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference());
let metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference());
if (metadata)
return metadata.addonID === this.addonID;
} catch (e) {
}
return metadata && metadata.addonID === this.addonID;
// Check the global for a __URI__ property and then try to map that to an
// add-on
let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
if (uridescriptor && "value" in uridescriptor) {
try {
let uri = Services.io.newURI(uridescriptor.value, null, null);
let id = {};
if (this.addonManager.mapURIToAddonID(uri, id))
return id.value === this.addonID;
}
catch (e) {
console.log("Unexpected URI " + uridescriptor.value);
}
}
return false;
}
});

View File

@ -101,6 +101,7 @@
<!ENTITY cmd.installAddon.accesskey "I">
<!ENTITY cmd.uninstallAddon.label "Remove">
<!ENTITY cmd.uninstallAddon.accesskey "R">
<!ENTITY cmd.debugAddon.label "Debug">
<!ENTITY cmd.showPreferencesWin.label "Options">
<!ENTITY cmd.showPreferencesWin.tooltip "Change this add-on's options">
<!ENTITY cmd.showPreferencesUnix.label "Preferences">

View File

@ -16,7 +16,10 @@ Cu.import("resource://gre/modules/PluralForm.jsm");
Cu.import("resource://gre/modules/DownloadUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
return Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {}).
BrowserToolboxProcess;
});
const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
@ -26,6 +29,8 @@ const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled";
const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden";
const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
const PREF_ADDON_DEBUGGING_ENABLED = "devtools.debugger.addon-enabled";
const PREF_REMOTE_DEBUGGING_ENABLED = "devtools.debugger.remote-enabled";
const LOADING_MSG_DELAY = 100;
@ -143,6 +148,9 @@ function initialize(event) {
}
gViewController.loadInitialView(view);
Services.prefs.addObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged, false);
Services.prefs.addObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged, false);
}
function notifyInitialized() {
@ -163,6 +171,8 @@ function shutdown() {
gEventManager.shutdown();
gViewController.shutdown();
Services.obs.removeObserver(sendEMPong, "EM-ping");
Services.prefs.removeObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged);
Services.prefs.removeObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged);
}
function sendEMPong(aSubject, aTopic, aData) {
@ -372,7 +382,7 @@ var gEventManager = {
contextMenu.addEventListener("popupshowing", function contextMenu_onPopupshowing() {
var addon = gViewController.currentViewObj.getSelectedAddon();
contextMenu.setAttribute("addontype", addon.type);
var menuSep = document.getElementById("addonitem-menuseparator");
var countEnabledMenuCmds = 0;
for (let child of contextMenu.children) {
@ -381,10 +391,10 @@ var gEventManager = {
countEnabledMenuCmds++;
}
}
// with only one menu item, we hide the menu separator
menuSep.hidden = (countEnabledMenuCmds <= 1);
}, false);
},
@ -460,14 +470,14 @@ var gEventManager = {
}
}
},
refreshGlobalWarning: function gEM_refreshGlobalWarning() {
var page = document.getElementById("addons-page");
if (Services.appinfo.inSafeMode) {
page.setAttribute("warning", "safemode");
return;
}
}
if (AddonManager.checkUpdateSecurityDefault &&
!AddonManager.checkUpdateSecurity) {
@ -945,6 +955,20 @@ var gViewController = {
}
},
cmd_debugItem: {
doCommand: function cmd_debugItem_doCommand(aAddon) {
BrowserToolboxProcess.init({ addonID: aAddon.id });
},
isEnabled: function cmd_debugItem_isEnabled(aAddon) {
let debuggerEnabled = Services.prefs.
getBoolPref(PREF_ADDON_DEBUGGING_ENABLED);
let remoteEnabled = Services.prefs.
getBoolPref(PREF_REMOTE_DEBUGGING_ENABLED);
return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled;
}
},
cmd_showItemPreferences: {
isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) {
if (!aAddon || !aAddon.isActive || !aAddon.optionsURL)
@ -1282,7 +1306,7 @@ function openOptionsInTab(optionsURL) {
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
.getInterface(Ci.nsIDOMWindow);
if ("switchToTabHavingURI" in mainWindow) {
mainWindow.switchToTabHavingURI(optionsURL, true);
return true;
@ -1979,7 +2003,7 @@ var gDiscoverView = {
this._loadURL(this.homepageURL.spec, aIsRefresh,
gViewController.notifyViewChanged.bind(gViewController));
},
canRefresh: function gDiscoverView_canRefresh() {
if (this._browser.currentURI &&
this._browser.currentURI.spec == this._browser.homePage)
@ -2279,7 +2303,7 @@ var gSearchView = {
}
});
},
showLoading: function gSearchView_showLoading(aLoading) {
this._loading.hidden = !aLoading;
this._listBox.hidden = aLoading;
@ -2620,7 +2644,7 @@ var gDetailView = {
self._addon.applyBackgroundUpdates = self._autoUpdate.value;
}, true);
},
shutdown: function gDetailView_shutdown() {
AddonManager.removeManagerListener(this);
},
@ -2791,7 +2815,7 @@ var gDetailView = {
document.getElementById("detail-prefs-btn").hidden = !aIsRemote &&
!gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon);
var gridRows = document.querySelectorAll("#detail-grid rows row");
let first = true;
for (let gridRow of gridRows) {
@ -3108,7 +3132,7 @@ var gDetailView = {
if (firstRow) {
let top = firstRow.boxObject.y;
top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top"));
let detailViewBoxObject = gDetailView.node.boxObject;
top -= detailViewBoxObject.y;
@ -3348,7 +3372,7 @@ var gUpdatesView = {
notifyInitialized();
});
},
maybeDisableUpdateSelected: function gUpdatesView_maybeDisableUpdateSelected() {
for (let item of this._listBox.childNodes) {
if (item.includeUpdate) {
@ -3411,6 +3435,11 @@ var gUpdatesView = {
}
};
function debuggingPrefChanged() {
gViewController.updateState();
gViewController.updateCommands();
gViewController.notifyViewChanged();
}
var gDragDrop = {
onDragOver: function gDragDrop_onDragOver(aEvent) {

View File

@ -196,7 +196,7 @@
this._status.value = val;
]]></setter>
</property>
<method name="cancel">
<body><![CDATA[
this.mInstall.cancel();
@ -867,6 +867,11 @@
tooltiptext="&cmd.showPreferencesUnix.tooltip;"
#endif
oncommand="document.getBindingParent(this).showPreferences();"/>
<!-- label="&cmd.debugAddon.label;" -->
<xul:button anonid="debug-btn" class="addon-control debug"
label="&cmd.debugAddon.label;"
oncommand="document.getBindingParent(this).debug();"/>
<xul:button anonid="enable-btn" class="addon-control enable"
label="&cmd.enableAddon.label;"
oncommand="document.getBindingParent(this).userDisabled = false;"/>
@ -1003,6 +1008,10 @@
document.getAnonymousElementByAttribute(this, "anonid",
"enable-btn");
</field>
<field name="_debugBtn">
document.getAnonymousElementByAttribute(this, "anonid",
"debug-btn");
</field>
<field name="_disableBtn">
document.getAnonymousElementByAttribute(this, "anonid",
"disable-btn");
@ -1332,6 +1341,12 @@
var showProgress = this.mAddon.purchaseURL || (this.mAddon.install &&
this.mAddon.install.state != AddonManager.STATE_INSTALLED);
this._showStatus(showProgress ? "progress" : "none");
let debuggable = this.mAddon.isDebuggable &&
Services.prefs.getBoolPref('devtools.debugger.addon-enabled') &&
Services.prefs.getBoolPref('devtools.debugger.remote-enabled');
this._debugBtn.disabled = this._debugBtn.hidden = !debuggable
]]></body>
</method>
@ -1353,11 +1368,11 @@
}
var relNotesData = null, transformData = null;
this._relNotesLoaded = true;
this._relNotesLoading.hidden = false;
this._relNotesError.hidden = true;
function sendToggleEvent() {
var event = document.createEvent("Events");
event.initEvent("RelNotesToggle", true, true);
@ -1492,6 +1507,12 @@
]]></body>
</method>
<method name="debug">
<body><![CDATA[
gViewController.doCommand("cmd_debugItem", this.mAddon);
]]></body>
</method>
<method name="showPreferences">
<body><![CDATA[
gViewController.doCommand("cmd_showItemPreferences", this.mAddon);
@ -1880,7 +1901,7 @@
this._icon.src = this.mAddon.iconURL ||
(this.mInstall ? this.mInstall.iconURL : "");
this._name.value = this.mAddon.name;
if (this.mAddon.version) {
this._version.value = this.mAddon.version;
this._version.hidden = false;
@ -1931,7 +1952,7 @@
}
]]></body>
</method>
<method name="retryInstall">
<body><![CDATA[
this.mInstall.install();

View File

@ -52,6 +52,8 @@
<menuitem id="menuitem_uninstallItem" command="cmd_uninstallItem"
label="&cmd.uninstallAddon.label;"
accesskey="&cmd.uninstallAddon.accesskey;"/>
<menuitem id="menuitem_debugItem" command="cmd_debugItem"
label="&cmd.debugAddon.label;"/>
<menuseparator id="addonitem-menuseparator" />
<menuitem id="menuitem_preferences" command="cmd_showItemPreferences"
#ifdef XP_WIN
@ -96,6 +98,7 @@
<command id="cmd_findItemUpdates"/>
<command id="cmd_showItemPreferences"/>
<command id="cmd_showItemAbout"/>
<command id="cmd_debugItem"/>
<command id="cmd_enableItem"/>
<command id="cmd_disableItem"/>
<command id="cmd_installItem"/>
@ -597,6 +600,9 @@
#endif
command="cmd_showItemPreferences"/>
<spacer flex="1"/>
<button id="detail-debug-btn" class="addon-control debug"
label="Debug"
command="cmd_debugItem" />
<button id="detail-enable-btn" class="addon-control enable"
label="&cmd.enableAddon.label;"
accesskey="&cmd.enableAddon.accesskey;"

View File

@ -4030,16 +4030,21 @@ var XPIProvider = {
if (!aFile.exists()) {
this.bootstrapScopes[aId] =
new Cu.Sandbox(principal, {sandboxName: aFile.path,
wantGlobalProperties: ["indexedDB"]});
new Cu.Sandbox(principal, { sandboxName: aFile.path,
wantGlobalProperties: ["indexedDB"],
metadata: { addonID: aId } });
logger.error("Attempted to load bootstrap scope from missing directory " + aFile.path);
return;
}
let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
if (aType == "dictionary")
uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js"
this.bootstrapScopes[aId] =
new Cu.Sandbox(principal, {sandboxName: uri,
wantGlobalProperties: ["indexedDB"]});
new Cu.Sandbox(principal, { sandboxName: uri,
wantGlobalProperties: ["indexedDB"],
metadata: { addonID: aId, URI: uri } });
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
createInstance(Ci.mozIJSSubScriptLoader);
@ -4061,12 +4066,7 @@ var XPIProvider = {
// As we don't want our caller to control the JS version used for the
// bootstrap file, we run loadSubScript within the context of the
// sandbox with the latest JS version set explicitly.
if (aType == "dictionary") {
this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ =
"resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js"
} else {
this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ = uri;
}
this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ = uri;
Components.utils.evalInSandbox(
"Components.classes['@mozilla.org/moz/jssubscript-loader;1'] \
.createInstance(Components.interfaces.mozIJSSubScriptLoader) \
@ -6479,6 +6479,10 @@ function AddonWrapper(aAddon) {
return ops;
});
this.__defineGetter__("isDebuggable", function AddonWrapper_isDebuggable() {
return this.isActive && aAddon.bootstrap;
});
this.__defineGetter__("permissions", function AddonWrapper_permisionsGetter() {
let permissions = 0;

View File

@ -1072,7 +1072,7 @@ this.XPIDatabase = {
})
.then(null,
error => {
logger.error("getAddonList failed", e);
logger.error("getAddonList failed", error);
makeSafe(aCallback)([]);
});
},

View File

@ -0,0 +1,17 @@
Components.utils.import("resource://gre/modules/Services.jsm");
function install(data, reason) {
Services.prefs.setIntPref("jetpacktest.installed_version", 1);
}
function startup(data, reason) {
Services.prefs.setIntPref("jetpacktest.active_version", 1);
}
function shutdown(data, reason) {
Services.prefs.setIntPref("jetpacktest.active_version", 0);
}
function uninstall(data, reason) {
Services.prefs.setIntPref("jetpacktest.installed_version", 0);
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>jetpack@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:bootstrap>true</em:bootstrap>
<!-- Front End MetaData -->
<em:name>Test jetpack</em:name>
<em:description>Test Description</em:description>
<em:iconURL>chrome://foo/skin/icon.png</em:iconURL>
<em:aboutURL>chrome://foo/content/about.xul</em:aboutURL>
<em:optionsURL>chrome://foo/content/options.xul</em:optionsURL>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>1</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -29,6 +29,7 @@ support-files =
[browser_bug679604.js]
[browser_bug714593.js]
[browser_bug590347.js]
[browser_debug_button.js]
[browser_details.js]
[browser_discovery.js]
[browser_dragdrop.js]

View File

@ -0,0 +1,112 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/**
* Tests debug button for addons in list view
*/
let { Promise } = Components.utils.import("resource://gre/modules/Promise.jsm", {});
let { Task } = Components.utils.import("resource://gre/modules/Task.jsm", {});
const getDebugButton = node =>
node.ownerDocument.getAnonymousElementByAttribute(node, "anonid", "debug-btn");
const addonDebuggingEnabled = bool =>
Services.prefs.setBoolPref("devtools.debugger.addon-enabled", !!bool);
const remoteDebuggingEnabled = bool =>
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", !!bool);
function test() {
requestLongerTimeout(2);
waitForExplicitFinish();
var gProvider = new MockProvider();
gProvider.createAddons([{
id: "non-debuggable@tests.mozilla.org",
name: "No debug",
description: "foo"
},
{
id: "debuggable@tests.mozilla.org",
name: "Debuggable",
description: "bar",
isDebuggable: true
}]);
Task.spawn(function* () {
addonDebuggingEnabled(false);
remoteDebuggingEnabled(false);
yield testDOM((nondebug, debuggable) => {
is(nondebug.disabled, true,
"addon:disabled::remote:disabled button is disabled for legacy addons");
is(nondebug.hidden, true,
"addon:disabled::remote:disabled button is hidden for legacy addons");
is(debuggable.disabled, true,
"addon:disabled::remote:disabled button is disabled for debuggable addons");
is(debuggable.hidden, true,
"addon:disabled::remote:disabled button is hidden for debuggable addons");
});
addonDebuggingEnabled(true);
remoteDebuggingEnabled(false);
yield testDOM((nondebug, debuggable) => {
is(nondebug.disabled, true,
"addon:enabled::remote:disabled button is disabled for legacy addons");
is(nondebug.disabled, true,
"addon:enabled::remote:disabled button is hidden for legacy addons");
is(debuggable.disabled, true,
"addon:enabled::remote:disabled button is disabled for debuggable addons");
is(debuggable.disabled, true,
"addon:enabled::remote:disabled button is hidden for debuggable addons");
});
addonDebuggingEnabled(false);
remoteDebuggingEnabled(true);
yield testDOM((nondebug, debuggable) => {
is(nondebug.disabled, true,
"addon:disabled::remote:enabled button is disabled for legacy addons");
is(nondebug.disabled, true,
"addon:disabled::remote:enabled button is hidden for legacy addons");
is(debuggable.disabled, true,
"addon:disabled::remote:enabled button is disabled for debuggable addons");
is(debuggable.disabled, true,
"addon:disabled::remote:enabled button is hidden for debuggable addons");
});
addonDebuggingEnabled(true);
remoteDebuggingEnabled(true);
yield testDOM((nondebug, debuggable) => {
is(nondebug.disabled, true,
"addon:enabled::remote:enabled button is disabled for legacy addons");
is(nondebug.disabled, true,
"addon:enabled::remote:enabled button is hidden for legacy addons");
is(debuggable.disabled, false,
"addon:enabled::remote:enabled button is enabled for debuggable addons");
is(debuggable.hidden, false,
"addon:enabled::remote:enabled button is visible for debuggable addons");
});
finish();
});
function testDOM (testCallback) {
let deferred = Promise.defer();
open_manager("addons://list/extension", function(aManager) {
const {document} = aManager;
const addonList = document.getElementById("addon-list");
const nondebug = addonList.querySelector("[name='No debug']");
const debuggable = addonList.querySelector("[name='Debuggable']");
testCallback.apply(null, [nondebug, debuggable].map(getDebugButton));
close_manager(aManager, deferred.resolve);
});
return deferred.promise;
}
}

View File

@ -68,6 +68,8 @@ var gRestorePrefs = [{name: PREF_LOGGING_ENABLED},
{name: "extensions.getAddons.search.browseURL"},
{name: "extensions.getAddons.search.url"},
{name: "extensions.getAddons.cache.enabled"},
{name: "devtools.debugger.addon-enabled"},
{name: "devtools.debugger.remote-enabled"},
{name: PREF_SEARCH_MAXRESULTS},
{name: PREF_STRICT_COMPAT},
{name: PREF_CHECK_COMPATIBILITY}];
@ -950,6 +952,7 @@ function MockAddon(aId, aName, aType, aOperationsRequiringRestart) {
this.type = aType || "extension";
this.version = "";
this.isCompatible = true;
this.isDebuggable = false;
this.providesUpdatesSecurely = true;
this.blocklistState = 0;
this._appDisabled = false;

View File

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
var ADDONS = [
"test_bootstrap2_1", // restartless addon
"test_bootstrap1_4", // old-school addon
"test_jetpack" // sdk addon
];
var IDS = [
"bootstrap1@tests.mozilla.org",
"bootstrap2@tests.mozilla.org",
"jetpack@tests.mozilla.org"
];
function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
startupManager();
AddonManager.checkCompatibility = false;
installAllFiles(ADDONS.map(do_get_addon), function () {
restartManager();
AddonManager.getAddonsByIDs(IDS, function([a1, a2, a3]) {
do_check_eq(a1.isDebuggable, false);
do_check_eq(a2.isDebuggable, true);
do_check_eq(a3.isDebuggable, true);
do_test_finished();
});
}, true);
}

View File

@ -194,6 +194,7 @@ skip-if = os == "android"
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
run-sequentially = Uses hardcoded ports in xpi files.
[test_isDebuggable.js]
[test_locale.js]
[test_locked.js]
[test_locked2.js]