mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to mozilla-central
This commit is contained in:
commit
a3d3c8dcb5
@ -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);
|
||||
},
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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]
|
||||
|
@ -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.");
|
||||
});
|
||||
|
@ -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.");
|
||||
});
|
@ -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
|
||||
|
@ -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,
|
||||
|
23
browser/devtools/debugger/test/addon-source/browser_dbg_addon4/bootstrap.js
vendored
Normal file
23
browser/devtools/debugger/test/addon-source/browser_dbg_addon4/bootstrap.js
vendored
Normal 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);
|
||||
}
|
@ -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>
|
@ -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 = {};
|
@ -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 = {};
|
BIN
browser/devtools/debugger/test/addon4.xpi
Normal file
BIN
browser/devtools/debugger/test/addon4.xpi
Normal file
Binary file not shown.
@ -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]
|
||||
|
114
browser/devtools/debugger/test/browser_dbg_addon-modules.js
Normal file
114
browser/devtools/debugger/test/browser_dbg_addon-modules.js
Normal 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();
|
||||
});
|
115
browser/devtools/debugger/test/browser_dbg_addon-sources.js
Normal file
115
browser/devtools/debugger/test/browser_dbg_addon-sources.js
Normal 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();
|
||||
});
|
@ -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.");
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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, " "));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 > 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'>
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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;"
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1072,7 +1072,7 @@ this.XPIDatabase = {
|
||||
})
|
||||
.then(null,
|
||||
error => {
|
||||
logger.error("getAddonList failed", e);
|
||||
logger.error("getAddonList failed", error);
|
||||
makeSafe(aCallback)([]);
|
||||
});
|
||||
},
|
||||
|
17
toolkit/mozapps/extensions/test/addons/test_jetpack/bootstrap.js
vendored
Normal file
17
toolkit/mozapps/extensions/test/addons/test_jetpack/bootstrap.js
vendored
Normal 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);
|
||||
}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -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>
|
@ -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]
|
||||
|
112
toolkit/mozapps/extensions/test/browser/browser_debug_button.js
Normal file
112
toolkit/mozapps/extensions/test/browser/browser_debug_button.js
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user