Merge branch 'mozilla-central'

This commit is contained in:
Jacek Caban 2016-02-25 06:29:24 +01:00
commit 6b587ffb31
829 changed files with 32737 additions and 22726 deletions

View File

@ -27,8 +27,8 @@ TreeWalker::
{ {
NS_ASSERTION(aContent, "No node for the accessible tree walker!"); NS_ASSERTION(aContent, "No node for the accessible tree walker!");
mChildFilter = mContext->CanHaveAnonChildren() ? mChildFilter = mContext->NoXBLKids() ?
nsIContent::eAllChildren : nsIContent::eAllButXBL; nsIContent::eAllButXBL : nsIContent::eAllChildren;
mChildFilter |= nsIContent::eSkipPlaceholderContent; mChildFilter |= nsIContent::eSkipPlaceholderContent;
if (aContent) if (aContent)

View File

@ -302,12 +302,6 @@ Accessible::KeyboardShortcut() const
return KeyBinding(); return KeyBinding();
} }
bool
Accessible::CanHaveAnonChildren()
{
return true;
}
void void
Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut) Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut)
{ {

View File

@ -494,11 +494,6 @@ public:
*/ */
virtual nsresult HandleAccEvent(AccEvent* aAccEvent); virtual nsresult HandleAccEvent(AccEvent* aAccEvent);
/**
* Return true if this accessible allows accessible children from anonymous subtree.
*/
virtual bool CanHaveAnonChildren();
/** /**
* Return true if the accessible is an acceptable child. * Return true if the accessible is an acceptable child.
*/ */
@ -930,6 +925,12 @@ public:
mStateFlags &= ~eRelocated; mStateFlags &= ~eRelocated;
} }
/**
* Return true if the accessible doesn't allow accessible children from XBL
* anonymous subtree.
*/
bool NoXBLKids() { return mStateFlags & eNoXBLKids; }
/** /**
* Return true if this accessible has a parent whose name depends on this * Return true if this accessible has a parent whose name depends on this
* accessible. * accessible.
@ -1021,8 +1022,9 @@ protected:
eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events
eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them
eRelocated = 1 << 9, // accessible was moved in tree eRelocated = 1 << 9, // accessible was moved in tree
eNoXBLKids = 1 << 10, // accessible don't allows XBL children
eLastStateFlag = eRelocated eLastStateFlag = eNoXBLKids
}; };
/** /**
@ -1137,7 +1139,7 @@ protected:
int32_t mIndexInParent; int32_t mIndexInParent;
static const uint8_t kChildrenFlagsBits = 2; static const uint8_t kChildrenFlagsBits = 2;
static const uint8_t kStateFlagsBits = 10; static const uint8_t kStateFlagsBits = 11;
static const uint8_t kContextFlagsBits = 2; static const uint8_t kContextFlagsBits = 2;
static const uint8_t kTypeBits = 6; static const uint8_t kTypeBits = 6;
static const uint8_t kGenericTypesBits = 14; static const uint8_t kGenericTypesBits = 14;

View File

@ -18,22 +18,26 @@
src="../layout.js"></script> src="../layout.js"></script>
<script type="application/javascript"> <script type="application/javascript">
var kX = 10, kY = 10, kWidth = 150, kHeight = 100;
function doTest() function doTest()
{ {
var canv = document.getElementById("c"); var canv = document.getElementById("c");
var context = canv.getContext('2d'); var context = canv.getContext('2d');
var element = document.getElementById("showA"); var element = document.getElementById("showA");
context.beginPath(); context.beginPath();
context.rect(10, 10, 150, 100); context.rect(kX, kY, kWidth, kHeight);
context.addHitRegion({control: element}); context.addHitRegion({control: element});
var input = getAccessible("showA");
var input = getAccessible("showA"); var input = getAccessible("showA");
var [cnvX, cnvY, cnvWidth, cnvHeight] = getBoundsForDOMElm(canv); var [cnvX, cnvY, cnvWidth, cnvHeight] = getBoundsForDOMElm(canv);
var [accX, accY, accWidth, accHeight] = getBounds(input); var [accX, accY, accWidth, accHeight] = getBounds(input);
is(accX, cnvX + 10, "accX should be 10 and not " + accX);
is(accY, cnvY + 10, "accY should be 10 and not " + accY); var [x, y, w, h] = CSSToDevicePixels(window, kX, kY, kWidth, kHeight);
is(accWidth, 150, "accWidth should be 150 and not " + accWidth); is(accX, cnvX + x, "wrong accX");
is(accHeight, 100, "accHeight should be 100 and not " + accHeight); is(accY, cnvY + y, "wrong accY");
is(accWidth, w, "wrong accWidth");
is(accHeight, h, "wrong accHeight");
SimpleTest.finish(); SimpleTest.finish();
} }

View File

@ -345,10 +345,17 @@ function eventQueue(aEventType)
var msg = "Test with ID = '" + this.getEventID(checker) + var msg = "Test with ID = '" + this.getEventID(checker) +
"' succeed. "; "' succeed. ";
if (checker.unexpected) if (checker.unexpected) {
ok(true, msg + "There's no unexpected " + typeStr + " event."); if (checker.todo) {
else todo(false, "Event " + typeStr + " event is still missing");
}
else {
ok(true, msg + "There's no unexpected " + typeStr + " event.");
}
}
else {
ok(true, msg + "Event " + typeStr + " was handled."); ok(true, msg + "Event " + typeStr + " was handled.");
}
} }
} }
} }
@ -371,8 +378,13 @@ function eventQueue(aEventType)
ok(false, msg + "Dupe " + typeStr + " event."); ok(false, msg + "Dupe " + typeStr + " event.");
if (checker.unexpected) { if (checker.unexpected) {
if (checker.wasCaught) if (checker.todo) {
todo(checker.wasCaught,
"Event " + typeStr + " event is still missing");
}
else if (checker.wasCaught) {
ok(false, msg + "There's unexpected " + typeStr + " event."); ok(false, msg + "There's unexpected " + typeStr + " event.");
}
} else if (!checker.wasCaught) { } else if (!checker.wasCaught) {
ok(false, msg + typeStr + " event was missed."); ok(false, msg + typeStr + " event was missed.");
} }
@ -1667,6 +1679,18 @@ function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync)
this.mTargetFuncArg = aTargetFuncArg; this.mTargetFuncArg = aTargetFuncArg;
} }
/**
* Generic invoker checker for todo events.
*/
function todo_invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
{
this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
aTargetFuncArg, true);
this.unexpected = true;
this.todo = true;
}
/** /**
* Generic invoker checker for unexpected events. * Generic invoker checker for unexpected events.
*/ */

View File

@ -358,6 +358,25 @@
} }
} }
function showHiddenParentOfVisibleChild()
{
this.eventSeq = [
new todo_invokerChecker(EVENT_HIDE, getNode("c4_child")),
new invokerChecker(EVENT_SHOW, getNode("c4_middle")),
new invokerChecker(EVENT_REORDER, getNode("c4"))
];
this.invoke = function showHiddenParentOfVisibleChild_invoke()
{
getNode("c4_middle").style.visibility = 'visible';
}
this.getID = function showHiddenParentOfVisibleChild_getID()
{
return "show hidden parent of visible child";
}
}
/** /**
* Target getters. * Target getters.
*/ */
@ -484,6 +503,7 @@
gQueue.push(new test2("testContainer", "testNestedContainer")); gQueue.push(new test2("testContainer", "testNestedContainer"));
gQueue.push(new test3("testContainer")); gQueue.push(new test3("testContainer"));
gQueue.push(new insertReferredElm("testContainer3")); gQueue.push(new insertReferredElm("testContainer3"));
gQueue.push(new showHiddenParentOfVisibleChild());
gQueue.invoke(); // Will call SimpleTest.finish(); gQueue.invoke(); // Will call SimpleTest.finish();
} }
@ -544,5 +564,11 @@
</div> </div>
<div id="testContainer2"></div> <div id="testContainer2"></div>
<div id="testContainer3"></div> <div id="testContainer3"></div>
<div id="c4">
<div style="visibility:hidden" id="c4_middle">
<div style="visibility:visible" id="c4_child"></div>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -41,29 +41,31 @@
function doTest() function doTest()
{ {
var offsetX = 20, offsetY = 40;
getNode("hitcanvas").scrollIntoView(true); getNode("hitcanvas").scrollIntoView(true);
var context = document.getElementById("hitcanvas").getContext('2d'); var context = document.getElementById("hitcanvas").getContext('2d');
redrawCheckbox(context, document.getElementById('hitcheck'), 20, 40); redrawCheckbox(context, document.getElementById('hitcheck'),
offsetX, offsetY);
var hitcanvas = getAccessible("hitcanvas"); var hitcanvas = getAccessible("hitcanvas");
var hitcheck = getAccessible("hitcheck"); var hitcheck = getAccessible("hitcheck");
var [hitX, hitY, hitWidth, hitHeight] = getBounds(hitcanvas); var [hitX, hitY, hitWidth, hitHeight] = getBounds(hitcanvas);
var [deltaX, deltaY] = CSSToDevicePixels(window, offsetX, offsetY);
var docAcc = getAccessible(document); var docAcc = getAccessible(document);
var tgtX = hitX+25;
var tgtY = hitY+45;
hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
// test if we hit the region associated with the shadow dom checkbox
is(hitAcc, hitcheck, "Hit match at " + tgtX + "," + tgtY +
". Found: " + prettyName(hitAcc));
tgtY = hitY+75; // test if we hit the region associated with the shadow dom checkbox
var tgtX = hitX + deltaX;
var tgtY = hitY + deltaY;
hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY); hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
isObject(hitAcc, hitcheck, `Hit match at (${tgtX}, ${tgtY}`);
// test that we don't hit the region associated with the shadow dom checkbox // test that we don't hit the region associated with the shadow dom checkbox
is(hitAcc, hitcanvas, "Hit match at " + tgtX + "," + tgtY + tgtY = hitY + deltaY * 2;
". Found: " + prettyName(hitAcc)); hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
isObject(hitAcc, hitcanvas, `Hit match at (${tgtX}, ${tgtY}`);
SimpleTest.finish(); SimpleTest.finish();
} }

View File

@ -2,7 +2,6 @@
[test_bindings.xhtml] [test_bindings.xhtml]
[test_embeds.xul] [test_embeds.xul]
skip-if = (os == "linux" && (debug || asan)) # Bug 845176
[test_general.html] [test_general.html]
[test_general.xul] [test_general.xul]
[test_tabbrowser.xul] [test_tabbrowser.xul]

View File

@ -37,7 +37,7 @@
} }
this.eventSeq = [ this.eventSeq = [
new invokerChecker(EVENT_REORDER, currentBrowser) new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument)
]; ];
this.finalCheck = function loadURI_finalCheck() this.finalCheck = function loadURI_finalCheck()
@ -52,27 +52,6 @@
} }
} }
function browserReorderChecker()
{
this.type = EVENT_REORDER;
this.match = function browserReorderChecker_match(aEvent)
{
if (!isAccessible(currentBrowser()))
return false;
// Reorder event might be duped because of temporary document creation.
if (aEvent.accessible == getAccessible(currentBrowser())) {
this.cnt++;
return this.cnt != 2;
}
return false;
}
this.cnt = 0;
}
function loadOneTab(aURI) function loadOneTab(aURI)
{ {
this.invoke = function loadOneTab_invoke() this.invoke = function loadOneTab_invoke()
@ -81,7 +60,7 @@
} }
this.eventSeq = [ this.eventSeq = [
new browserReorderChecker() new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument)
]; ];
this.finalCheck = function loadURI_finalCheck() this.finalCheck = function loadURI_finalCheck()
@ -99,7 +78,7 @@
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Testing // Testing
gA11yEventDumpToConsole = true; // debug //gA11yEventDumpToConsole = true; // debug
var gQueue = null; var gQueue = null;
function doTests() function doTests()

View File

@ -32,7 +32,8 @@
anchor = getAccessible("bottom2"); anchor = getAccessible("bottom2");
var [x, y] = getPos(anchor); var [x, y] = getPos(anchor);
var wnd = getRootAccessible().DOMDocument.defaultView; var wnd = getRootAccessible().DOMDocument.defaultView;
var scrollToX = docX - wnd.screenX, scrollToY = docY - wnd.screenY; var [screenX, screenY] = CSSToDevicePixels(wnd, wnd.screenX, wnd.screenY);
var scrollToX = docX - screenX, scrollToY = docY - screenY;
anchor.scrollToPoint(COORDTYPE_WINDOW_RELATIVE, scrollToX, scrollToY); anchor.scrollToPoint(COORDTYPE_WINDOW_RELATIVE, scrollToX, scrollToY);
testPos(anchor, [x, docY]); testPos(anchor, [x, docY]);

View File

@ -16,6 +16,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429659
src="role.js"></script> src="role.js"></script>
<script type="application/javascript" <script type="application/javascript"
src="attributes.js"></script> src="attributes.js"></script>
<script type="application/javascript"
src="layout.js"></script>
<script type="application/javascript"> <script type="application/javascript">
function testCoordinates(aID, aAcc, aWidth, aHeight) function testCoordinates(aID, aAcc, aWidth, aHeight)
@ -70,10 +72,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429659
// "Wrong screen y coordinate for " + aID + "!"); // "Wrong screen y coordinate for " + aID + "!");
} }
var [expected_w, expected_h] = CSSToDevicePixels(window, aWidth, aHeight);
var width = {}, height = {}; var width = {}, height = {};
aAcc.getImageSize(width, height); aAcc.getImageSize(width, height);
is(width.value, aWidth, "Wrong width for " + aID + "!"); is(width.value, expected_w, "Wrong width for " + aID + "!");
is(height.value, aHeight, "wrong height for " + aID + "!"); is(height.value, expected_h, "wrong height for " + aID + "!");
} }
function testThis(aID, aSRC, aWidth, aHeight, function testThis(aID, aSRC, aWidth, aHeight,

View File

@ -31,6 +31,15 @@ XULComboboxAccessible::
mGenericTypes |= eAutoComplete; mGenericTypes |= eAutoComplete;
else else
mGenericTypes |= eCombobox; mGenericTypes |= eCombobox;
// Both the XUL <textbox type="autocomplete"> and <menulist editable="true">
// widgets use XULComboboxAccessible. We need to walk the anonymous children
// for these so that the entry field is a child. Otherwise no XBL children.
if (!mContent->NodeInfo()->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL) &&
!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
nsGkAtoms::_true, eIgnoreCase)) {
mStateFlags |= eNoXBLKids;
}
} }
role role
@ -96,23 +105,6 @@ XULComboboxAccessible::Value(nsString& aValue)
menuList->GetLabel(aValue); menuList->GetLabel(aValue);
} }
bool
XULComboboxAccessible::CanHaveAnonChildren()
{
if (mContent->NodeInfo()->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL) ||
mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
nsGkAtoms::_true, eIgnoreCase)) {
// Both the XUL <textbox type="autocomplete"> and <menulist editable="true"> widgets
// use XULComboboxAccessible. We need to walk the anonymous children for these
// so that the entry field is a child
return true;
}
// Argument of false indicates we don't walk anonymous children for
// menuitems
return false;
}
uint8_t uint8_t
XULComboboxAccessible::ActionCount() XULComboboxAccessible::ActionCount()
{ {

View File

@ -26,7 +26,6 @@ public:
virtual void Value(nsString& aValue) override; virtual void Value(nsString& aValue) override;
virtual a11y::role NativeRole() override; virtual a11y::role NativeRole() override;
virtual uint64_t NativeState() override; virtual uint64_t NativeState() override;
virtual bool CanHaveAnonChildren() override;
// ActionAccessible // ActionAccessible
virtual uint8_t ActionCount() override; virtual uint8_t ActionCount() override;

View File

@ -544,6 +544,10 @@ XULListitemAccessible::
nsGkAtoms::checkbox, nsGkAtoms::checkbox,
eCaseMatters); eCaseMatters);
mType = eXULListItemType; mType = eXULListItemType;
// Walk XBL anonymous children for list items. Overrides the flag value from
// base XULMenuitemAccessible class.
mStateFlags &= ~eNoXBLKids;
} }
XULListitemAccessible::~XULListitemAccessible() XULListitemAccessible::~XULListitemAccessible()
@ -668,13 +672,6 @@ XULListitemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
} }
} }
bool
XULListitemAccessible::CanHaveAnonChildren()
{
// That indicates we should walk anonymous children for listitems
return true;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// XULListitemAccessible: Widgets // XULListitemAccessible: Widgets

View File

@ -115,7 +115,6 @@ public:
virtual a11y::role NativeRole() override; virtual a11y::role NativeRole() override;
virtual uint64_t NativeState() override; virtual uint64_t NativeState() override;
virtual uint64_t NativeInteractiveState() const override; virtual uint64_t NativeInteractiveState() const override;
virtual bool CanHaveAnonChildren() override;
// Actions // Actions
virtual void ActionNameAt(uint8_t index, nsAString& aName) override; virtual void ActionNameAt(uint8_t index, nsAString& aName) override;

View File

@ -41,6 +41,7 @@ XULMenuitemAccessible::
XULMenuitemAccessible(nsIContent* aContent, DocAccessible* aDoc) : XULMenuitemAccessible(nsIContent* aContent, DocAccessible* aDoc) :
AccessibleWrap(aContent, aDoc) AccessibleWrap(aContent, aDoc)
{ {
mStateFlags |= eNoXBLKids;
} }
uint64_t uint64_t
@ -267,13 +268,6 @@ XULMenuitemAccessible::GetLevelInternal()
return nsAccUtils::GetLevelForXULContainerItem(mContent); return nsAccUtils::GetLevelForXULContainerItem(mContent);
} }
bool
XULMenuitemAccessible::CanHaveAnonChildren()
{
// That indicates we don't walk anonymous children for menuitems
return false;
}
bool bool
XULMenuitemAccessible::DoAction(uint8_t index) XULMenuitemAccessible::DoAction(uint8_t index)
{ {
@ -413,6 +407,8 @@ XULMenupopupAccessible::
mSelectControl = do_QueryInterface(mContent->GetFlattenedTreeParent()); mSelectControl = do_QueryInterface(mContent->GetFlattenedTreeParent());
if (!mSelectControl) if (!mSelectControl)
mGenericTypes &= ~eSelect; mGenericTypes &= ~eSelect;
mStateFlags |= eNoXBLKids;
} }
uint64_t uint64_t

View File

@ -30,8 +30,6 @@ public:
virtual uint64_t NativeInteractiveState() const override; virtual uint64_t NativeInteractiveState() const override;
virtual int32_t GetLevelInternal() override; virtual int32_t GetLevelInternal() override;
virtual bool CanHaveAnonChildren() override;
// ActionAccessible // ActionAccessible
virtual uint8_t ActionCount() override; virtual uint8_t ActionCount() override;
virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;

View File

@ -23,7 +23,7 @@ XULSliderAccessible::
XULSliderAccessible(nsIContent* aContent, DocAccessible* aDoc) : XULSliderAccessible(nsIContent* aContent, DocAccessible* aDoc) :
AccessibleWrap(aContent, aDoc) AccessibleWrap(aContent, aDoc)
{ {
mStateFlags |= eHasNumericValue; mStateFlags |= eHasNumericValue | eNoXBLKids;
} }
// Accessible // Accessible
@ -127,13 +127,6 @@ XULSliderAccessible::SetCurValue(double aValue)
return SetSliderAttr(nsGkAtoms::curpos, aValue); return SetSliderAttr(nsGkAtoms::curpos, aValue);
} }
bool
XULSliderAccessible::CanHaveAnonChildren()
{
// Do not allow anonymous xul:slider be accessible.
return false;
}
// Utils // Utils
nsIContent* nsIContent*

View File

@ -26,7 +26,6 @@ public:
virtual a11y::role NativeRole() override; virtual a11y::role NativeRole() override;
virtual uint64_t NativeInteractiveState() const override; virtual uint64_t NativeInteractiveState() const override;
virtual bool NativelyUnavailable() const override; virtual bool NativelyUnavailable() const override;
virtual bool CanHaveAnonChildren() override;
// Value // Value
virtual double MaxValue() const override; virtual double MaxValue() const override;

View File

@ -9,6 +9,8 @@ var StarUI = {
_itemId: -1, _itemId: -1,
uri: null, uri: null,
_batching: false, _batching: false,
_isNewBookmark: false,
_autoCloseTimer: 0,
_element: function(aID) { _element: function(aID) {
return document.getElementById(aID); return document.getElementById(aID);
@ -21,8 +23,11 @@ var StarUI = {
// initially the panel is hidden // initially the panel is hidden
// to avoid impacting startup / new window performance // to avoid impacting startup / new window performance
element.hidden = false; element.hidden = false;
element.addEventListener("popuphidden", this, false);
element.addEventListener("keypress", this, false); element.addEventListener("keypress", this, false);
element.addEventListener("mouseout", this, false);
element.addEventListener("mouseover", this, false);
element.addEventListener("popuphidden", this, false);
element.addEventListener("popupshown", this, false);
return this.panel = element; return this.panel = element;
}, },
@ -58,7 +63,11 @@ var StarUI = {
// nsIDOMEventListener // nsIDOMEventListener
handleEvent(aEvent) { handleEvent(aEvent) {
switch (aEvent.type) { switch (aEvent.type) {
case "mouseover":
clearTimeout(this._autoCloseTimer);
break;
case "popuphidden": case "popuphidden":
clearTimeout(this._autoCloseTimer);
if (aEvent.originalTarget == this.panel) { if (aEvent.originalTarget == this.panel) {
if (!this._element("editBookmarkPanelContent").hidden) if (!this._element("editBookmarkPanelContent").hidden)
this.quitEditMode(); this.quitEditMode();
@ -72,44 +81,42 @@ var StarUI = {
if (this._batching) if (this._batching)
this.endBatch(); this.endBatch();
switch (this._actionOnHide) { if (this._uriForRemoval) {
case "cancel": { if (this._isNewBookmark) {
if (!PlacesUIUtils.useAsyncTransactions) { if (!PlacesUtils.useAsyncTransactions) {
PlacesUtils.transactionManager.undoTransaction(); PlacesUtils.transactionManager.undoTransaction();
break; break;
} }
PlacesTransactions.undo().catch(Cu.reportError); PlacesTransactions().undo().catch(Cu.reportError);
break; break;
} }
case "remove": { // Remove all bookmarks for the bookmark's url, this also removes
// Remove all bookmarks for the bookmark's url, this also removes // the tags for the url.
// the tags for the url. if (!PlacesUIUtils.useAsyncTransactions) {
if (!PlacesUIUtils.useAsyncTransactions) { let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval); for (let itemId of itemIds) {
for (let itemId of itemIds) { let txn = new PlacesRemoveItemTransaction(itemId);
let txn = new PlacesRemoveItemTransaction(itemId); PlacesUtils.transactionManager.doTransaction(txn);
PlacesUtils.transactionManager.doTransaction(txn);
}
break;
} }
PlacesTransactions.RemoveBookmarksForUrls(this._uriForRemoval)
.transact().catch(Cu.reportError);
break; break;
} }
PlacesTransactions.RemoveBookmarksForUrls([this._uriForRemoval])
.transact().catch(Cu.reportError);
} }
this._actionOnHide = "";
} }
break; break;
case "keypress": case "keypress":
clearTimeout(this._autoCloseTimer);
if (aEvent.defaultPrevented) { if (aEvent.defaultPrevented) {
// The event has already been consumed inside of the panel. // The event has already been consumed inside of the panel.
break; break;
} }
switch (aEvent.keyCode) { switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_ESCAPE: case KeyEvent.DOM_VK_ESCAPE:
if (!this._element("editBookmarkPanelContent").hidden) this.panel.hidePopup();
this.cancelButtonOnCommand();
break; break;
case KeyEvent.DOM_VK_RETURN: case KeyEvent.DOM_VK_RETURN:
if (aEvent.target.classList.contains("expander-up") || if (aEvent.target.classList.contains("expander-up") ||
@ -123,12 +130,40 @@ var StarUI = {
break; break;
} }
break; break;
case "mouseout": {
// Don't handle events for descendent elements.
if (aEvent.target != aEvent.currentTarget) {
break;
}
// Explicit fall-through
}
case "popupshown":
// auto-close if new and not interacted with
if (this._isNewBookmark) {
// 3500ms matches the timeout that Pocket uses in
// browser/extensions/pocket/content/panels/js/saved.js
let delay = 3500;
if (this._closePanelQuickForTesting) {
delay /= 10;
}
this._autoCloseTimer = setTimeout(() => this.panel.hidePopup(), delay, this);
}
break;
} }
}, },
_overlayLoaded: false, _overlayLoaded: false,
_overlayLoading: false, _overlayLoading: false,
showEditBookmarkPopup: Task.async(function* (aNode, aAnchorElement, aPosition) { showEditBookmarkPopup: Task.async(function* (aNode, aAnchorElement, aPosition, aIsNewBookmark) {
// Slow double-clicks (not true double-clicks) shouldn't
// cause the panel to flicker.
if (this.panel.state == "showing" ||
this.panel.state == "open") {
return;
}
this._isNewBookmark = aIsNewBookmark;
this._uriForRemoval = "";
// TODO: Deprecate this once async transactions are enabled and the legacy // TODO: Deprecate this once async transactions are enabled and the legacy
// transactions code is gone (bug 1131491) - we don't want addons to to use // transactions code is gone (bug 1131491) - we don't want addons to to use
// the completeNodeLikeObjectForItemId, so it's better if they keep passing // the completeNodeLikeObjectForItemId, so it's better if they keep passing
@ -177,26 +212,18 @@ var StarUI = {
if (this.panel.state != "closed") if (this.panel.state != "closed")
return; return;
this._blockCommands(); // un-done in the popuphiding handler this._blockCommands(); // un-done in the popuphidden handler
// Set panel title:
// if we are batching, i.e. the bookmark has been added now,
// then show Page Bookmarked, else if the bookmark did already exist,
// we are about editing it, then use Edit This Bookmark.
this._element("editBookmarkPanelTitle").value = this._element("editBookmarkPanelTitle").value =
this._batching ? this._isNewBookmark ?
gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") : gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"); gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle");
// No description; show the Done, Cancel; // No description; show the Done, Remove;
this._element("editBookmarkPanelDescription").textContent = ""; this._element("editBookmarkPanelDescription").textContent = "";
this._element("editBookmarkPanelBottomButtons").hidden = false; this._element("editBookmarkPanelBottomButtons").hidden = false;
this._element("editBookmarkPanelContent").hidden = false; this._element("editBookmarkPanelContent").hidden = false;
// The remove button is shown only if we're not already batching, i.e.
// if the cancel button/ESC does not remove the bookmark.
this._element("editBookmarkPanelRemoveButton").hidden = this._batching;
// The label of the remove button differs if the URI is bookmarked // The label of the remove button differs if the URI is bookmarked
// multiple times. // multiple times.
let bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI); let bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
@ -250,14 +277,8 @@ var StarUI = {
gEditItemOverlay.uninitPanel(true); gEditItemOverlay.uninitPanel(true);
}, },
cancelButtonOnCommand: function SU_cancelButtonOnCommand() {
this._actionOnHide = "cancel";
this.panel.hidePopup(true);
},
removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() { removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId); this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
this._actionOnHide = "remove";
this.panel.hidePopup(); this.panel.hidePopup();
}, },
@ -325,7 +346,8 @@ var PlacesCommandHook = {
var uri = aBrowser.currentURI; var uri = aBrowser.currentURI;
var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri); var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
if (itemId == -1) { let isNewBookmark = itemId == -1;
if (isNewBookmark) {
// Bug 1148838 - Make this code work for full page plugins. // Bug 1148838 - Make this code work for full page plugins.
var title; var title;
var description; var description;
@ -342,10 +364,10 @@ var PlacesCommandHook = {
} }
catch (e) { } catch (e) { }
if (aShowEditUI) { if (aShowEditUI && isNewBookmark) {
// If we bookmark the page here (i.e. page was not "starred" already) // If we bookmark the page here but open right into a cancelable
// but open right into the "edit" state, start batching here, so // state (i.e. new bookmark in Library), start batching here so
// "Cancel" in that state removes the bookmark. // all of the actions can be undone in a single undo step.
StarUI.beginBatch(); StarUI.beginBatch();
} }
@ -376,16 +398,16 @@ var PlacesCommandHook = {
// 3. the content area // 3. the content area
if (BookmarkingUI.anchor) { if (BookmarkingUI.anchor) {
StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor, StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
"bottomcenter topright"); "bottomcenter topright", isNewBookmark);
return; return;
} }
let identityIcon = document.getElementById("identity-icon"); let identityIcon = document.getElementById("identity-icon");
if (isElementVisible(identityIcon)) { if (isElementVisible(identityIcon)) {
StarUI.showEditBookmarkPopup(itemId, identityIcon, StarUI.showEditBookmarkPopup(itemId, identityIcon,
"bottomcenter topright"); "bottomcenter topright", isNewBookmark);
} else { } else {
StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap"); StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
} }
}), }),
@ -394,6 +416,7 @@ var PlacesCommandHook = {
_bookmarkPagePT: Task.async(function* (aBrowser, aParentId, aShowEditUI) { _bookmarkPagePT: Task.async(function* (aBrowser, aParentId, aShowEditUI) {
let url = new URL(aBrowser.currentURI.spec); let url = new URL(aBrowser.currentURI.spec);
let info = yield PlacesUtils.bookmarks.fetch({ url }); let info = yield PlacesUtils.bookmarks.fetch({ url });
let isNewBookmark = !info;
if (!info) { if (!info) {
let parentGuid = aParentId !== undefined ? let parentGuid = aParentId !== undefined ?
yield PlacesUtils.promiseItemGuid(aParentId) : yield PlacesUtils.promiseItemGuid(aParentId) :
@ -417,10 +440,10 @@ var PlacesCommandHook = {
Components.utils.reportError(e); Components.utils.reportError(e);
} }
if (aShowEditUI) { if (aShowEditUI && isNewBookmark) {
// If we bookmark the page here (i.e. page was not "starred" already) // If we bookmark the page here but open right into a cancelable
// but open right into the "edit" state, start batching here, so // state (i.e. new bookmark in Library), start batching here so
// "Cancel" in that state removes the bookmark. // all of the actions can be undone in a single undo step.
StarUI.beginBatch(); StarUI.beginBatch();
} }
@ -452,16 +475,16 @@ var PlacesCommandHook = {
// 3. the content area // 3. the content area
if (BookmarkingUI.anchor) { if (BookmarkingUI.anchor) {
StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor, StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
"bottomcenter topright"); "bottomcenter topright", isNewBookmark);
return; return;
} }
let identityIcon = document.getElementById("identity-icon"); let identityIcon = document.getElementById("identity-icon");
if (isElementVisible(identityIcon)) { if (isElementVisible(identityIcon)) {
StarUI.showEditBookmarkPopup(node, identityIcon, StarUI.showEditBookmarkPopup(node, identityIcon,
"bottomcenter topright"); "bottomcenter topright", isNewBookmark);
} else { } else {
StarUI.showEditBookmarkPopup(node, aBrowser, "overlap"); StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
} }
}), }),
@ -1703,19 +1726,15 @@ var BookmarkingUI = {
let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID) let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
.forWindow(window); .forWindow(window);
if (widget.overflowed) { if (widget.overflowed) {
// Allow to close the panel if the page is already bookmarked, cause // Close the overflow panel because the Edit Bookmark panel will appear.
// we are going to open the edit bookmark panel. widget.node.removeAttribute("closemenu");
if (isBookmarked)
widget.node.removeAttribute("closemenu");
else
widget.node.setAttribute("closemenu", "none");
} }
// Ignore clicks on the star if we are updating its state. // Ignore clicks on the star if we are updating its state.
if (!this._pendingStmt) { if (!this._pendingStmt) {
if (!isBookmarked) if (!isBookmarked)
this._showBookmarkedNotification(); this._showBookmarkedNotification();
PlacesCommandHook.bookmarkCurrentPage(isBookmarked); PlacesCommandHook.bookmarkCurrentPage(true);
} }
}, },

View File

@ -599,6 +599,10 @@ toolbarbutton.bookmark-item {
#personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-icon { #personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
image-rendering: -moz-crisp-edges; image-rendering: -moz-crisp-edges;
} }
/* Synced Tabs sidebar */
html|*.tabs-container html|*.item-tabs-list html|*.item-icon-container {
image-rendering: -moz-crisp-edges;
}
} }
#editBMPanel_tagsSelector { #editBMPanel_tagsSelector {

View File

@ -2345,7 +2345,12 @@ function URLBarSetURI(aURI) {
checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) { checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
value = ""; value = "";
} else { } else {
value = losslessDecodeURI(uri); // We should deal with losslessDecodeURI throwing for exotic URIs
try {
value = losslessDecodeURI(uri);
} catch (ex) {
value = "about:blank";
}
} }
valid = !isBlankPageURL(uri.spec); valid = !isBlankPageURL(uri.spec);
@ -6443,13 +6448,14 @@ function checkEmptyPageOrigin(browser = gBrowser.selectedBrowser,
} }
// Not all principals have URIs... // Not all principals have URIs...
if (contentPrincipal.URI) { if (contentPrincipal.URI) {
// A manually entered about:blank URI is slightly magical:
if (uri.spec == "about:blank" && contentPrincipal.isNullPrincipal) { if (uri.spec == "about:blank" && contentPrincipal.isNullPrincipal) {
return true; return true;
} }
return contentPrincipal.URI.equals(uri); return contentPrincipal.URI.equals(uri);
} }
// ... so for those that don't have them, enforce that the page has the // ... so for those that don't have them, enforce that the page has the
// system principal (this matches e.g. on about:home). // system principal (this matches e.g. on about:newtab).
let ssm = Services.scriptSecurityManager; let ssm = Services.scriptSecurityManager;
return ssm.isSystemPrincipal(contentPrincipal); return ssm.isSystemPrincipal(contentPrincipal);
} }

View File

@ -180,12 +180,6 @@
<vbox> <vbox>
<label id="editBookmarkPanelTitle"/> <label id="editBookmarkPanelTitle"/>
<description id="editBookmarkPanelDescription"/> <description id="editBookmarkPanelDescription"/>
<hbox>
<button id="editBookmarkPanelRemoveButton"
class="editBookmarkPanelHeaderButton"
oncommand="StarUI.removeBookmarkButtonCommand();"
accesskey="&editBookmark.removeBookmark.accessKey;"/>
</hbox>
</vbox> </vbox>
</row> </row>
<vbox id="editBookmarkPanelContent" flex="1" hidden="true"/> <vbox id="editBookmarkPanelContent" flex="1" hidden="true"/>
@ -196,15 +190,15 @@
label="&editBookmark.done.label;" label="&editBookmark.done.label;"
default="true" default="true"
oncommand="StarUI.panel.hidePopup();"/> oncommand="StarUI.panel.hidePopup();"/>
<button id="editBookmarkPanelDeleteButton" <button id="editBookmarkPanelRemoveButton"
class="editBookmarkPanelBottomButton" class="editBookmarkPanelBottomButton"
label="&editBookmark.cancel.label;" oncommand="StarUI.removeBookmarkButtonCommand();"
oncommand="StarUI.cancelButtonOnCommand();"/> accesskey="&editBookmark.removeBookmark.accessKey;"/>
#else #else
<button id="editBookmarkPanelDeleteButton" <button id="editBookmarkPanelRemoveButton"
class="editBookmarkPanelBottomButton" class="editBookmarkPanelBottomButton"
label="&editBookmark.cancel.label;" oncommand="StarUI.removeBookmarkButtonCommand();"
oncommand="StarUI.cancelButtonOnCommand();"/> accesskey="&editBookmark.removeBookmark.accessKey;"/>
<button id="editBookmarkPanelDoneButton" <button id="editBookmarkPanelDoneButton"
class="editBookmarkPanelBottomButton" class="editBookmarkPanelBottomButton"
label="&editBookmark.done.label;" label="&editBookmark.done.label;"

View File

@ -564,12 +564,18 @@ nsContextMenu.prototype = {
LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host); LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
}, },
inspectNode: function CM_inspectNode() { inspectNode: function() {
let {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {}); let {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
let gBrowser = this.browser.ownerDocument.defaultView.gBrowser; let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
let tt = devtools.TargetFactory.forTab(gBrowser.selectedTab); let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
return gDevTools.showToolbox(tt, "inspector").then(function(toolbox) {
return gDevTools.showToolbox(target, "inspector").then(toolbox => {
let inspector = toolbox.getCurrentPanel(); let inspector = toolbox.getCurrentPanel();
// new-node-front tells us when the node has been selected, whether the
// browser is remote or not.
let onNewNode = inspector.selection.once("new-node-front");
if (this.isRemote) { if (this.isRemote) {
this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target}); this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target});
inspector.walker.findInspectingNode().then(nodeFront => { inspector.walker.findInspectingNode().then(nodeFront => {
@ -578,7 +584,13 @@ nsContextMenu.prototype = {
} else { } else {
inspector.selection.setNode(this.target, "browser-context-menu"); inspector.selection.setNode(this.target, "browser-context-menu");
} }
}.bind(this));
return onNewNode.then(() => {
// Now that the node has been selected, wait until the inspector is
// fully updated.
return inspector.once("inspector-updated");
});
});
}, },
// Set various context menu attributes based on the state of the world. // Set various context menu attributes based on the state of the world.
@ -981,11 +993,16 @@ nsContextMenu.prototype = {
catch (e) { } catch (e) { }
} }
let params = this._openLinkInParameters({ let params = {
allowMixedContent: persistAllowMixedContentInChildTab, allowMixedContent: persistAllowMixedContentInChildTab,
userContextId: parseInt(event.target.getAttribute('usercontextid')), userContextId: parseInt(event.target.getAttribute('usercontextid'))
}); };
openLinkIn(this.linkURL, "tab", params);
if (params.userContextId != this.principal.originAttributes.userContextId) {
params.noReferrer = true;
}
openLinkIn(this.linkURL, "tab", this._openLinkInParameters(params));
}, },
// open URL in current tab // open URL in current tab

View File

@ -111,7 +111,8 @@ Sanitizer.prototype = {
// Store the list of items to clear, in case we are killed before we // Store the list of items to clear, in case we are killed before we
// get a chance to complete. // get a chance to complete.
Preferences.set(Sanitizer.PREF_SANITIZE_IN_PROGRESS, JSON.stringify(itemsToClear)); Preferences.set(Sanitizer.PREF_SANITIZE_IN_PROGRESS,
JSON.stringify(itemsToClear));
// Store the list of items to clear, for debugging/forensics purposes // Store the list of items to clear, for debugging/forensics purposes
for (let k of itemsToClear) { for (let k of itemsToClear) {
@ -677,10 +678,18 @@ Sanitizer.prototype = {
} }
}; };
// "Static" members // The preferences branch for the sanitizer.
Sanitizer.PREF_DOMAIN = "privacy.sanitize."; Sanitizer.PREF_DOMAIN = "privacy.sanitize.";
// Whether we should sanitize on shutdown.
Sanitizer.PREF_SANITIZE_ON_SHUTDOWN = "privacy.sanitize.sanitizeOnShutdown"; Sanitizer.PREF_SANITIZE_ON_SHUTDOWN = "privacy.sanitize.sanitizeOnShutdown";
// During a sanitization this is set to a json containing the array of items
// being sanitized, then cleared once the sanitization is complete.
// This allows to retry a sanitization on startup in case it was interrupted
// by a crash.
Sanitizer.PREF_SANITIZE_IN_PROGRESS = "privacy.sanitize.sanitizeInProgress"; Sanitizer.PREF_SANITIZE_IN_PROGRESS = "privacy.sanitize.sanitizeInProgress";
// Whether the previous shutdown sanitization completed successfully.
// Note that PREF_SANITIZE_IN_PROGRESS would be enough to detect an interrupted
// sanitization, but this is still supported for backwards compatibility.
Sanitizer.PREF_SANITIZE_DID_SHUTDOWN = "privacy.sanitize.didShutdownSanitize"; Sanitizer.PREF_SANITIZE_DID_SHUTDOWN = "privacy.sanitize.didShutdownSanitize";
// Time span constants corresponding to values of the privacy.sanitize.timeSpan // Time span constants corresponding to values of the privacy.sanitize.timeSpan
@ -766,6 +775,15 @@ Sanitizer.sanitize = function(aParentWindow)
}; };
Sanitizer.onStartup = Task.async(function*() { Sanitizer.onStartup = Task.async(function*() {
// Check if we were interrupted during the last shutdown sanitization.
let shutownSanitizationWasInterrupted =
Preferences.get(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false) &&
!Preferences.has(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
// Regardless, reset the pref, since we want to check it at the next startup
// even if the browser exits abruptly.
Preferences.reset(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
Services.prefs.savePrefFile(null);
// Make sure that we are triggered during shutdown, at the right time, // Make sure that we are triggered during shutdown, at the right time,
// and only once. // and only once.
let placesClient = Cc["@mozilla.org/browser/nav-history-service;1"] let placesClient = Cc["@mozilla.org/browser/nav-history-service;1"]
@ -786,18 +804,19 @@ Sanitizer.onStartup = Task.async(function*() {
} }
placesClient.addBlocker("sanitize.js: Sanitize on shutdown", doSanitize); placesClient.addBlocker("sanitize.js: Sanitize on shutdown", doSanitize);
// Handle incomplete sanitizations // Check if Firefox crashed before completing a sanitization.
if (Preferences.has(Sanitizer.PREF_SANITIZE_IN_PROGRESS)) { let lastInterruptedSanitization = Preferences.get(Sanitizer.PREF_SANITIZE_IN_PROGRESS, "");
// Firefox crashed during sanitization. if (lastInterruptedSanitization) {
let s = new Sanitizer(); let s = new Sanitizer();
let json = Preferences.get(Sanitizer.PREF_SANITIZE_IN_PROGRESS); // If the json is invalid this will just throw and reject the Task.
let itemsToClear = JSON.parse(json); let itemsToClear = JSON.parse(lastInterruptedSanitization);
yield s.sanitize(itemsToClear); yield s.sanitize(itemsToClear);
} } else if (shutownSanitizationWasInterrupted) {
if (Preferences.has(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN)) { // Ideally lastInterruptedSanitization should always be set when a
// Firefox crashed before having a chance to sanitize during shutdown. // sanitization is interrupted, but some add-ons or Firefox previous
// (note that if Firefox crashed during shutdown sanitization, we // versions may not set the pref.
// will hit both `if` so we will run a second double-sanitization). // In such a case, we can still detect an interrupted shutdown sanitization,
// and just redo it.
yield Sanitizer.onShutdown(); yield Sanitizer.onShutdown();
} }
}); });
@ -810,5 +829,8 @@ Sanitizer.onShutdown = Task.async(function*() {
let s = new Sanitizer(); let s = new Sanitizer();
s.prefDomain = "privacy.clearOnShutdown."; s.prefDomain = "privacy.clearOnShutdown.";
yield s.sanitize(); yield s.sanitize();
// We didn't crash during shutdown sanitization, so annotate it to avoid
// sanitizing again on startup.
Preferences.set(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN, true); Preferences.set(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN, true);
Services.prefs.savePrefFile(null);
}); });

View File

@ -1026,11 +1026,16 @@
if (!aForceUpdate) { if (!aForceUpdate) {
TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS"); TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
if (!Services.appinfo.browserTabsRemoteAutostart) { if (!gMultiProcessBrowser) {
// old way of measuring tab paint which is not // old way of measuring tab paint which is not valid with e10s.
// valid with e10s. // Waiting until the next MozAfterPaint ensures that we capture
window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) // the time it takes to paint, upload the textures to the compositor,
.beginTabSwitch(); // and then composite.
TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_MS");
window.addEventListener("MozAfterPaint", function onMozAfterPaint() {
TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_MS");
window.removeEventListener("MozAfterPaint", onMozAfterPaint);
});
} }
} }
@ -5891,7 +5896,7 @@
bgLoad = !bgLoad; bgLoad = !bgLoad;
let tab = this._getDragTargetTab(event, true); let tab = this._getDragTargetTab(event, true);
if (!tab || dropEffect == "copy") { if (!tab) {
// We're adding a new tab. // We're adding a new tab.
let newIndex = this._getDropIndex(event, true); let newIndex = this._getDropIndex(event, true);
let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true}); let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true});
@ -6243,9 +6248,11 @@
if (browser.audioMuted) { if (browser.audioMuted) {
browser.unmute(); browser.unmute();
this.removeAttribute("muted"); this.removeAttribute("muted");
BrowserUITelemetry.countTabMutingEvent("unmute", aMuteReason);
} else { } else {
browser.mute(); browser.mute();
this.setAttribute("muted", "true"); this.setAttribute("muted", "true");
BrowserUITelemetry.countTabMutingEvent("mute", aMuteReason);
} }
this.muteReason = aMuteReason || null; this.muteReason = aMuteReason || null;
tabContainer.tabbrowser._tabAttrModified(this, ["muted"]); tabContainer.tabbrowser._tabAttrModified(this, ["muted"]);

View File

@ -8,5 +8,6 @@ support-files =
[browser_notification_open_settings.js] [browser_notification_open_settings.js]
[browser_notification_remove_permission.js] [browser_notification_remove_permission.js]
[browser_notification_permission_migration.js] [browser_notification_permission_migration.js]
[browser_notification_replace.js]
[browser_notification_tab_switching.js] [browser_notification_tab_switching.js]
skip-if = buildapp == 'mulet' skip-if = buildapp == 'mulet'

View File

@ -28,7 +28,7 @@ add_task(function* test_notificationClose() {
let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel"); let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel");
is(alertTitleLabel.value, "Test title", "Title text of notification should be present"); is(alertTitleLabel.value, "Test title", "Title text of notification should be present");
let alertTextLabel = alertWindow.document.getElementById("alertTextLabel"); let alertTextLabel = alertWindow.document.getElementById("alertTextLabel");
is(alertTextLabel.textContent, "Test body", "Body text of notification should be present"); is(alertTextLabel.textContent, "Test body 2", "Body text of notification should be present");
let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton"); let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton");
is(alertCloseButton.localName, "toolbarbutton", "close button found"); is(alertCloseButton.localName, "toolbarbutton", "close button found");

View File

@ -0,0 +1,38 @@
"use strict";
let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
add_task(function* test_notificationReplace() {
let pm = Services.perms;
pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: notificationURL
}, function* dummyTabTask(aBrowser) {
yield ContentTask.spawn(aBrowser, {}, function* () {
let win = content.window.wrappedJSObject;
let notification = win.showNotification1();
let promiseCloseEvent = ContentTaskUtils.waitForEvent(notification, "close");
let showEvent = yield ContentTaskUtils.waitForEvent(notification, "show");
is(showEvent.target.body, "Test body 1", "Showed tagged notification");
let newNotification = win.showNotification2();
let newShowEvent = yield ContentTaskUtils.waitForEvent(newNotification, "show");
is(newShowEvent.target.body, "Test body 2", "Showed new notification with same tag");
let closeEvent = yield promiseCloseEvent;
is(closeEvent.target.body, "Test body 1", "Closed previous tagged notification");
let promiseNewCloseEvent = ContentTaskUtils.waitForEvent(newNotification, "close");
newNotification.close();
let newCloseEvent = yield promiseNewCloseEvent;
is(newCloseEvent.target.body, "Test body 2", "Closed new notification");
});
});
});
add_task(function* cleanup() {
Services.perms.remove(makeURI(notificationURL), "desktop-notification");
});

View File

@ -8,7 +8,7 @@ function showNotification1() {
var options = { var options = {
dir: undefined, dir: undefined,
lang: undefined, lang: undefined,
body: "Test body", body: "Test body 1",
tag: "Test tag", tag: "Test tag",
icon: undefined, icon: undefined,
}; };
@ -23,7 +23,7 @@ function showNotification2() {
var options = { var options = {
dir: undefined, dir: undefined,
lang: undefined, lang: undefined,
body: "Test body", body: "Test body 2",
tag: "Test tag", tag: "Test tag",
icon: undefined, icon: undefined,
}; };

View File

@ -154,6 +154,7 @@ skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir
skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
[browser_beforeunload_duplicate_dialogs.js] [browser_beforeunload_duplicate_dialogs.js]
[browser_blob-channelname.js] [browser_blob-channelname.js]
[browser_bookmark_popup.js]
[browser_bookmark_titles.js] [browser_bookmark_titles.js]
skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341) skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
[browser_bug304198.js] [browser_bug304198.js]

View File

@ -0,0 +1,271 @@
/* 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";
/**
* Test opening and closing the bookmarks panel.
*/
let bookmarkPanel = document.getElementById("editBookmarkPanel");
let bookmarkStar = document.getElementById("bookmarks-menu-button");
let bookmarkPanelTitle = document.getElementById("editBookmarkPanelTitle");
StarUI._closePanelQuickForTesting = true;
Services.prefs.setBoolPref("browser.bookmarks.closePanelQuickForTesting", true);
function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
shouldAutoClose, popupHideFn, isBookmarkRemoved}) {
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
try {
if (!isNewBookmark) {
yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "about:home",
title: "Home Page"
});
}
is(bookmarkStar.hasAttribute("starred"), !isNewBookmark,
"Page should only be starred prior to popupshown if editing bookmark");
is(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test");
let shownPromise = promisePopupShown(bookmarkPanel);
yield popupShowFn(tab.linkedBrowser);
yield shownPromise;
is(bookmarkPanel.state, "open", "Panel should be 'open' after shownPromise is resolved");
if (popupEditFn) {
yield popupEditFn();
}
let bookmarks = [];
yield PlacesUtils.bookmarks.fetch({url: "about:home"}, bm => bookmarks.push(bm));
is(bookmarks.length, 1, "Only one bookmark should exist");
is(bookmarkStar.getAttribute("starred"), "true", "Page is starred");
is(bookmarkPanel.state, "open", "Check that panel state is 'open'");
is(bookmarkPanelTitle.value,
isNewBookmark ?
gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"),
"title should match isEditingBookmark state");
if (!shouldAutoClose) {
yield new Promise(resolve => setTimeout(resolve, 400));
}
let hiddenPromise = promisePopupHidden(bookmarkPanel);
if (popupHideFn) {
yield popupHideFn();
}
yield hiddenPromise;
is(bookmarkStar.hasAttribute("starred"), !isBookmarkRemoved,
"Page is starred after closing");
} finally {
let bookmark = yield PlacesUtils.bookmarks.fetch({url: "about:home"});
is(!!bookmark, !isBookmarkRemoved,
"bookmark should not be present if a panel action should've removed it");
if (bookmark) {
yield PlacesUtils.bookmarks.remove(bookmark);
}
gBrowser.removeTab(tab);
}
}
add_task(function* panel_shown_for_new_bookmarks_and_autocloses() {
yield test_bookmarks_popup({
isNewBookmark: true,
popupShowFn() {
bookmarkStar.click();
},
shouldAutoClose: true,
isBookmarkRemoved: false,
});
});
add_task(function* panel_shown_for_once_for_doubleclick_on_new_bookmark_star_and_autocloses() {
yield test_bookmarks_popup({
isNewBookmark: true,
popupShowFn() {
EventUtils.synthesizeMouse(bookmarkStar, 10, 10, { clickCount: 2 },
window);
},
shouldAutoClose: true,
isBookmarkRemoved: false,
});
});
add_task(function* panel_shown_once_for_slow_doubleclick_on_new_bookmark_star_and_autocloses() {
todo(false, "bug 1250267, may need to add some tracking state to " +
"browser-places.js for this.");
return;
yield test_bookmarks_popup({
isNewBookmark: true,
*popupShowFn() {
EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
yield new Promise(resolve => setTimeout(resolve, 300));
EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
},
shouldAutoClose: true,
isBookmarkRemoved: false,
});
});
add_task(function* panel_shown_for_keyboardshortcut_on_new_bookmark_star_and_autocloses() {
yield test_bookmarks_popup({
isNewBookmark: true,
popupShowFn() {
EventUtils.synthesizeKey("D", {accelKey: true}, window);
},
shouldAutoClose: true,
isBookmarkRemoved: false,
});
});
add_task(function* panel_shown_for_new_bookmarks_mouseover_mouseout() {
yield test_bookmarks_popup({
isNewBookmark: true,
popupShowFn() {
bookmarkStar.click();
},
*popupEditFn() {
let mouseOverPromise = new Promise(resolve => {
bookmarkPanel.addEventListener("mouseover", function onmouseover() {
bookmarkPanel.removeEventListener("mouseover", onmouseover);
resolve();
});
});
yield new Promise(resolve => {
EventUtils.synthesizeNativeMouseMove(bookmarkPanel, 0, 0, resolve, window);
});
info("Waiting for mouseover event");
yield mouseOverPromise;
info("Got mouseover event");
yield new Promise(resolve => setTimeout(resolve, 400));
is(bookmarkPanel.state, "open", "Panel should still be open on mouseover");
let mouseOutPromise = new Promise(resolve => {
bookmarkPanel.addEventListener("mouseout", function onmouseout() {
bookmarkPanel.removeEventListener("mouseout", onmouseout);
resolve();
});
});
yield new Promise(resolve => {
EventUtils.synthesizeNativeMouseMove(bookmarkStar, 0, 0, resolve, window);
});
info("Waiting for mouseout event");
yield mouseOutPromise;
info("Got mouseout event, should autoclose now");
},
shouldAutoClose: true,
isBookmarkRemoved: false,
});
});
add_task(function* panel_shown_for_new_bookmark_no_autoclose_close_with_ESC() {
yield test_bookmarks_popup({
isNewBookmark: false,
popupShowFn() {
bookmarkStar.click();
},
shouldAutoClose: false,
popupHideFn() {
EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
},
isBookmarkRemoved: false,
});
});
add_task(function* panel_shown_for_editing_no_autoclose_close_with_ESC() {
yield test_bookmarks_popup({
isNewBookmark: false,
popupShowFn() {
bookmarkStar.click();
},
shouldAutoClose: false,
popupHideFn() {
EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
},
isBookmarkRemoved: false,
});
});
add_task(function* panel_shown_for_new_bookmark_keypress_no_autoclose() {
yield test_bookmarks_popup({
isNewBookmark: true,
popupShowFn() {
bookmarkStar.click();
},
popupEditFn() {
EventUtils.sendChar("VK_TAB", window);
},
shouldAutoClose: false,
popupHideFn() {
bookmarkPanel.hidePopup();
},
isBookmarkRemoved: false,
});
});
add_task(function* contextmenu_new_bookmark_click_no_autoclose() {
yield test_bookmarks_popup({
isNewBookmark: true,
*popupShowFn(browser) {
let contextMenu = document.getElementById("contentAreaContextMenu");
let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu,
"popupshown");
let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu,
"popuphidden");
yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
type: "contextmenu",
button: 2
}, browser);
yield awaitPopupShown;
document.getElementById("context-bookmarkpage").click();
contextMenu.hidePopup();
yield awaitPopupHidden;
},
popupEditFn() {
bookmarkPanelTitle.click();
},
shouldAutoClose: false,
popupHideFn() {
bookmarkPanel.hidePopup();
},
isBookmarkRemoved: false,
});
});
add_task(function* bookmarks_menu_new_bookmark_remove_bookmark() {
yield test_bookmarks_popup({
isNewBookmark: true,
popupShowFn(browser) {
document.getElementById("menu_bookmarkThisPage").doCommand();
},
shouldAutoClose: true,
popupHideFn() {
document.getElementById("editBookmarkPanelRemoveButton").click();
},
isBookmarkRemoved: true,
});
});
add_task(function* ctrl_d_edit_bookmark_remove_bookmark() {
yield test_bookmarks_popup({
isNewBookmark: false,
popupShowFn(browser) {
EventUtils.synthesizeKey("D", {accelKey: true}, window);
},
shouldAutoClose: true,
popupHideFn() {
document.getElementById("editBookmarkPanelRemoveButton").click();
},
isBookmarkRemoved: true,
});
});
registerCleanupFunction(function() {
Services.prefs.clearUserPref("browser.bookmarks.closePanelQuickForTesting");
delete StarUI._closePanelQuickForTesting;
})

View File

@ -60,6 +60,9 @@ function test() {
testVal("<user:pass@sub1.sub2.sub3.>mozilla.org"); testVal("<user:pass@sub1.sub2.sub3.>mozilla.org");
testVal("<user:pass@>mozilla.org"); testVal("<user:pass@>mozilla.org");
testVal("<https://>mozilla.org< >");
testVal("mozilla.org< >");
testVal("<https://>mozilla.org</file.ext>"); testVal("<https://>mozilla.org</file.ext>");
testVal("<https://>mozilla.org</sub/file.ext>"); testVal("<https://>mozilla.org</sub/file.ext>");
testVal("<https://>mozilla.org</sub/file.ext?foo>"); testVal("<https://>mozilla.org</sub/file.ext?foo>");

View File

@ -13,3 +13,5 @@ skip-if = os == 'linux' # Bug 1144816
[browser_referrer_open_link_in_window.js] [browser_referrer_open_link_in_window.js]
skip-if = os == 'linux' # Bug 1145199 skip-if = os == 'linux' # Bug 1145199
[browser_referrer_simple_click.js] [browser_referrer_simple_click.js]
[browser_referrer_open_link_in_container_tab.js]
skip-if = os == 'linux' # Bug 1144816

View File

@ -0,0 +1,50 @@
// Tests referrer on context menu navigation - open link in new container tab.
// Selects "open link in new container tab" from the context menu.
function getReferrerTest(aTestNumber) {
let test = _referrerTests[aTestNumber];
if (test) {
// We want all the referrer tests to fail!
test.result = "";
}
return test;
}
function startNewTabTestCase(aTestNumber) {
info("browser_referrer_open_link_in_container_tab: " +
getReferrerTestDescription(aTestNumber));
contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
someTabLoaded(gTestWindow).then(function(aNewTab) {
gTestWindow.gBrowser.selectedTab = aNewTab;
checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
startNewTabTestCase);
});
let menu = gTestWindow.document.getElementById("context-openlinkinusercontext-menu");
ok(menu && menu.firstChild, "The menu exists and it has a first child node.");
let menupopup = menu.firstChild;
is(menupopup.nodeType, Node.ELEMENT_NODE, "We have a menupopup.");
ok(menupopup.firstChild, "We have a first container entry.");
let firstContext = menupopup.firstChild;
is(firstContext.nodeType, Node.ELEMENT_NODE, "We have a first container entry.");
ok(firstContext.hasAttribute('usercontextid'), "We have a usercontextid value.");
firstContext.doCommand();
aContextMenu.hidePopup();
});
}
function test() {
waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{set: [["privacy.userContext.enabled", true]]},
function() {
requestLongerTimeout(10); // slowwww shutdown on e10s
startReferrerTest(startNewTabTestCase);
});
}

View File

@ -1,3 +1,4 @@
[browser_moz_action_link.js]
[browser_urlbar_blanking.js] [browser_urlbar_blanking.js]
support-files = support-files =
file_blank_but_not_blank.html file_blank_but_not_blank.html

View File

@ -0,0 +1,31 @@
"use strict";
const kURIs = [
"moz-action:foo,",
"moz-action:foo",
];
add_task(function*() {
for (let uri of kURIs) {
let dataURI = `data:text/html,<a id=a href="${uri}" target=_blank>Link</a>`;
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI);
let tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, function() {});
yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
content.document.getElementById("a").click();
});
yield tabSwitchPromise;
isnot(gBrowser.selectedTab, tab, "Switched to new tab!");
is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank");
let newTab = gBrowser.selectedTab;
yield BrowserTestUtils.switchTab(gBrowser, tab);
yield BrowserTestUtils.switchTab(gBrowser, newTab);
is(gBrowser.selectedTab, newTab, "Switched to new tab again!");
is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank after tab switch");
// Finally, check that directly setting it produces the right results, too:
URLBarSetURI(makeURI(uri));
is(gURLBar.value, "about:blank", "URL bar should still be displaying about:blank");
yield BrowserTestUtils.removeTab(newTab);
yield BrowserTestUtils.removeTab(tab);
}
});

View File

@ -261,7 +261,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
trimmedLength = "http://".length; trimmedLength = "http://".length;
} }
let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(.+?)(?::\d+)?(?:[\/#?]|$)/); let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(\S+?)(?::\d+)?\s*(?:[\/#?]|$)/);
if (!matchedURL) if (!matchedURL)
return; return;
@ -839,12 +839,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<method name="_parseActionUrl"> <method name="_parseActionUrl">
<parameter name="aUrl"/> <parameter name="aUrl"/>
<body><![CDATA[ <body><![CDATA[
if (!aUrl.startsWith("moz-action:")) const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
if (!MOZ_ACTION_REGEX.test(aUrl))
return null; return null;
// URL is in the format moz-action:ACTION,PARAMS // URL is in the format moz-action:ACTION,PARAMS
// Where PARAMS is a JSON encoded object. // Where PARAMS is a JSON encoded object.
let [, type, params] = aUrl.match(/^moz-action:([^,]+),(.*)$/); let [, type, params] = aUrl.match(MOZ_ACTION_REGEX);
let action = { let action = {
type: type, type: type,

View File

@ -33,7 +33,6 @@ skip-if = os == "mac"
skip-if = os == "linux" skip-if = os == "linux"
[browser_901207_searchbar_in_panel.js] [browser_901207_searchbar_in_panel.js]
skip-if = e10s # bug 1090656
[browser_913972_currentset_overflow.js] [browser_913972_currentset_overflow.js]
skip-if = os == "linux" skip-if = os == "linux"
@ -69,9 +68,9 @@ skip-if = os == "linux"
[browser_947914_button_addons.js] [browser_947914_button_addons.js]
skip-if = os == "linux" # Intermittent failures skip-if = os == "linux" # Intermittent failures
[browser_947914_button_copy.js] [browser_947914_button_copy.js]
skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561 skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_cut.js] [browser_947914_button_cut.js]
skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561 skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_find.js] [browser_947914_button_find.js]
skip-if = os == "linux" # Intermittent failures skip-if = os == "linux" # Intermittent failures
[browser_947914_button_history.js] [browser_947914_button_history.js]
@ -81,9 +80,9 @@ skip-if = os == "linux" # Intermittent failures
[browser_947914_button_newWindow.js] [browser_947914_button_newWindow.js]
skip-if = os == "linux" # Intermittent failures skip-if = os == "linux" # Intermittent failures
[browser_947914_button_paste.js] [browser_947914_button_paste.js]
skip-if = os == "linux" || e10s # Intermittent failures on Linux, e10s issues are bug 1091561 skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_print.js] [browser_947914_button_print.js]
skip-if = os == "linux" || (os == "win" && e10s) # Intermittent failures on Linux, e10s issues on Windows (bug 1088714) skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_savePage.js] [browser_947914_button_savePage.js]
skip-if = os == "linux" # Intermittent failures skip-if = os == "linux" # Intermittent failures
[browser_947914_button_zoomIn.js] [browser_947914_button_zoomIn.js]
@ -123,7 +122,6 @@ skip-if = os == "linux"
[browser_984455_bookmarks_items_reparenting.js] [browser_984455_bookmarks_items_reparenting.js]
skip-if = os == "linux" skip-if = os == "linux"
[browser_985815_propagate_setToolbarVisibility.js] [browser_985815_propagate_setToolbarVisibility.js]
skip-if = e10s # bug 1090635
[browser_987177_destroyWidget_xul.js] [browser_987177_destroyWidget_xul.js]
[browser_987177_xul_wrapper_updating.js] [browser_987177_xul_wrapper_updating.js]
[browser_987185_syncButton.js] [browser_987185_syncButton.js]
@ -133,7 +131,6 @@ skip-if = e10s # Bug 1088710
[browser_988072_sidebar_events.js] [browser_988072_sidebar_events.js]
[browser_989338_saved_placements_not_resaved.js] [browser_989338_saved_placements_not_resaved.js]
[browser_989751_subviewbutton_class.js] [browser_989751_subviewbutton_class.js]
skip-if = os == "linux" && e10s # Bug 1102900, bug 1104745, bug 1104761
[browser_992747_toggle_noncustomizable_toolbar.js] [browser_992747_toggle_noncustomizable_toolbar.js]
[browser_993322_widget_notoolbar.js] [browser_993322_widget_notoolbar.js]
[browser_995164_registerArea_during_customize_mode.js] [browser_995164_registerArea_during_customize_mode.js]

View File

@ -8,57 +8,52 @@ var initialLocation = gBrowser.currentURI.spec;
var globalClipboard; var globalClipboard;
add_task(function*() { add_task(function*() {
info("Check copy button existence and functionality"); yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
info("Check copy button existence and functionality");
let testText = "copy text test"; let testText = "copy text test";
gURLBar.focus(); gURLBar.focus();
info("The URL bar was focused"); info("The URL bar was focused");
yield PanelUI.show(); yield PanelUI.show();
info("Menu panel was opened"); info("Menu panel was opened");
let copyButton = document.getElementById("copy-button"); let copyButton = document.getElementById("copy-button");
ok(copyButton, "Copy button exists in Panel Menu"); ok(copyButton, "Copy button exists in Panel Menu");
ok(copyButton.getAttribute("disabled"), "Copy button is initially disabled"); ok(copyButton.getAttribute("disabled"), "Copy button is initially disabled");
// copy text from URL bar // copy text from URL bar
gURLBar.value = testText; gURLBar.value = testText;
gURLBar.focus(); gURLBar.focus();
gURLBar.select(); gURLBar.select();
yield PanelUI.show(); yield PanelUI.show();
info("Menu panel was opened"); info("Menu panel was opened");
ok(!copyButton.hasAttribute("disabled"), "Copy button is enabled when selecting"); ok(!copyButton.hasAttribute("disabled"), "Copy button is enabled when selecting");
copyButton.click(); copyButton.click();
is(gURLBar.value, testText, "Selected text is unaltered when clicking copy"); is(gURLBar.value, testText, "Selected text is unaltered when clicking copy");
// check that the text was added to the clipboard // check that the text was added to the clipboard
let clipboard = Services.clipboard; let clipboard = Services.clipboard;
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable); let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
globalClipboard = clipboard.kGlobalClipboard; globalClipboard = clipboard.kGlobalClipboard;
transferable.init(null); transferable.init(null);
transferable.addDataFlavor("text/unicode"); transferable.addDataFlavor("text/unicode");
clipboard.getData(transferable, globalClipboard); clipboard.getData(transferable, globalClipboard);
let str = {}, strLength = {}; let str = {}, strLength = {};
transferable.getTransferData("text/unicode", str, strLength); transferable.getTransferData("text/unicode", str, strLength);
let clipboardValue = ""; let clipboardValue = "";
if (str.value) { if (str.value) {
str.value.QueryInterface(Ci.nsISupportsString); str.value.QueryInterface(Ci.nsISupportsString);
clipboardValue = str.value.data; clipboardValue = str.value.data;
} }
is(clipboardValue, testText, "Data was copied to the clipboard."); is(clipboardValue, testText, "Data was copied to the clipboard.");
});
}); });
add_task(function* asyncCleanup() { registerCleanupFunction(function cleanup() {
// clear the clipboard
Services.clipboard.emptyClipboard(globalClipboard); Services.clipboard.emptyClipboard(globalClipboard);
info("Clipboard was cleared");
// restore the tab as it was at the begining of the test
gBrowser.addTab(initialLocation);
gBrowser.removeTab(gBrowser.selectedTab);
info("Tabs were restored");
}); });

View File

@ -8,55 +8,50 @@ var initialLocation = gBrowser.currentURI.spec;
var globalClipboard; var globalClipboard;
add_task(function*() { add_task(function*() {
info("Check cut button existence and functionality"); yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
info("Check cut button existence and functionality");
let testText = "cut text test"; let testText = "cut text test";
gURLBar.focus(); gURLBar.focus();
yield PanelUI.show(); yield PanelUI.show();
info("Menu panel was opened"); info("Menu panel was opened");
let cutButton = document.getElementById("cut-button"); let cutButton = document.getElementById("cut-button");
ok(cutButton, "Cut button exists in Panel Menu"); ok(cutButton, "Cut button exists in Panel Menu");
ok(cutButton.hasAttribute("disabled"), "Cut button is disabled"); ok(cutButton.hasAttribute("disabled"), "Cut button is disabled");
// cut text from URL bar // cut text from URL bar
gURLBar.value = testText; gURLBar.value = testText;
gURLBar.focus(); gURLBar.focus();
gURLBar.select(); gURLBar.select();
yield PanelUI.show(); yield PanelUI.show();
info("Menu panel was opened"); info("Menu panel was opened");
ok(!cutButton.hasAttribute("disabled"), "Cut button is enabled when selecting"); ok(!cutButton.hasAttribute("disabled"), "Cut button is enabled when selecting");
cutButton.click(); cutButton.click();
is(gURLBar.value, "", "Selected text is removed from source when clicking on cut"); is(gURLBar.value, "", "Selected text is removed from source when clicking on cut");
// check that the text was added to the clipboard // check that the text was added to the clipboard
let clipboard = Services.clipboard; let clipboard = Services.clipboard;
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable); let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
globalClipboard = clipboard.kGlobalClipboard; globalClipboard = clipboard.kGlobalClipboard;
transferable.init(null); transferable.init(null);
transferable.addDataFlavor("text/unicode"); transferable.addDataFlavor("text/unicode");
clipboard.getData(transferable, globalClipboard); clipboard.getData(transferable, globalClipboard);
let str = {}, strLength = {}; let str = {}, strLength = {};
transferable.getTransferData("text/unicode", str, strLength); transferable.getTransferData("text/unicode", str, strLength);
let clipboardValue = ""; let clipboardValue = "";
if (str.value) { if (str.value) {
str.value.QueryInterface(Ci.nsISupportsString); str.value.QueryInterface(Ci.nsISupportsString);
clipboardValue = str.value.data; clipboardValue = str.value.data;
} }
is(clipboardValue, testText, "Data was copied to the clipboard."); is(clipboardValue, testText, "Data was copied to the clipboard.");
});
}); });
add_task(function* asyncCleanup() { registerCleanupFunction(function cleanup() {
// clear the clipboard
Services.clipboard.emptyClipboard(globalClipboard); Services.clipboard.emptyClipboard(globalClipboard);
info("Clipboard was cleared");
// restore the tab as it was at the begining of the test
gBrowser.addTab(initialLocation);
gBrowser.removeTab(gBrowser.selectedTab);
info("Tabs were restored");
}); });

View File

@ -8,40 +8,34 @@ var initialLocation = gBrowser.currentURI.spec;
var globalClipboard; var globalClipboard;
add_task(function*() { add_task(function*() {
info("Check paste button existence and functionality"); yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
info("Check paste button existence and functionality");
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
globalClipboard = Services.clipboard.kGlobalClipboard; globalClipboard = Services.clipboard.kGlobalClipboard;
yield PanelUI.show(); yield PanelUI.show();
info("Menu panel was opened"); info("Menu panel was opened");
let pasteButton = document.getElementById("paste-button"); let pasteButton = document.getElementById("paste-button");
ok(pasteButton, "Paste button exists in Panel Menu"); ok(pasteButton, "Paste button exists in Panel Menu");
// add text to clipboard // add text to clipboard
let text = "Sample text for testing"; let text = "Sample text for testing";
clipboard.copyString(text); clipboard.copyString(text);
// test paste button by pasting text to URL bar // test paste button by pasting text to URL bar
gURLBar.focus(); gURLBar.focus();
yield PanelUI.show(); yield PanelUI.show();
info("Menu panel was opened"); info("Menu panel was opened");
ok(!pasteButton.hasAttribute("disabled"), "Paste button is enabled"); ok(!pasteButton.hasAttribute("disabled"), "Paste button is enabled");
pasteButton.click(); pasteButton.click();
is(gURLBar.value, text, "Text pasted successfully"); is(gURLBar.value, text, "Text pasted successfully");
});
}); });
add_task(function* asyncCleanup() { registerCleanupFunction(function cleanup() {
// clear the clipboard
Services.clipboard.emptyClipboard(globalClipboard); Services.clipboard.emptyClipboard(globalClipboard);
info("Clipboard was cleared");
// restore the tab as it was at the begining of the test
gBrowser.addTab(initialLocation);
gBrowser.removeTab(gBrowser.selectedTab);
info("Tabs were restored");
}); });

View File

@ -157,10 +157,11 @@ const Utils = {
// We also reject handlers registered from a different host (see bug 402287) // We also reject handlers registered from a different host (see bug 402287)
// The pref allows us to test the feature // The pref allows us to test the feature
let pb = Services.prefs; let pb = Services.prefs;
if ((!pb.prefHasUserValue(PREF_ALLOW_DIFFERENT_HOST) || if (!pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST) &&
!pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST)) && (!["http:", "https:"].includes(aContentWindow.location.protocol) ||
aContentWindow.location.hostname != uri.host) aContentWindow.location.hostname != uri.host)) {
throw("Permission denied to add " + uri.spec + " as a content or protocol handler"); throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
}
// If the uri doesn't contain '%s', it won't be a good handler // If the uri doesn't contain '%s', it won't be a good handler
if (uri.spec.indexOf("%s") < 0) if (uri.spec.indexOf("%s") < 0)
@ -413,7 +414,7 @@ WebContentConverterRegistrar.prototype = {
let notificationValue = "Protocol Registration: " + aProtocol; let notificationValue = "Protocol Registration: " + aProtocol;
let addButton = { let addButton = {
label: this._getString("addProtocolHandlerAddButton"), label: this._getString("addProtocolHandlerAddButton"),
accessKey: this._getString("addHandlerAddButtonAccesskey"), accessKey: this._getString("addProtocolHandlerAddButtonAccesskey"),
protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle }, protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
callback(aNotification, aButtonInfo) { callback(aNotification, aButtonInfo) {

View File

@ -103,6 +103,9 @@ let gOpenDBs = new Map();
// Track open libraries // Track open libraries
let gLibs = {}; let gLibs = {};
this.ESE = ESE; // Required for tests.
this.KERNEL = KERNEL; // ditto
this.gLibs = gLibs; // ditto
function convertESEError(errorCode) { function convertESEError(errorCode) {
switch (errorCode) { switch (errorCode) {
@ -285,7 +288,10 @@ ESEDB.prototype = {
ESE.SetSystemParameterW(this._instanceId.address(), 0, ESE.SetSystemParameterW(this._instanceId.address(), 0,
2 /* JET_paramLogFilePath*/, 0, this.logPath); 2 /* JET_paramLogFilePath*/, 0, this.logPath);
// Shouldn't try to call JetTerm if the following call fails.
this._instanceCreated = false;
ESE.Init(this._instanceId.address()); ESE.Init(this._instanceId.address());
this._instanceCreated = true;
this._sessionId = new ESE.JET_SESID(); this._sessionId = new ESE.JET_SESID();
ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null, ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null,
null); null);

View File

@ -52,15 +52,17 @@ XPCOMUtils.defineLazyGetter(this, "gEdgeDatabase", function() {
* @param {function} filterFn a function that is called for each row. * @param {function} filterFn a function that is called for each row.
* Only rows for which it returns a truthy * Only rows for which it returns a truthy
* value are included in the result. * value are included in the result.
* @param {nsIFile} dbFile the database file to use. Defaults to
* the main Edge database.
* @returns {Array} An array of row objects. * @returns {Array} An array of row objects.
*/ */
function readTableFromEdgeDB(tableName, columns, filterFn) { function readTableFromEdgeDB(tableName, columns, filterFn, dbFile=gEdgeDatabase) {
let database; let database;
let rows = []; let rows = [];
try { try {
let logFile = gEdgeDatabase.parent; let logFile = dbFile.parent;
logFile.append("LogFiles"); logFile.append("LogFiles");
database = ESEDBReader.openDB(gEdgeDatabase.parent, gEdgeDatabase, logFile); database = ESEDBReader.openDB(dbFile.parent, dbFile, logFile);
if (typeof columns == "function") { if (typeof columns == "function") {
columns = columns(database); columns = columns(database);
@ -74,7 +76,7 @@ function readTableFromEdgeDB(tableName, columns, filterFn) {
} }
} catch (ex) { } catch (ex) {
Cu.reportError("Failed to extract items from table " + tableName + " in Edge database at " + Cu.reportError("Failed to extract items from table " + tableName + " in Edge database at " +
gEdgeDatabase.path + " due to the following error: " + ex); dbFile.path + " due to the following error: " + ex);
// Deliberately make this fail so we expose failure in the UI: // Deliberately make this fail so we expose failure in the UI:
throw ex; throw ex;
} finally { } finally {
@ -221,33 +223,36 @@ EdgeReadingListMigrator.prototype = {
}), }),
}; };
function EdgeBookmarksMigrator() { function EdgeBookmarksMigrator(dbOverride) {
this.dbOverride = dbOverride;
} }
EdgeBookmarksMigrator.prototype = { EdgeBookmarksMigrator.prototype = {
type: MigrationUtils.resourceTypes.BOOKMARKS, type: MigrationUtils.resourceTypes.BOOKMARKS,
get db() { return this.dbOverride || gEdgeDatabase; },
get TABLE_NAME() { return "Favorites" }, get TABLE_NAME() { return "Favorites" },
get exists() { get exists() {
if ("_exists" in this) { if ("_exists" in this) {
return this._exists; return this._exists;
} }
return this._exists = (!!gEdgeDatabase && this._checkTableExists()); return this._exists = (!!this.db && this._checkTableExists());
}, },
_checkTableExists() { _checkTableExists() {
let database; let database;
let rv; let rv;
try { try {
let logFile = gEdgeDatabase.parent; let logFile = this.db.parent;
logFile.append("LogFiles"); logFile.append("LogFiles");
database = ESEDBReader.openDB(gEdgeDatabase.parent, gEdgeDatabase, logFile); database = ESEDBReader.openDB(this.db.parent, this.db, logFile);
rv = database.tableExists(this.TABLE_NAME); rv = database.tableExists(this.TABLE_NAME);
} catch (ex) { } catch (ex) {
Cu.reportError("Failed to check for table " + tableName + " in Edge database at " + Cu.reportError("Failed to check for table " + this.TABLE_NAME + " in Edge database at " +
gEdgeDatabase.path + " due to the following error: " + ex); this.db.path + " due to the following error: " + ex);
return false; return false;
} finally { } finally {
if (database) { if (database) {
@ -348,7 +353,7 @@ EdgeBookmarksMigrator.prototype = {
} }
return true; return true;
} }
let bookmarks = readTableFromEdgeDB(this.TABLE_NAME, columns, filterFn); let bookmarks = readTableFromEdgeDB(this.TABLE_NAME, columns, filterFn, this.db);
return {bookmarks, folderMap}; return {bookmarks, folderMap};
}, },
@ -388,10 +393,15 @@ EdgeBookmarksMigrator.prototype = {
} }
function EdgeProfileMigrator() { function EdgeProfileMigrator() {
this.wrappedJSObject = this;
} }
EdgeProfileMigrator.prototype = Object.create(MigratorPrototype); EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
EdgeProfileMigrator.prototype.getESEMigratorForTesting = function(dbOverride) {
return new EdgeBookmarksMigrator(dbOverride);
};
EdgeProfileMigrator.prototype.getResources = function() { EdgeProfileMigrator.prototype.getResources = function() {
let bookmarksMigrator = new EdgeBookmarksMigrator(); let bookmarksMigrator = new EdgeBookmarksMigrator();
if (!bookmarksMigrator.exists) { if (!bookmarksMigrator.exists) {

View File

@ -0,0 +1,465 @@
"use strict";
Cu.import("resource://gre/modules/ctypes.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let eseBackStage = Cu.import("resource:///modules/ESEDBReader.jsm");
let ESE = eseBackStage.ESE;
let KERNEL = eseBackStage.KERNEL;
let gLibs = eseBackStage.gLibs;
let COLUMN_TYPES = eseBackStage.COLUMN_TYPES;
let declareESEFunction = eseBackStage.declareESEFunction;
let loadLibraries = eseBackStage.loadLibraries;
let gESEInstanceCounter = 1;
ESE.JET_COLUMNCREATE_W = new ctypes.StructType("JET_COLUMNCREATE_W", [
{"cbStruct": ctypes.unsigned_long},
{"szColumnName": ESE.JET_PCWSTR},
{"coltyp": ESE.JET_COLTYP },
{"cbMax": ctypes.unsigned_long },
{"grbit": ESE.JET_GRBIT },
{"pvDefault": ctypes.voidptr_t},
{"cbDefault": ctypes.unsigned_long },
{"cp": ctypes.unsigned_long },
{"columnid": ESE.JET_COLUMNID},
{"err": ESE.JET_ERR},
]);
function createColumnCreationWrapper({name, type, cbMax}) {
// We use a wrapper object because we need to be sure the JS engine won't GC
// data that we're "only" pointing to.
let wrapper = {};
wrapper.column = new ESE.JET_COLUMNCREATE_W();
wrapper.column.cbStruct = ESE.JET_COLUMNCREATE_W.size;
let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
wrapper.name = new wchar_tArray(name.length + 1);
wrapper.name.value = String(name);
wrapper.column.szColumnName = wrapper.name;
wrapper.column.coltyp = type;
let fallback = 0;
switch (type) {
case COLUMN_TYPES.JET_coltypText:
fallback = 255;
case COLUMN_TYPES.JET_coltypLongText:
wrapper.column.cbMax = cbMax || fallback || 64 * 1024;
break;
case COLUMN_TYPES.JET_coltypGUID:
wrapper.column.cbMax = 16;
break;
case COLUMN_TYPES.JET_coltypBit:
wrapper.column.cbMax = 1;
break;
case COLUMN_TYPES.JET_coltypLongLong:
wrapper.column.cbMax = 8;
break;
default:
throw "Unknown column type!";
}
wrapper.column.columnid = new ESE.JET_COLUMNID();
wrapper.column.grbit = 0;
wrapper.column.pvDefault = null;
wrapper.column.cbDefault = 0;
wrapper.column.cp = 0;
return wrapper;
}
// "forward declarations" of indexcreate and setinfo structs, which we don't use.
ESE.JET_INDEXCREATE = new ctypes.StructType("JET_INDEXCREATE");
ESE.JET_SETINFO = new ctypes.StructType("JET_SETINFO");
ESE.JET_TABLECREATE_W = new ctypes.StructType("JET_TABLECREATE_W", [
{"cbStruct": ctypes.unsigned_long},
{"szTableName": ESE.JET_PCWSTR},
{"szTemplateTableName": ESE.JET_PCWSTR},
{"ulPages": ctypes.unsigned_long},
{"ulDensity": ctypes.unsigned_long},
{"rgcolumncreate": ESE.JET_COLUMNCREATE_W.ptr},
{"cColumns": ctypes.unsigned_long},
{"rgindexcreate": ESE.JET_INDEXCREATE.ptr},
{"cIndexes": ctypes.unsigned_long},
{"grbit": ESE.JET_GRBIT},
{"tableid": ESE.JET_TABLEID},
{"cCreated": ctypes.unsigned_long},
]);
function createTableCreationWrapper(tableName, columns) {
let wrapper = {};
let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
wrapper.name = new wchar_tArray(tableName.length + 1);
wrapper.name.value = String(tableName);
wrapper.table = new ESE.JET_TABLECREATE_W();
wrapper.table.cbStruct = ESE.JET_TABLECREATE_W.size;
wrapper.table.szTableName = wrapper.name;
wrapper.table.szTemplateTableName = null;
wrapper.table.ulPages = 1;
wrapper.table.ulDensity = 0;
let columnArrayType = ESE.JET_COLUMNCREATE_W.array(columns.length);
wrapper.columnAry = new columnArrayType();
wrapper.table.rgcolumncreate = wrapper.columnAry.addressOfElement(0);
wrapper.table.cColumns = columns.length;
wrapper.columns = [];
for (let i = 0; i < columns.length; i++) {
let column = columns[i];
let columnWrapper = createColumnCreationWrapper(column);
wrapper.columnAry.addressOfElement(i).contents = columnWrapper.column;
wrapper.columns.push(columnWrapper);
}
wrapper.table.rgindexcreate = null;
wrapper.table.cIndexes = 0;
return wrapper;
}
function convertValueForWriting(value, valueType) {
let buffer;
let valueOfValueType = ctypes.UInt64.lo(valueType);
switch (valueOfValueType) {
case COLUMN_TYPES.JET_coltypLongLong:
if (value instanceof Date) {
buffer = new KERNEL.FILETIME();
let sysTime = new KERNEL.SYSTEMTIME();
sysTime.wYear = value.getUTCFullYear();
sysTime.wMonth = value.getUTCMonth() + 1;
sysTime.wDay = value.getUTCDate();
sysTime.wHour = value.getUTCHours();
sysTime.wMinute = value.getUTCMinutes();
sysTime.wSecond = value.getUTCSeconds();
sysTime.wMilliseconds = value.getUTCMilliseconds();
let rv = KERNEL.SystemTimeToFileTime(sysTime.address(), buffer.address());
if (!rv) {
throw new Error("Failed to get FileTime.");
}
return [buffer, KERNEL.FILETIME.size];
}
throw new Error("Unrecognized value for longlong column");
case COLUMN_TYPES.JET_coltypLongText:
let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
buffer = new wchar_tArray(value.length + 1);
buffer.value = String(value);
return [buffer, buffer.length * 2];
case COLUMN_TYPES.JET_coltypBit:
buffer = new ctypes.uint8_t();
// Bizarre boolean values, but whatever:
buffer.value = value ? 255 : 0;
return [buffer, 1];
case COLUMN_TYPES.JET_coltypGUID:
let byteArray = ctypes.ArrayType(ctypes.uint8_t);
buffer = new byteArray(16);
let j = 0;
for (let i = 0; i < value.length; i++) {
if (!(/[0-9a-f]/i).test(value[i])) {
continue;
}
let byteAsHex = value.substr(i, 2);
buffer[j++] = parseInt(byteAsHex, 16);
i++;
}
return [buffer, 16];
}
throw new Error("Unknown type " + valueType);
}
let initializedESE = false;
let eseDBWritingHelpers = {
setupDB(dbFile, tableName, columns, rows) {
if (!initializedESE) {
initializedESE = true;
loadLibraries();
KERNEL.SystemTimeToFileTime = gLibs.kernel.declare("SystemTimeToFileTime",
ctypes.default_abi, ctypes.bool, KERNEL.SYSTEMTIME.ptr, KERNEL.FILETIME.ptr);
declareESEFunction("CreateDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT);
declareESEFunction("CreateTableColumnIndexW", ESE.JET_SESID, ESE.JET_DBID,
ESE.JET_TABLECREATE_W.ptr);
declareESEFunction("BeginTransaction", ESE.JET_SESID);
declareESEFunction("CommitTransaction", ESE.JET_SESID, ESE.JET_GRBIT);
declareESEFunction("PrepareUpdate", ESE.JET_SESID, ESE.JET_TABLEID,
ctypes.unsigned_long);
declareESEFunction("Update", ESE.JET_SESID, ESE.JET_TABLEID,
ctypes.voidptr_t, ctypes.unsigned_long,
ctypes.unsigned_long.ptr);
declareESEFunction("SetColumn", ESE.JET_SESID, ESE.JET_TABLEID,
ESE.JET_COLUMNID, ctypes.voidptr_t,
ctypes.unsigned_long, ESE.JET_GRBIT, ESE.JET_SETINFO.ptr);
ESE.SetSystemParameterW(null, 0, 64 /* JET_paramDatabasePageSize*/,
8192, null);
}
let rootPath = dbFile.parent.path + "\\";
let logPath = rootPath + "LogFiles\\";
try {
this._instanceId = new ESE.JET_INSTANCE();
ESE.CreateInstanceW(this._instanceId.address(),
"firefox-dbwriter-" + (gESEInstanceCounter++));
this._instanceCreated = true;
ESE.SetSystemParameterW(this._instanceId.address(), 0,
0 /* JET_paramSystemPath*/, 0, rootPath);
ESE.SetSystemParameterW(this._instanceId.address(), 0,
1 /* JET_paramTempPath */, 0, rootPath);
ESE.SetSystemParameterW(this._instanceId.address(), 0,
2 /* JET_paramLogFilePath*/, 0, logPath);
// Shouldn't try to call JetTerm if the following call fails.
this._instanceCreated = false;
ESE.Init(this._instanceId.address());
this._instanceCreated = true;
this._sessionId = new ESE.JET_SESID();
ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null,
null);
this._sessionCreated = true;
this._dbId = new ESE.JET_DBID();
this._dbPath = rootPath + "spartan.edb";
ESE.CreateDatabaseW(this._sessionId, this._dbPath, null,
this._dbId.address(), 0);
this._opened = this._attached = true;
let tableCreationWrapper = createTableCreationWrapper(tableName, columns);
ESE.CreateTableColumnIndexW(this._sessionId, this._dbId,
tableCreationWrapper.table.address());
this._tableId = tableCreationWrapper.table.tableid;
let columnIdMap = new Map();
if (rows.length) {
// Iterate over the struct we passed into ESENT because they have the
// created column ids.
let columnCount = ctypes.UInt64.lo(tableCreationWrapper.table.cColumns);
let columnsPassed = tableCreationWrapper.table.rgcolumncreate;
for (let i = 0; i < columnCount; i++) {
let column = columnsPassed.contents;
columnIdMap.set(column.szColumnName.readString(), column);
columnsPassed = columnsPassed.increment();
}
let rv = ESE.ManualMove(this._sessionId, this._tableId,
-2147483648 /* JET_MoveFirst */, 0);
ESE.BeginTransaction(this._sessionId);
for (let row of rows) {
ESE.PrepareUpdate(this._sessionId, this._tableId, 0 /* JET_prepInsert */);
for (let columnName in row) {
let col = columnIdMap.get(columnName);
let colId = col.columnid;
let [val, valSize] = convertValueForWriting(row[columnName], col.coltyp);
/* JET_bitSetOverwriteLV */
ESE.SetColumn(this._sessionId, this._tableId, colId, val.address(), valSize, 4, null);
}
let actualBookmarkSize = new ctypes.unsigned_long();
ESE.Update(this._sessionId, this._tableId, null, 0, actualBookmarkSize.address());
}
ESE.CommitTransaction(this._sessionId, 0 /* JET_bitWaitLastLevel0Commit */);
}
} finally {
try {
this._close();
} catch (ex) {
Cu.reportError(ex);
}
}
},
_close() {
if (this._tableId) {
ESE.FailSafeCloseTable(this._sessionId, this._tableId);
delete this._tableId;
}
if (this._opened) {
ESE.FailSafeCloseDatabase(this._sessionId, this._dbId, 0);
this._opened = false;
}
if (this._attached) {
ESE.FailSafeDetachDatabaseW(this._sessionId, this._dbPath);
this._attached = false;
}
if (this._sessionCreated) {
ESE.FailSafeEndSession(this._sessionId, 0);
this._sessionCreated = false;
}
if (this._instanceCreated) {
ESE.FailSafeTerm(this._instanceId);
this._instanceCreated = false;
}
},
};
add_task(function*() {
let tempFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
tempFile.append("fx-xpcshell-edge-db");
tempFile.createUnique(tempFile.DIRECTORY_TYPE, 0o600);
let db = tempFile.clone();
db.append("spartan.edb");
let logs = tempFile.clone();
logs.append("LogFiles");
logs.create(tempFile.DIRECTORY_TYPE, 0o600);
let creationDate = new Date(Date.now() - 5000);
const kEdgeMenuParent = "62d07e2b-5f0d-4e41-8426-5f5ec9717beb";
let itemsInDB = [
{
URL: "http://www.mozilla.org/",
Title: "Mozilla",
DateUpdated: new Date(creationDate.valueOf() + 100),
ItemId: "1c00c10a-15f6-4618-92dd-22575102a4da",
ParentId: kEdgeMenuParent,
IsFolder: false,
IsDeleted: false,
},
{
Title: "Folder",
DateUpdated: new Date(creationDate.valueOf() + 200),
ItemId: "564b21f2-05d6-4f7d-8499-304d00ccc3aa",
ParentId: kEdgeMenuParent,
IsFolder: true,
IsDeleted: false,
},
{
Title: "Item in folder",
URL: "http://www.iteminfolder.org/",
DateUpdated: new Date(creationDate.valueOf() + 300),
ItemId: "c295ddaf-04a1-424a-866c-0ebde011e7c8",
ParentId: "564b21f2-05d6-4f7d-8499-304d00ccc3aa",
IsFolder: false,
IsDeleted: false,
},
{
Title: "Deleted folder",
DateUpdated: new Date(creationDate.valueOf() + 400),
ItemId: "a547573c-4d4d-4406-a736-5b5462d93bca",
ParentId: kEdgeMenuParent,
IsFolder: true,
IsDeleted: true,
},
{
Title: "Deleted item",
URL: "http://www.deleteditem.org/",
DateUpdated: new Date(creationDate.valueOf() + 500),
ItemId: "37a574bb-b44b-4bbc-a414-908615536435",
ParentId: kEdgeMenuParent,
IsFolder: false,
IsDeleted: true,
},
{
Title: "Item in deleted folder (should be in root)",
URL: "http://www.itemindeletedfolder.org/",
DateUpdated: new Date(creationDate.valueOf() + 600),
ItemId: "74dd1cc3-4c5d-471f-bccc-7bc7c72fa621",
ParentId: "a547573c-4d4d-4406-a736-5b5462d93bca",
IsFolder: false,
IsDeleted: false,
},
{
Title: "_Favorites_Bar_",
DateUpdated: new Date(creationDate.valueOf() + 700),
ItemId: "921dc8a0-6c83-40ef-8df1-9bd1c5c56aaf",
ParentId: kEdgeMenuParent,
IsFolder: true,
IsDeleted: false,
},
{
Title: "Item in favorites bar",
URL: "http://www.iteminfavoritesbar.org/",
DateUpdated: new Date(creationDate.valueOf() + 800),
ItemId: "9f2b1ff8-b651-46cf-8f41-16da8bcb6791",
ParentId: "921dc8a0-6c83-40ef-8df1-9bd1c5c56aaf",
IsFolder: false,
IsDeleted: false,
},
];
eseDBWritingHelpers.setupDB(db, "Favorites", [
{type: COLUMN_TYPES.JET_coltypLongText, name: "URL", cbMax: 4096},
{type: COLUMN_TYPES.JET_coltypLongText, name: "Title", cbMax: 4096},
{type: COLUMN_TYPES.JET_coltypLongLong, name: "DateUpdated"},
{type: COLUMN_TYPES.JET_coltypGUID, name: "ItemId"},
{type: COLUMN_TYPES.JET_coltypBit, name: "IsDeleted"},
{type: COLUMN_TYPES.JET_coltypBit, name: "IsFolder"},
{type: COLUMN_TYPES.JET_coltypGUID, name: "ParentId"},
], itemsInDB);
let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=edge"]
.createInstance(Ci.nsIBrowserProfileMigrator);
let bookmarksMigrator = migrator.wrappedJSObject.getESEMigratorForTesting(db);
Assert.ok(bookmarksMigrator.exists, "Should recognize table we just created");
let seenBookmarks = [];
let bookmarkObserver = {
onItemAdded(itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid) {
if (title.startsWith("Deleted")) {
ok(false, "Should not see deleted items being bookmarked!");
}
seenBookmarks.push({itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid});
},
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onItemRemoved() {},
onItemChanged() {},
onItemVisited() {},
onItemMoved() {},
}
PlacesUtils.bookmarks.addObserver(bookmarkObserver, false);
let migrateResult = yield new Promise(resolve => bookmarksMigrator.migrate(resolve)).catch(ex => {
Cu.reportError(ex);
Assert.ok(false, "Got an exception trying to migrate data! " + ex);
return false;
});
PlacesUtils.bookmarks.removeObserver(bookmarkObserver);
Assert.ok(migrateResult, "Migration should succeed");
Assert.equal(seenBookmarks.length, 7, "Should have seen 7 items being bookmarked.");
let menuParents = seenBookmarks.filter(item => item.parentGuid == PlacesUtils.bookmarks.menuGuid);
Assert.equal(menuParents.length, 1, "Should have a single folder added to the menu");
let toolbarParents = seenBookmarks.filter(item => item.parentGuid == PlacesUtils.bookmarks.toolbarGuid);
Assert.equal(toolbarParents.length, 1, "Should have a single item added to the toolbar");
let menuParentGuid = menuParents[0].itemGuid;
let toolbarParentGuid = toolbarParents[0].itemGuid;
let expectedTitlesInMenu = itemsInDB.filter(item => item.ParentId == kEdgeMenuParent).map(item => item.Title);
// Hacky, but seems like much the simplest way:
expectedTitlesInMenu.push("Item in deleted folder (should be in root)");
let expectedTitlesInToolbar = itemsInDB.filter(item => item.ParentId == "921dc8a0-6c83-40ef-8df1-9bd1c5c56aaf").map(item => item.Title);
let edgeNameStr = MigrationUtils.getLocalizedString("sourceNameEdge");
let importParentFolderName = MigrationUtils.getLocalizedString("importedBookmarksFolder", [edgeNameStr]);
for (let bookmark of seenBookmarks) {
let shouldBeInMenu = expectedTitlesInMenu.includes(bookmark.title);
let shouldBeInToolbar = expectedTitlesInToolbar.includes(bookmark.title);
if (bookmark.title == "Folder" || bookmark.title == importParentFolderName) {
Assert.equal(bookmark.itemType, PlacesUtils.bookmarks.TYPE_FOLDER,
"Bookmark " + bookmark.title + " should be a folder");
} else {
Assert.notEqual(bookmark.itemType, PlacesUtils.bookmarks.TYPE_FOLDER,
"Bookmark " + bookmark.title + " should not be a folder");
}
if (shouldBeInMenu) {
Assert.equal(bookmark.parentGuid, menuParentGuid, "Item '" + bookmark.title + "' should be in menu");
} else if (shouldBeInToolbar) {
Assert.equal(bookmark.parentGuid, toolbarParentGuid, "Item '" + bookmark.title + "' should be in toolbar");
} else if (bookmark.itemGuid == menuParentGuid || bookmark.itemGuid == toolbarParentGuid) {
Assert.ok(true, "Expect toolbar and menu folders to not be in menu or toolbar");
} else {
// Bit hacky, but we do need to check this.
Assert.equal(bookmark.title, "Item in folder", "Subfoldered item shouldn't be in menu or toolbar");
let parent = seenBookmarks.find(maybeParent => maybeParent.itemGuid == bookmark.parentGuid);
Assert.equal(parent && parent.title, "Folder", "Subfoldered item should be in subfolder labeled 'Folder'");
}
let dbItem = itemsInDB.find(dbItem => bookmark.title == dbItem.Title);
if (!dbItem) {
Assert.equal(bookmark.title, importParentFolderName, "Only the extra layer of folders isn't in the input we stuck in the DB.");
Assert.ok([menuParentGuid, toolbarParentGuid].includes(bookmark.itemGuid), "This item should be one of the containers");
} else {
Assert.equal(dbItem.URL || null, bookmark.url && bookmark.url.spec, "URL is correct");
Assert.equal(dbItem.DateUpdated.valueOf(), (new Date(bookmark.dateAdded / 1000)).valueOf(), "Date added is correct");
}
}
});

View File

@ -12,6 +12,8 @@ skip-if = os != "mac" # Relies on ULibDir
[test_Chrome_passwords.js] [test_Chrome_passwords.js]
skip-if = os != "win" skip-if = os != "win"
[test_Edge_availability.js] [test_Edge_availability.js]
[test_Edge_db_migration.js]
skip-if = os != "win" || os_version == "5.1" || os_version == "5.2" # Relies on post-XP bits of ESEDB
[test_fx_telemetry.js] [test_fx_telemetry.js]
[test_IE_bookmarks.js] [test_IE_bookmarks.js]
skip-if = os != "win" skip-if = os != "win"

View File

@ -271,9 +271,7 @@ var gSyncPane = {
gSyncPane.signIn(); gSyncPane.signIn();
return false; return false;
}); });
setEventListener("verifiedManage", "click", setEventListener("fxaUnlinkButton", "command", function () {
gSyncPane.manageFirefoxAccount);
setEventListener("fxaUnlinkButton", "click", function () {
gSyncPane.unlinkFirefoxAccount(true); gSyncPane.unlinkFirefoxAccount(true);
}); });
setEventListener("verifyFxaAccount", "command", setEventListener("verifyFxaAccount", "command",
@ -550,13 +548,34 @@ var gSyncPane = {
this._openAboutAccounts("reauth"); this._openAboutAccounts("reauth");
}, },
openChangeProfileImage: function() {
fxAccounts.promiseAccountsChangeProfileURI(this._getEntryPoint(), "avatar") clickOrSpaceOrEnterPressed: function(event) {
.then(url => { // Note: charCode is deprecated, but 'char' not yet implemented.
// Replace charCode with char when implemented, see Bug 680830
if ((event.type == "click" && event.button == 0) || // button 0 = 'main button', typically left click.
(event.type == "keypress" &&
(event.charCode == KeyEvent.DOM_VK_SPACE || event.keyCode == KeyEvent.DOM_VK_RETURN))) {
return true;
} else {
return false;
}
},
openChangeProfileImage: function(event) {
if (this.clickOrSpaceOrEnterPressed(event)) {
fxAccounts.promiseAccountsChangeProfileURI(this._getEntryPoint(), "avatar")
.then(url => {
this.openContentInBrowser(url, { this.openContentInBrowser(url, {
replaceQueryString: true replaceQueryString: true
}); });
}); });
}
},
openManageFirefoxAccount: function(event) {
if (this.clickOrSpaceOrEnterPressed(event)) {
this.manageFirefoxAccount();
}
}, },
manageFirefoxAccount: function() { manageFirefoxAccount: function() {

View File

@ -199,8 +199,8 @@
<vbox flex="1"> <vbox flex="1">
<label id="signedOutAccountBoxTitle">&signedOut.accountBox.title;</label> <label id="signedOutAccountBoxTitle">&signedOut.accountBox.title;</label>
<hbox class="fxaAccountBoxButtons" align="center"> <hbox class="fxaAccountBoxButtons" align="center">
<button id="noFxaSignUp">&signedOut.accountBox.create;</button> <button id="noFxaSignUp" label="&signedOut.accountBox.create;"></button>
<button id="noFxaSignIn">&signedOut.accountBox.signin;</button> <button id="noFxaSignIn" label="&signedOut.accountBox.signin;"></button>
</hbox> </hbox>
</vbox> </vbox>
</hbox> </hbox>
@ -234,16 +234,21 @@
<!-- logged in and verified and all is good --> <!-- logged in and verified and all is good -->
<hbox id="fxaLoginVerified" class="fxaAccountBox"> <hbox id="fxaLoginVerified" class="fxaAccountBox">
<vbox> <vbox>
<image id="fxaProfileImage" <image id="fxaProfileImage" class="actionable"
onclick="gSyncPane.openChangeProfileImage();" hidden="true" role="button"
tooltiptext="&profilePicture.tooltip;" class="actionable"/> onclick="gSyncPane.openChangeProfileImage(event);" hidden="true"
onkeypress="gSyncPane.openChangeProfileImage(event);"
tooltiptext="&profilePicture.tooltip;"/>
</vbox> </vbox>
<vbox flex="1"> <vbox flex="1">
<label id="fxaEmailAddress1"/> <label id="fxaEmailAddress1"/>
<label id="fxaDisplayName" hidden="true"/> <label id="fxaDisplayName" hidden="true"/>
<hbox class="fxaAccountBoxButtons" align="center"> <hbox class="fxaAccountBoxButtons" align="center">
<button id="fxaUnlinkButton">&disconnect.label;</button> <button id="fxaUnlinkButton" label="&disconnect.label;"/>
<label id="verifiedManage" class="text-link">&verifiedManage.label;</label> <label id="verifiedManage" class="text-link"
onclick="gSyncPane.openManageFirefoxAccount(event);"
onkeypress="gSyncPane.openManageFirefoxAccount(event);"><!--
-->&verifiedManage.label;</label>
</hbox> </hbox>
</vbox> </vbox>
</hbox> </hbox>

View File

@ -141,6 +141,10 @@ var SessionHistoryInternal = {
entry.referrerPolicy = shEntry.referrerPolicy; entry.referrerPolicy = shEntry.referrerPolicy;
} }
if (shEntry.originalURI) {
entry.originalURI = shEntry.originalURI.spec;
}
if (shEntry.srcdocData) if (shEntry.srcdocData)
entry.srcdocData = shEntry.srcdocData; entry.srcdocData = shEntry.srcdocData;
@ -309,6 +313,9 @@ var SessionHistoryInternal = {
shEntry.referrerURI = Utils.makeURI(entry.referrer); shEntry.referrerURI = Utils.makeURI(entry.referrer);
shEntry.referrerPolicy = entry.referrerPolicy; shEntry.referrerPolicy = entry.referrerPolicy;
} }
if (entry.originalURI) {
shEntry.originalURI = Utils.makeURI(entry.originalURI);
}
if (entry.isSrcdocEntry) if (entry.isSrcdocEntry)
shEntry.srcdocData = entry.srcdocData; shEntry.srcdocData = entry.srcdocData;
if (entry.baseURI) if (entry.baseURI)

View File

@ -11,6 +11,8 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/AppConstants.jsm"); Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
"resource://gre/modules/WindowsRegistry.jsm");
/** /**
* Internal functionality to save and restore the docShell.allow* properties. * Internal functionality to save and restore the docShell.allow* properties.
@ -54,11 +56,30 @@ let ShellServiceInternal = {
return false; return false;
} }
return Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"); if (!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser")) {
return false;
}
if (AppConstants.platform == "win") {
let optOutValue = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
"Software\\Mozilla\\Firefox",
"DefaultBrowserOptOut");
WindowsRegistry.removeRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
"Software\\Mozilla\\Firefox",
"DefaultBrowserOptOut");
if (optOutValue == "True") {
Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", false);
return false;
}
}
return true;
}, },
set shouldCheckDefaultBrowser(shouldCheck) { set shouldCheckDefaultBrowser(shouldCheck) {
Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", !!shouldCheck); Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", !!shouldCheck);
}, },
isDefaultBrowser(startupCheck, forAllTypes) { isDefaultBrowser(startupCheck, forAllTypes) {
// If this is the first browser window, maintain internal state that we've // If this is the first browser window, maintain internal state that we've
// checked this session (so that subsequent window opens don't show the // checked this session (so that subsequent window opens don't show the

View File

@ -20,6 +20,7 @@
<link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/syncedtabs/sidebar.css"/> <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/syncedtabs/sidebar.css"/>
<link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/"/> <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/"/>
<link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/textbox.css"/> <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/textbox.css"/>
<link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/browser.css"/>
<title>&syncedTabs.sidebar.label;</title> <title>&syncedTabs.sidebar.label;</title>
</head> </head>

View File

@ -234,8 +234,21 @@ if (typeof Mozilla == 'undefined') {
}); });
}; };
Mozilla.UITour.showFirefoxAccounts = function() { /**
_sendEvent('showFirefoxAccounts'); * Request the browser open the Firefox Accounts page.
*
* @param {Object} extraURLCampaignParams - An object containing additional
* paramaters for the URL opened by the browser for reasons of promotional
* campaign tracking. Each attribute of the object must have a name that
* begins with "utm_" and a value that is a string. Both the name and value
* must contain only alphanumeric characters, dashes or underscores (meaning
* that you are limited to values that don't need encoding, as any such
* characters in the name or value will be rejected.)
*/
Mozilla.UITour.showFirefoxAccounts = function(extraURLCampaignParams) {
_sendEvent('showFirefoxAccounts', {
extraURLCampaignParams: JSON.stringify(extraURLCampaignParams),
});
}; };
Mozilla.UITour.resetFirefox = function() { Mozilla.UITour.resetFirefox = function() {

View File

@ -610,8 +610,15 @@ this.UITour = {
case "showFirefoxAccounts": { case "showFirefoxAccounts": {
// 'signup' is the only action that makes sense currently, so we don't // 'signup' is the only action that makes sense currently, so we don't
// accept arbitrary actions just to be safe... // accept arbitrary actions just to be safe...
let p = new URLSearchParams("action=signup&entrypoint=uitour");
// Call our helper to validate extraURLCampaignParams and populate URLSearchParams
if (!this._populateCampaignParams(p, data.extraURLCampaignParams)) {
log.warn("showFirefoxAccounts: invalid campaign args specified");
return false;
}
// We want to replace the current tab. // We want to replace the current tab.
browser.loadURI("about:accounts?action=signup&entrypoint=uitour"); browser.loadURI("about:accounts?" + p.toString());
break; break;
} }
@ -805,6 +812,52 @@ this.UITour = {
} }
}, },
// Given a string that is a JSONified represenation of an object with
// additional utm_* URL params that should be appended, validate and append
// them to the passed URLSearchParams object. Returns true if the params
// were validated and appended, and false if the request should be ignored.
_populateCampaignParams: function(urlSearchParams, extraURLCampaignParams) {
// We are extra paranoid about what params we allow to be appended.
if (typeof extraURLCampaignParams == "undefined") {
// no params, so it's all good.
return true;
}
if (typeof extraURLCampaignParams != "string") {
log.warn("_populateCampaignParams: extraURLCampaignParams is not a string");
return false;
}
let campaignParams;
try {
if (extraURLCampaignParams) {
campaignParams = JSON.parse(extraURLCampaignParams);
if (typeof campaignParams != "object") {
log.warn("_populateCampaignParams: extraURLCampaignParams is not a stringified object");
return false;
}
}
} catch (ex) {
log.warn("_populateCampaignParams: extraURLCampaignParams is not a JSON object");
return false;
}
if (campaignParams) {
// The regex that both the name and value of each param must match.
let reSimpleString = /^[-_a-zA-Z0-9]*$/;
for (let name in campaignParams) {
let value = campaignParams[name];
if (typeof name != "string" || typeof value != "string" ||
!name.startsWith("utm_") ||
value.length == 0 ||
!reSimpleString.test(name) ||
!reSimpleString.test(value)) {
log.warn("_populateCampaignParams: invalid campaign param specified");
return false;
}
urlSearchParams.append(name, value);
}
}
return true;
},
setTelemetryBucket: function(aPageID) { setTelemetryBucket: function(aPageID) {
let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID; let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID;
BrowserUITelemetry.setBucket(bucket); BrowserUITelemetry.setBucket(bucket);
@ -1700,7 +1753,7 @@ this.UITour = {
// An event object is expected but we don't want to toggle the panel with a click if the panel // An event object is expected but we don't want to toggle the panel with a click if the panel
// is already open. // is already open.
aWindow.LoopUI.openCallPanel({ target: toolbarButton.node, }, "rooms").then(() => { aWindow.LoopUI.openPanel({ target: toolbarButton.node, }, "rooms").then(() => {
if (aOpenCallback) { if (aOpenCallback) {
aOpenCallback(); aOpenCallback();
} }

View File

@ -37,6 +37,7 @@ skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly
skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly. skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly.
[browser_UITour_loop.js] [browser_UITour_loop.js]
skip-if = true # Bug 1225832 - New Loop architecture is not compatible with test. skip-if = true # Bug 1225832 - New Loop architecture is not compatible with test.
[browser_UITour_loop_panel.js]
[browser_UITour_modalDialog.js] [browser_UITour_modalDialog.js]
skip-if = os != "mac" || e10s # modal dialog disabling only working on OS X. Bug 1240747 - UITour.jsm not e10s friendly skip-if = os != "mac" || e10s # modal dialog disabling only working on OS X. Bug 1240747 - UITour.jsm not e10s friendly
[browser_UITour_observe.js] [browser_UITour_observe.js]

View File

@ -112,29 +112,6 @@ var tests = [
checkLoopPanelIsHidden(); checkLoopPanelIsHidden();
}), }),
taskify(function* test_menu_show_hide() {
// The targets to highlight only appear after getting started is launched.
// Set latestFTUVersion to lower number to show FTU panel.
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
is(loopButton.open, false, "Menu should initially be closed");
gContentAPI.showMenu("loop");
yield waitForConditionPromise(() => {
return loopButton.open;
}, "Menu should be visible after showMenu()");
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should be on the loop panel");
ok(loopPanel.hasAttribute("panelopen"), "The panel should have @panelopen");
is(loopPanel.state, "open", "The panel should be open");
ok(loopButton.hasAttribute("open"), "Loop button should know that the menu is open");
gContentAPI.hideMenu("loop");
yield waitForConditionPromise(() => {
return !loopButton.open;
}, "Menu should be hidden after hideMenu()");
checkLoopPanelIsHidden();
}),
// Test the menu was cleaned up in teardown. // Test the menu was cleaned up in teardown.
taskify(function* setup_menu_cleanup() { taskify(function* setup_menu_cleanup() {
gContentAPI.showMenu("loop"); gContentAPI.showMenu("loop");

View File

@ -0,0 +1,68 @@
"use strict";
var gTestTab;
var gContentAPI;
var gContentWindow;
var gMessageHandlers;
var loopButton;
var fakeRoom;
var loopPanel = document.getElementById("loop-notification-panel");
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {});
if (!Services.prefs.getBoolPref("loop.enabled")) {
ok(true, "Loop is disabled so skip the UITour Loop tests");
} else {
function checkLoopPanelIsHidden() {
ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
isnot(loopPanel.state, "open", "The panel shouldn't be open");
is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
}
add_task(setup_UITourTest);
add_task(function() {
loopButton = window.LoopUI.toolbarButton.node;
registerCleanupFunction(() => {
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
Services.io.offline = false;
// Copied from browser/components/loop/test/mochitest/head.js
// Remove the iframe after each test. This also avoids mochitest complaining
// about leaks on shutdown as we intentionally hold the iframe open for the
// life of the application.
let frameId = loopButton.getAttribute("notificationFrameId");
let frame = document.getElementById(frameId);
if (frame) {
frame.remove();
}
});
});
add_UITour_task(function* test_menu_show_hide() {
// The targets to highlight only appear after getting started is launched.
// Set latestFTUVersion to lower number to show FTU panel.
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
is(loopButton.open, false, "Menu should initially be closed");
gContentAPI.showMenu("loop");
yield waitForConditionPromise(() => {
return loopButton.open;
}, "Menu should be visible after showMenu()");
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should be on the loop panel");
ok(loopPanel.hasAttribute("panelopen"), "The panel should have @panelopen");
is(loopPanel.state, "open", "The panel should be open");
ok(loopButton.hasAttribute("open"), "Loop button should know that the menu is open");
gContentAPI.hideMenu("loop");
yield waitForConditionPromise(() => {
return !loopButton.open;
}, "Menu should be hidden after hideMenu()");
checkLoopPanelIsHidden();
});
}

View File

@ -22,8 +22,50 @@ add_UITour_task(function* test_checkSyncSetup_enabled() {
}); });
// The showFirefoxAccounts API is sync related, so we test that here too... // The showFirefoxAccounts API is sync related, so we test that here too...
add_UITour_task(function* test_firefoxAccounts() { add_UITour_task(function* test_firefoxAccountsNoParams() {
yield gContentAPI.showFirefoxAccounts(); yield gContentAPI.showFirefoxAccounts();
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false, yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
"about:accounts?action=signup&entrypoint=uitour"); "about:accounts?action=signup&entrypoint=uitour");
}); });
add_UITour_task(function* test_firefoxAccountsValidParams() {
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", utm_bar: "bar" });
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
"about:accounts?action=signup&entrypoint=uitour&utm_foo=foo&utm_bar=bar");
});
// A helper to check the request was ignored due to invalid params.
function* checkAboutAccountsNotLoaded() {
try {
yield waitForConditionPromise(() => {
return gBrowser.selectedBrowser.currentURI.spec.startsWith("about:accounts");
}, "Check if about:accounts opened");
ok(false, "No about:accounts tab should have opened");
} catch (ex) {
ok(true, "No about:accounts tab opened");
}
}
add_UITour_task(function* test_firefoxAccountsNonObject() {
// non-string should be rejected.
yield gContentAPI.showFirefoxAccounts(99);
yield checkAboutAccountsNotLoaded();
});
add_UITour_task(function* test_firefoxAccountsNonUtmPrefix() {
// Any non "utm_" name should should be rejected.
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", bar: "bar" });
yield checkAboutAccountsNotLoaded();
});
add_UITour_task(function* test_firefoxAccountsNonAlphaName() {
// Any "utm_" name which includes non-alpha chars should be rejected.
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", "utm_bar=": "bar" });
yield checkAboutAccountsNotLoaded();
});
add_UITour_task(function* test_firefoxAccountsNonAlphaValue() {
// Any non-alpha value should be rejected.
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo&" });
yield checkAboutAccountsNotLoaded();
});

View File

@ -147,13 +147,14 @@ function hideInfoPromise(...args) {
*/ */
function showInfoPromise(target, title, text, icon, buttonsFunctionName, optionsFunctionName) { function showInfoPromise(target, title, text, icon, buttonsFunctionName, optionsFunctionName) {
let popup = document.getElementById("UITourTooltip"); let popup = document.getElementById("UITourTooltip");
let shownPromise = promisePanelElementShown(window, popup);
return ContentTask.spawn(gTestTab.linkedBrowser, [...arguments], args => { return ContentTask.spawn(gTestTab.linkedBrowser, [...arguments], args => {
let contentWin = Components.utils.waiveXrays(content); let contentWin = Components.utils.waiveXrays(content);
let [target, title, text, icon, buttonsFunctionName, optionsFunctionName] = args; let [target, title, text, icon, buttonsFunctionName, optionsFunctionName] = args;
let buttons = buttonsFunctionName ? contentWin[buttonsFunctionName]() : null; let buttons = buttonsFunctionName ? contentWin[buttonsFunctionName]() : null;
let options = optionsFunctionName ? contentWin[optionsFunctionName]() : null; let options = optionsFunctionName ? contentWin[optionsFunctionName]() : null;
contentWin.Mozilla.UITour.showInfo(target, title, text, icon, buttons, options); contentWin.Mozilla.UITour.showInfo(target, title, text, icon, buttons, options);
}).then(() => promisePanelElementShown(window, popup)); }).then(() => shownPromise);
} }
function showHighlightPromise(...args) { function showHighlightPromise(...args) {
@ -193,7 +194,7 @@ function promisePanelElementEvent(win, aPanel, aEvent) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let timeoutId = win.setTimeout(() => { let timeoutId = win.setTimeout(() => {
aPanel.removeEventListener(aEvent, onPanelEvent); aPanel.removeEventListener(aEvent, onPanelEvent);
reject("Event did not happen within 5 seconds."); reject(aEvent + " event did not happen within 5 seconds.");
}, 5000); }, 5000);
function onPanelEvent(e) { function onPanelEvent(e) {

View File

@ -28,6 +28,7 @@ LOOPDIR=browser/extensions/loop
TESTS=" TESTS="
${LOOPDIR}/chrome/test/mochitest ${LOOPDIR}/chrome/test/mochitest
browser/components/uitour/test/browser_UITour_loop_panel.js
browser/base/content/test/general/browser_devices_get_user_media_about_urls.js browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
browser/base/content/test/general/browser_parsable_css.js browser/base/content/test/general/browser_parsable_css.js
" "

View File

@ -357,7 +357,7 @@ var PocketOverlay = {
PocketContextMenu.init(); PocketContextMenu.init();
PocketReader.startup(); PocketReader.startup();
if (reason == ADDON_ENABLE) { if (reason != APP_STARTUP) {
for (let win of allBrowserWindows()) { for (let win of allBrowserWindows()) {
this.setWindowScripts(win); this.setWindowScripts(win);
this.addStyles(win); this.addStyles(win);
@ -529,7 +529,7 @@ function startup(data, reason) {
function shutdown(data, reason) { function shutdown(data, reason) {
// For speed sake, we should only do a shutdown if we're being disabled. // For speed sake, we should only do a shutdown if we're being disabled.
// On an app shutdown, just let it fade away... // On an app shutdown, just let it fade away...
if (reason == ADDON_DISABLE) { if (reason != APP_SHUTDOWN) {
Services.prefs.removeObserver("extensions.pocket.enabled", prefObserver); Services.prefs.removeObserver("extensions.pocket.enabled", prefObserver);
PocketOverlay.shutdown(reason); PocketOverlay.shutdown(reason);
} }

View File

@ -605,6 +605,12 @@ Section "-InstallEndCleanup"
GetFunctionAddress $0 SetAsDefaultAppUserHKCU GetFunctionAddress $0 SetAsDefaultAppUserHKCU
UAC::ExecCodeSegment $0 UAC::ExecCodeSegment $0
${EndIf} ${EndIf}
${Else}
${LogHeader} "Writing default-browser opt-out"
WriteRegStr HKCU "Software\Mozilla\Firefox" "DefaultBrowserOptOut" "True"
${If} ${Errors}
${LogHeader} "Error writing default-browser opt-out"
${EndIf}
${EndIf} ${EndIf}
${EndUnless} ${EndUnless}

View File

@ -744,7 +744,6 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY spellAddDictionaries.accesskey "A"> <!ENTITY spellAddDictionaries.accesskey "A">
<!ENTITY editBookmark.done.label "Done"> <!ENTITY editBookmark.done.label "Done">
<!ENTITY editBookmark.cancel.label "Cancel">
<!ENTITY editBookmark.removeBookmark.accessKey "R"> <!ENTITY editBookmark.removeBookmark.accessKey "R">
<!ENTITY identity.connectionSecure "Secure Connection"> <!ENTITY identity.connectionSecure "Secure Connection">

View File

@ -49,3 +49,4 @@ feedSubscriptionVideoPodcast2=You can subscribe to this video podcast to receive
# "Add %appName (%appDomain) as an application for %protocolType links?" # "Add %appName (%appDomain) as an application for %protocolType links?"
addProtocolHandler=Add %S (%S) as an application for %S links? addProtocolHandler=Add %S (%S) as an application for %S links?
addProtocolHandlerAddButton=Add Application addProtocolHandlerAddButton=Add Application
addProtocolHandlerAddButtonAccesskey=A

View File

@ -134,6 +134,7 @@ XPCOMUtils.defineLazyGetter(this, "ALL_BUILTIN_ITEMS", function() {
"BMB_unsortedBookmarksPopup", "BMB_unsortedBookmarksPopup",
"BMB_bookmarksToolbarPopup", "BMB_bookmarksToolbarPopup",
"search-go-button", "search-go-button",
"soundplaying-icon",
] ]
return DEFAULT_ITEMS.concat(PALETTE_ITEMS) return DEFAULT_ITEMS.concat(PALETTE_ITEMS)
.concat(SPECIAL_CASES); .concat(SPECIAL_CASES);
@ -610,6 +611,10 @@ this.BrowserUITelemetry = {
this._countEvent(["forget-button", timeId]); this._countEvent(["forget-button", timeId]);
}, },
countTabMutingEvent: function(action, reason) {
this._countEvent(["tab-audio-control", action, reason || "no reason given"]);
},
_logAwesomeBarSearchResult: function (url) { _logAwesomeBarSearchResult: function (url) {
let spec = Services.search.parseSubmissionURL(url); let spec = Services.search.parseSubmissionURL(url);
if (spec.engine) { if (spec.engine) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -25,6 +25,10 @@
margin-top: 2px !important; margin-top: 2px !important;
} }
#fxaProfileImage {
-moz-user-focus: normal;
}
menulist.actionsMenu > .menulist-dropmarker { menulist.actionsMenu > .menulist-dropmarker {
margin-top: 11px; margin-top: 11px;
margin-bottom: 11px; margin-bottom: 11px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -19,6 +19,10 @@ prefpane .groupbox-title {
-moz-padding-start: 3px; -moz-padding-start: 3px;
} }
#fxaProfileImage {
-moz-user-focus: normal;
}
textbox + button { textbox + button {
-moz-margin-start: -4px; -moz-margin-start: -4px;
} }

View File

@ -63,6 +63,10 @@
-moz-image-region: rect(0px, 1024px, 32px, 992px); -moz-image-region: rect(0px, 1024px, 32px, 992px);
} }
#sync-button[cui-areatype="menu-panel"][panel-multiview-anchor=true] {
-moz-image-region: rect(32px, 1024px, 64px, 992px);
}
#feed-button[cui-areatype="menu-panel"], #feed-button[cui-areatype="menu-panel"],
toolbarpaletteitem[place="palette"] > #feed-button { toolbarpaletteitem[place="palette"] > #feed-button {
-moz-image-region: rect(0px, 416px, 32px, 384px); -moz-image-region: rect(0px, 416px, 32px, 384px);
@ -263,6 +267,10 @@
-moz-image-region: rect(0px, 2048px, 64px, 1984px); -moz-image-region: rect(0px, 2048px, 64px, 1984px);
} }
#sync-button[cui-areatype="menu-panel"][panel-multiview-anchor=true] {
-moz-image-region: rect(64px, 2048px, 128px, 1984px);
}
#feed-button[cui-areatype="menu-panel"], #feed-button[cui-areatype="menu-panel"],
toolbarpaletteitem[place="palette"] > #feed-button { toolbarpaletteitem[place="palette"] > #feed-button {
-moz-image-region: rect(0px, 832px, 64px, 768px); -moz-image-region: rect(0px, 832px, 64px, 768px);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -51,6 +51,10 @@ filefield + button {
padding-bottom: 0; /* override padding from normal preferences.css */ padding-bottom: 0; /* override padding from normal preferences.css */
} }
#fxaProfileImage {
-moz-user-focus: normal;
}
/** /**
* Dialog * Dialog
*/ */

View File

@ -27,6 +27,8 @@ If you would like to use a different directory, hit CTRL+c and set the
MOZBUILD_STATE_PATH environment variable to the directory you would like to MOZBUILD_STATE_PATH environment variable to the directory you would like to
use and re-run mach. For this change to take effect forever, you'll likely use and re-run mach. For this change to take effect forever, you'll likely
want to export this environment variable from your shell's init scripts. want to export this environment variable from your shell's init scripts.
Press ENTER/RETURN to continue or CTRL+c to abort.
'''.lstrip() '''.lstrip()
NO_MERCURIAL_SETUP = ''' NO_MERCURIAL_SETUP = '''
@ -254,8 +256,26 @@ def bootstrap(topsrcdir, mozilla_dir=None):
raise raise
# Add common metadata to help submit sorted data later on. # Add common metadata to help submit sorted data later on.
# For now, we'll just record the mach command that was invoked.
data['argv'] = sys.argv data['argv'] = sys.argv
data.setdefault('system', {}).update(dict(
architecture=list(platform.architecture()),
machine=platform.machine(),
python_version=platform.python_version(),
release=platform.release(),
system=platform.system(),
version=platform.version(),
))
if platform.system() == 'Linux':
dist = list(platform.linux_distribution())
data['system']['linux_distribution'] = dist
elif platform.system() == 'Windows':
win32_ver=list((platform.win32_ver())),
data['system']['win32_ver'] = win32_ver
elif platform.system() == 'Darwin':
# mac version is a special Cupertino snowflake
r, v, m = platform.mac_ver()
data['system']['mac_ver'] = [r, list(v), m]
with open(os.path.join(outgoing_dir, str(uuid.uuid4()) + '.json'), with open(os.path.join(outgoing_dir, str(uuid.uuid4()) + '.json'),
'w') as f: 'w') as f:
@ -383,25 +403,18 @@ def bootstrap(topsrcdir, mozilla_dir=None):
if is_environ: if is_environ:
if not os.path.exists(state_dir): if not os.path.exists(state_dir):
print('Creating global state directory from environment variable: %s' print('Creating global state directory from environment variable: %s'
% state_dir) % state_dir)
os.makedirs(state_dir, mode=0o770) os.makedirs(state_dir, mode=0o770)
print('Please re-run mach.')
sys.exit(1)
else: else:
if not os.path.exists(state_dir): if not os.path.exists(state_dir):
print(STATE_DIR_FIRST_RUN.format(userdir=state_dir)) print(STATE_DIR_FIRST_RUN.format(userdir=state_dir))
try: try:
for i in range(20, -1, -1): sys.stdin.readline()
time.sleep(1)
sys.stdout.write('%d ' % i)
sys.stdout.flush()
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit(1) sys.exit(1)
print('\nCreating default state directory: %s' % state_dir) print('\nCreating default state directory: %s' % state_dir)
os.mkdir(state_dir) os.makedirs(state_dir, mode=0o770)
print('Please re-run mach.')
sys.exit(1)
return state_dir return state_dir

View File

@ -0,0 +1,24 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"argv": {"type": "array"},
"system": {
"type": "object",
"properties": {
"architecture": {"type": "array"},
"linux_distribution": {"type": "array"},
"mac_ver": {"type": "array"},
"machine": {"type": "string"},
"python_version": {"type": "string"},
"release": {"type": "string"},
"system": {"type": "string"},
"version": {"type": "string"},
"win_ver": {"type": "array"}
},
"required": ["architecture", "machine", "python_version",
"release", "system", "version"]
}
},
"required": ["argv", "system"]
}

View File

@ -53,7 +53,7 @@ dnl ========================================================
MOZJPEG=62 MOZJPEG=62
MOZPNG=10619 MOZPNG=10619
NSPR_VERSION=4 NSPR_VERSION=4
NSPR_MINVER=4.11 NSPR_MINVER=4.12
NSS_VERSION=3 NSS_VERSION=3
dnl Set the minimum version of toolkit libs used by mozilla dnl Set the minimum version of toolkit libs used by mozilla
@ -3851,18 +3851,6 @@ MOZ_ARG_WITH_STRING(gcm-senderid-keyfile,
MOZ_ANDROID_GCM_SENDERID=`cat $withval`) MOZ_ANDROID_GCM_SENDERID=`cat $withval`)
AC_SUBST(MOZ_ANDROID_GCM_SENDERID) AC_SUBST(MOZ_ANDROID_GCM_SENDERID)
# Whether to include optional-but-large font files in the final APK.
# We want this in mobile/android/confvars.sh, so it goes early.
MOZ_ARG_DISABLE_BOOL(android-include-fonts,
[ --disable-android-include-fonts
Disable the inclusion of fonts into the final APK],
MOZ_ANDROID_EXCLUDE_FONTS=1)
if test -n "$MOZ_ANDROID_EXCLUDE_FONTS"; then
AC_DEFINE(MOZ_ANDROID_EXCLUDE_FONTS)
fi
AC_SUBST(MOZ_ANDROID_EXCLUDE_FONTS)
# Whether this APK is destined for resource constrained devices. # Whether this APK is destined for resource constrained devices.
# We want this in mobile/android/confvars.sh, so it goes early. # We want this in mobile/android/confvars.sh, so it goes early.
MOZ_ARG_ENABLE_BOOL(android-resource-constrained, MOZ_ARG_ENABLE_BOOL(android-resource-constrained,
@ -4775,6 +4763,13 @@ if test -n "$MOZ_ANDROID_MLS_STUMBLER"; then
AC_DEFINE(MOZ_ANDROID_MLS_STUMBLER) AC_DEFINE(MOZ_ANDROID_MLS_STUMBLER)
fi fi
dnl =========================================================
dnl = Whether to exclude font files in the build
dnl =========================================================
if test -n "$MOZ_ANDROID_EXCLUDE_FONTS"; then
AC_DEFINE(MOZ_ANDROID_EXCLUDE_FONTS)
fi
dnl ========================================================= dnl =========================================================
dnl = Whether to exclude hyphenations files in the build dnl = Whether to exclude hyphenations files in the build
dnl ========================================================= dnl =========================================================
@ -8457,6 +8452,7 @@ AC_SUBST(MOZ_ANDROID_APPLICATION_CLASS)
AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS) AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS)
AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS) AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS)
AC_SUBST(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) AC_SUBST(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
AC_SUBST(MOZ_ANDROID_EXCLUDE_FONTS)
AC_SUBST(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES) AC_SUBST(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
AC_SUBST(MOZ_INSTALL_TRACKING) AC_SUBST(MOZ_INSTALL_TRACKING)
AC_SUBST(MOZ_SWITCHBOARD) AC_SUBST(MOZ_SWITCHBOARD)
@ -8562,10 +8558,12 @@ AC_SUBST(MOZ_CHILD_PROCESS_BUNDLE)
# "Profile" field, which controls profile location. # "Profile" field, which controls profile location.
# - MOZ_APP_ID: When set, used for application.ini's "ID" field, and # - MOZ_APP_ID: When set, used for application.ini's "ID" field, and
# crash reporter server url. # crash reporter server url.
# - MOZ_APP_ANDROID_VERSION_CODE: On android, "android:versionCode" for # - MOZ_APP_ANDROID_VERSION_CODE: On Android, "android:versionCode" for
# the main application is set to the value of this variable. If not # the main application is set to the value of this variable. If not
# set, it falls back to a Mozilla-specific value derived from the # set, it falls back to a Mozilla-specific value derived from the
# build ID. # build ID.
# - MOZ_ANDROID_SHARED_ID: On Android, "android:sharedUserId" for all Android
# - packages produced.
# - MOZ_PROFILE_MIGRATOR: When set, enables profile migrator. # - MOZ_PROFILE_MIGRATOR: When set, enables profile migrator.
if test -z "$MOZ_APP_NAME"; then if test -z "$MOZ_APP_NAME"; then
@ -8584,6 +8582,14 @@ if test -z "$ANDROID_PACKAGE_NAME" ; then
ANDROID_PACKAGE_NAME="org.mozilla.$MOZ_APP_NAME" ANDROID_PACKAGE_NAME="org.mozilla.$MOZ_APP_NAME"
fi fi
# Mozilla released Firefox for Android {Release,Beta} and {Aurora,Nightly} to
# the public with specific common shared IDs and we need to keep them
# consistent forever. The specific common values are set by per-channel
# branding; all other channels use a generic sharedID, set below.
if test -z "$MOZ_ANDROID_SHARED_ID" ; then
MOZ_ANDROID_SHARED_ID="${ANDROID_PACKAGE_NAME}.sharedID"
fi
# For extensions and langpacks, we require a max version that is compatible # For extensions and langpacks, we require a max version that is compatible
# across security releases. MOZ_APP_MAXVERSION is our method for doing that. # across security releases. MOZ_APP_MAXVERSION is our method for doing that.
# 24.0a1 and 24.0a2 aren't affected # 24.0a1 and 24.0a2 aren't affected
@ -8614,6 +8620,7 @@ AC_SUBST(MOZ_APP_VENDOR)
AC_SUBST(MOZ_APP_PROFILE) AC_SUBST(MOZ_APP_PROFILE)
AC_SUBST(MOZ_APP_ID) AC_SUBST(MOZ_APP_ID)
AC_SUBST(MOZ_APP_ANDROID_VERSION_CODE) AC_SUBST(MOZ_APP_ANDROID_VERSION_CODE)
AC_SUBST(MOZ_ANDROID_SHARED_ID)
AC_SUBST(MAR_CHANNEL_ID) AC_SUBST(MAR_CHANNEL_ID)
AC_SUBST(ACCEPTED_MAR_CHANNEL_IDS) AC_SUBST(ACCEPTED_MAR_CHANNEL_IDS)
AC_SUBST(MOZ_PROFILE_MIGRATOR) AC_SUBST(MOZ_PROFILE_MIGRATOR)

View File

@ -265,8 +265,8 @@
// Disallow unreachable statements after a return, throw, continue, or break // Disallow unreachable statements after a return, throw, continue, or break
// statement. // statement.
"no-unreachable": 2, "no-unreachable": 2,
// Disallow declaration of variables that are not used in the code // Disallow global and local variables that aren't used, but allow unused function arguments.
"no-unused-vars": 2, "no-unused-vars": [2, {"vars": "all", "args": "none"}],
// Allow using variables before they are defined. // Allow using variables before they are defined.
"no-use-before-define": 0, "no-use-before-define": 0,
// We use var-only-at-top-level instead of no-var as we allow top level // We use var-only-at-top-level instead of no-var as we allow top level

View File

@ -16,11 +16,6 @@ button {
padding-right: 20px; padding-right: 20px;
} }
#body {
display: flex;
flex-direction: row;
}
/* Category tabs */ /* Category tabs */
.category { .category {
@ -33,6 +28,13 @@ button {
cursor: default; cursor: default;
} }
.app {
height: 100%;
width: 100%;
display: flex;
flex-direction: row;
}
.main-content { .main-content {
flex: 1; flex: 1;
} }
@ -41,17 +43,6 @@ button {
max-width: 800px; max-width: 800px;
} }
.tab:not(.active) {
display: none;
}
/* Prefs */
label {
display: block;
margin-bottom: 5px;
}
/* Targets */ /* Targets */
.targets { .targets {
@ -82,11 +73,16 @@ label {
flex: 1; flex: 1;
} }
.addon-controls { .addons-controls {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.addon-options { .addons-options {
flex: 1; flex: 1;
} }
.addons-debugging-label {
display: inline-block;
margin: 0 5px 5px 0;
}

View File

@ -1,179 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env browser */
/* global AddonsComponent, DebuggerClient, DebuggerServer, React,
WorkersComponent */
"use strict";
const { classes: Cc, interfaces: Ci } = Components;
const { loader } = Components.utils.import(
"resource://devtools/shared/Loader.jsm", {});
loader.lazyRequireGetter(this, "AddonsComponent",
"devtools/client/aboutdebugging/components/addons", true);
loader.lazyRequireGetter(this, "DebuggerClient",
"devtools/shared/client/main", true);
loader.lazyRequireGetter(this, "DebuggerServer",
"devtools/server/main", true);
loader.lazyRequireGetter(this, "Telemetry",
"devtools/client/shared/telemetry");
loader.lazyRequireGetter(this, "WorkersComponent",
"devtools/client/aboutdebugging/components/workers", true);
loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
var AboutDebugging = {
_prefListeners: [],
// Pointer to the current React component.
_component: null,
_categories: null,
get categories() {
// If needed, initialize the list of available categories.
if (!this._categories) {
let elements = document.querySelectorAll(".category");
this._categories = Array.map(elements, element => {
let value = element.getAttribute("value");
element.addEventListener("click", this.showTab.bind(this, value));
return value;
});
}
return this._categories;
},
showTab(category) {
// If no category was specified, try the URL hash.
if (!category) {
category = location.hash.substr(1);
}
// If no corresponding category can be found, use the first available.
let categories = this.categories;
if (categories.indexOf(category) < 0) {
category = categories[0];
}
// Show the corresponding tab and hide the others.
document.querySelector(".tab.active").classList.remove("active");
document.querySelector("#tab-" + category).classList.add("active");
document.querySelector(".category[selected]").removeAttribute("selected");
document.querySelector(".category[value=" + category + "]")
.setAttribute("selected", "true");
location.hash = "#" + category;
if (category == "addons") {
this._component = React.render(React.createElement(AddonsComponent,
{client: this.client}), document.querySelector("#addons"));
} else if (category == "workers") {
this._component = React.render(React.createElement(WorkersComponent,
{client: this.client}), document.querySelector("#workers"));
}
},
init() {
let telemetry = this._telemetry = new Telemetry();
telemetry.toolOpened("aboutdebugging");
// Link checkboxes to prefs.
let elements = document.querySelectorAll("input[type=checkbox][data-pref]");
Array.map(elements, element => {
let pref = element.dataset.pref;
let updatePref = () => {
Services.prefs.setBoolPref(pref, element.checked);
};
element.addEventListener("change", updatePref, false);
let onPreferenceChanged = () => {
element.checked = Services.prefs.getBoolPref(pref);
this.update();
};
Services.prefs.addObserver(pref, onPreferenceChanged, false);
this._prefListeners.push([pref, onPreferenceChanged]);
// Initialize the current checkbox element.
element.checked = Services.prefs.getBoolPref(pref);
});
// Link buttons to their associated actions.
let loadAddonButton = document.getElementById("load-addon-from-file");
loadAddonButton.addEventListener("click", this.loadAddonFromFile);
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
DebuggerServer.allowChromeProcess = true;
this.client = new DebuggerClient(DebuggerServer.connectPipe());
this.client.connect().then(() => {
// Show the first available tab.
this.showTab();
window.addEventListener("hashchange", () => this.showTab());
});
},
update() {
if (this._component) {
this._component.setState({});
}
},
loadAddonFromFile() {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window,
Strings.GetStringFromName("selectAddonFromFile"),
Ci.nsIFilePicker.modeOpen);
let res = fp.show();
if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
return;
}
let file = fp.file;
// AddonManager.installTemporaryAddon accepts either
// addon directory or final xpi file.
if (!file.isDirectory() && !file.leafName.endsWith(".xpi")) {
file = file.parent;
}
try {
AddonManager.installTemporaryAddon(file);
} catch (e) {
alert("Error while installing the addon:\n" + e.message + "\n");
throw e;
}
},
destroy() {
let telemetry = this._telemetry;
telemetry.toolClosed("aboutdebugging");
telemetry.destroy();
this._prefListeners.forEach(([pref, listener]) => {
Services.prefs.removeObserver(pref, listener);
});
this._prefListeners = [];
React.unmountComponentAtNode(document.querySelector("#addons"));
React.unmountComponentAtNode(document.querySelector("#workers"));
this.client.close();
this.client = null;
},
};
window.addEventListener("DOMContentLoaded", function load() {
window.removeEventListener("DOMContentLoaded", load);
AboutDebugging.init();
});
window.addEventListener("unload", function unload() {
window.removeEventListener("unload", unload);
AboutDebugging.destroy();
});

View File

@ -16,39 +16,8 @@
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/> <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/content/aboutdebugging/aboutdebugging.css" type="text/css"/> <link rel="stylesheet" href="chrome://devtools/content/aboutdebugging/aboutdebugging.css" type="text/css"/>
<script type="application/javascript" src="resource://devtools/client/shared/vendor/react.js"></script> <script type="application/javascript" src="resource://devtools/client/shared/vendor/react.js"></script>
<script type="application/javascript;version=1.8" src="chrome://devtools/content/aboutdebugging/aboutdebugging.js"></script> <script type="application/javascript;version=1.8" src="chrome://devtools/content/aboutdebugging/initializer.js"></script>
</head> </head>
<body id="body"> <body id="body">
<div id="categories">
<div class="category" value="addons" selected="true">
<img class="category-icon" src="chrome://devtools/skin/images/debugging-addons.svg"/>
<div class="category-name">&aboutDebugging.addons;</div>
</div>
<div class="category" value="workers">
<img class="category-icon" src="chrome://devtools/skin/images/debugging-workers.svg"/>
<div class="category-name">&aboutDebugging.workers;</div>
</div>
</div>
<div class="main-content">
<div id="tab-addons" class="tab active">
<div class="header">
<h1 class="header-name">&aboutDebugging.addons;</h1>
</div>
<div class="addon-controls">
<div class="addon-options">
<input id="enable-addon-debugging" type="checkbox" data-pref="devtools.chrome.enabled"/>
<label for="enable-addon-debugging" title="&aboutDebugging.addonDebugging.tooltip;">&aboutDebugging.addonDebugging.label;</label>
</div>
<button id="load-addon-from-file">&aboutDebugging.loadTemporaryAddon;</button>
</div>
<div id="addons"></div>
</div>
<div id="tab-workers" class="tab">
<div class="header">
<h1 class="header-name">&aboutDebugging.workers;</h1>
</div>
<div id="workers"></div>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,84 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global React */
"use strict";
loader.lazyRequireGetter(this, "React",
"devtools/client/shared/vendor/react");
loader.lazyRequireGetter(this, "AddonsTab",
"devtools/client/aboutdebugging/components/addons-tab", true);
loader.lazyRequireGetter(this, "TabMenu",
"devtools/client/aboutdebugging/components/tab-menu", true);
loader.lazyRequireGetter(this, "WorkersTab",
"devtools/client/aboutdebugging/components/workers-tab", true);
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
const tabs = [
{ id: "addons", name: Strings.GetStringFromName("addons"),
icon: "chrome://devtools/skin/images/debugging-addons.svg",
component: AddonsTab },
{ id: "workers", name: Strings.GetStringFromName("workers"),
icon: "chrome://devtools/skin/images/debugging-workers.svg",
component: WorkersTab },
];
const defaultTabId = "addons";
exports.AboutDebuggingApp = React.createClass({
displayName: "AboutDebuggingApp",
getInitialState() {
return {
selectedTabId: defaultTabId
};
},
componentDidMount() {
this.props.window.addEventListener("hashchange", this.onHashChange);
this.onHashChange();
this.props.telemetry.toolOpened("aboutdebugging");
},
componentWillUnmount() {
this.props.window.removeEventListener("hashchange", this.onHashChange);
this.props.telemetry.toolClosed("aboutdebugging");
this.props.telemetry.destroy();
},
render() {
let { client } = this.props;
let { selectedTabId } = this.state;
let selectTab = this.selectTab;
let selectedTab = tabs.find(t => t.id == selectedTabId);
return React.createElement(
"div", { className: "app"},
React.createElement(TabMenu, { tabs, selectedTabId, selectTab }),
React.createElement("div", { className: "main-content" },
React.createElement(selectedTab.component, { client }))
);
},
onHashChange() {
let tabId = this.props.window.location.hash.substr(1);
let isValid = tabs.some(t => t.id == tabId);
if (isValid) {
this.setState({ selectedTabId: tabId });
} else {
// If the current hash matches no valid category, navigate to the default
// tab.
this.selectTab(defaultTabId);
}
},
selectTab(tabId) {
let win = this.props.window;
win.location.hash = "#" + tabId;
}
});

View File

@ -0,0 +1,85 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global React */
"use strict";
loader.lazyRequireGetter(this, "Ci", "chrome", true);
loader.lazyRequireGetter(this, "Cc", "chrome", true);
loader.lazyRequireGetter(this, "React", "devtools/client/shared/vendor/react");
loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" +
"/about:debugging#Enabling_add-on_debugging";
exports.AddonsControls = React.createClass({
displayName: "AddonsControls",
render() {
let { debugDisabled } = this.props;
return React.createElement(
"div", { className: "addons-controls" }, React.createElement(
"div", { className: "addons-options" },
React.createElement("input", {
id: "enable-addon-debugging",
type: "checkbox",
checked: !debugDisabled,
onChange: this.onEnableAddonDebuggingChange,
}),
React.createElement("label", {
className: "addons-debugging-label",
htmlFor: "enable-addon-debugging",
title: Strings.GetStringFromName("addonDebugging.tooltip")
}, Strings.GetStringFromName("addonDebugging.label")),
"(",
React.createElement("a", { href: MORE_INFO_URL, target: "_blank" },
Strings.GetStringFromName("addonDebugging.moreInfo")),
")"
),
React.createElement("button", {
id: "load-addon-from-file",
onClick: this.loadAddonFromFile,
}, Strings.GetStringFromName("loadTemporaryAddon"))
);
},
onEnableAddonDebuggingChange(event) {
let enabled = event.target.checked;
Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
},
loadAddonFromFile(event) {
let win = event.target.ownerDocument.defaultView;
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(win,
Strings.GetStringFromName("selectAddonFromFile"),
Ci.nsIFilePicker.modeOpen);
let res = fp.show();
if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
return;
}
let file = fp.file;
// AddonManager.installTemporaryAddon accepts either
// addon directory or final xpi file.
if (!file.isDirectory() && !file.leafName.endsWith(".xpi")) {
file = file.parent;
}
try {
AddonManager.installTemporaryAddon(file);
} catch (e) {
win.alert("Error while installing the addon:\n" + e.message + "\n");
throw e;
}
},
});

View File

@ -0,0 +1,128 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global AddonManager, React */
"use strict";
loader.lazyRequireGetter(this, "React",
"devtools/client/shared/vendor/react");
loader.lazyRequireGetter(this, "TargetList",
"devtools/client/aboutdebugging/components/target-list", true);
loader.lazyRequireGetter(this, "TabHeader",
"devtools/client/aboutdebugging/components/tab-header", true);
loader.lazyRequireGetter(this, "AddonsControls",
"devtools/client/aboutdebugging/components/addons-controls", true);
loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
exports.AddonsTab = React.createClass({
displayName: "AddonsTab",
getInitialState() {
return {
extensions: [],
debugDisabled: false,
};
},
componentDidMount() {
AddonManager.addAddonListener(this);
Services.prefs.addObserver(CHROME_ENABLED_PREF,
this.updateDebugStatus, false);
Services.prefs.addObserver(REMOTE_ENABLED_PREF,
this.updateDebugStatus, false);
this.updateDebugStatus();
this.updateAddonsList();
},
componentWillUnmount() {
AddonManager.removeAddonListener(this);
Services.prefs.removeObserver(CHROME_ENABLED_PREF,
this.updateDebugStatus);
Services.prefs.removeObserver(REMOTE_ENABLED_PREF,
this.updateDebugStatus);
},
render() {
let { client } = this.props;
let { debugDisabled, extensions: targets } = this.state;
let name = Strings.GetStringFromName("extensions");
return React.createElement(
"div", { id: "tab-addons", className: "tab", role: "tabpanel",
"aria-labelledby": "tab-addons-header-name" },
React.createElement(TabHeader, {
id: "tab-addons-header-name",
name: Strings.GetStringFromName("addons")}),
React.createElement(AddonsControls, { debugDisabled }),
React.createElement(
"div", { id: "addons" },
React.createElement(TargetList,
{ name, targets, client, debugDisabled })
)
);
},
updateDebugStatus() {
let debugDisabled =
!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
this.setState({ debugDisabled });
},
updateAddonsList() {
AddonManager.getAllAddons(addons => {
let extensions = addons.filter(addon => addon.isDebuggable).map(addon => {
return {
name: addon.name,
icon: addon.iconURL || ExtensionIcon,
type: addon.type,
addonID: addon.id
};
});
this.setState({ extensions });
});
},
/**
* Mandatory callback as AddonManager listener.
*/
onInstalled() {
this.updateAddonsList();
},
/**
* Mandatory callback as AddonManager listener.
*/
onUninstalled() {
this.updateAddonsList();
},
/**
* Mandatory callback as AddonManager listener.
*/
onEnabled() {
this.updateAddonsList();
},
/**
* Mandatory callback as AddonManager listener.
*/
onDisabled() {
this.updateAddonsList();
},
});

View File

@ -1,80 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global AddonManager, React, TargetListComponent */
"use strict";
loader.lazyRequireGetter(this, "React",
"devtools/client/shared/vendor/react");
loader.lazyRequireGetter(this, "TargetListComponent",
"devtools/client/aboutdebugging/components/target-list", true);
loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
exports.AddonsComponent = React.createClass({
displayName: "AddonsComponent",
getInitialState() {
return {
extensions: []
};
},
componentDidMount() {
AddonManager.addAddonListener(this);
this.update();
},
componentWillUnmount() {
AddonManager.removeAddonListener(this);
},
render() {
let client = this.props.client;
let targets = this.state.extensions;
let name = Strings.GetStringFromName("extensions");
let debugDisabled = !Services.prefs.getBoolPref("devtools.chrome.enabled");
return React.createElement("div", null,
React.createElement(TargetListComponent,
{ name, targets, client, debugDisabled })
);
},
update() {
AddonManager.getAllAddons(addons => {
let extensions = addons.filter(addon => addon.isDebuggable).map(addon => {
return {
name: addon.name,
icon: addon.iconURL || ExtensionIcon,
type: addon.type,
addonID: addon.id
};
});
this.setState({ extensions });
});
},
onInstalled() {
this.update();
},
onUninstalled() {
this.update();
},
onEnabled() {
this.update();
},
onDisabled() {
this.update();
},
});

View File

@ -3,8 +3,13 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules( DevToolsModules(
'addons.js', 'aboutdebugging.js',
'addons-controls.js',
'addons-tab.js',
'tab-header.js',
'tab-menu-entry.js',
'tab-menu.js',
'target-list.js', 'target-list.js',
'target.js', 'target.js',
'workers.js', 'workers-tab.js',
) )

View File

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global React */
"use strict";
loader.lazyRequireGetter(this, "React",
"devtools/client/shared/vendor/react");
exports.TabHeader = React.createClass({
displayName: "TabHeader",
render() {
let { name, id } = this.props;
return React.createElement(
"div", { className: "header" }, React.createElement(
"h1", { id, className: "header-name" }, name));
},
});

View File

@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global React */
"use strict";
loader.lazyRequireGetter(this, "React",
"devtools/client/shared/vendor/react");
exports.TabMenuEntry = React.createClass({
displayName: "TabMenuEntry",
render() {
let { icon, name, selected } = this.props;
// Here .category, .category-icon, .category-name classnames are used to
// apply common styles defined.
let className = "category" + (selected ? " selected" : "");
return React.createElement(
"div", { className, onClick: this.onClick,
"aria-selected": selected, role: "tab" },
React.createElement("img", { className: "category-icon", src: icon,
role: "presentation" }),
React.createElement("div", { className: "category-name" }, name)
);
},
onClick() {
this.props.selectTab(this.props.tabId);
}
});

View File

@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global React */
"use strict";
loader.lazyRequireGetter(this, "React",
"devtools/client/shared/vendor/react");
loader.lazyRequireGetter(this, "TabMenuEntry",
"devtools/client/aboutdebugging/components/tab-menu-entry", true);
exports.TabMenu = React.createClass({
displayName: "TabMenu",
render() {
let { tabs, selectedTabId, selectTab } = this.props;
let tabLinks = tabs.map(({ id, name, icon }) => {
let selected = id == selectedTabId;
return React.createElement(TabMenuEntry,
{ tabId: id, name, icon, selected, selectTab });
});
// "categories" id used for styling purposes
return React.createElement("div", { id: "categories" }, tabLinks);
},
});

View File

@ -2,13 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global React, TargetComponent */ /* global React */
"use strict"; "use strict";
loader.lazyRequireGetter(this, "React", loader.lazyRequireGetter(this, "React",
"devtools/client/shared/vendor/react"); "devtools/client/shared/vendor/react");
loader.lazyRequireGetter(this, "TargetComponent", loader.lazyRequireGetter(this, "Target",
"devtools/client/aboutdebugging/components/target", true); "devtools/client/aboutdebugging/components/target", true);
loader.lazyRequireGetter(this, "Services"); loader.lazyRequireGetter(this, "Services");
@ -18,15 +18,13 @@ const LocaleCompare = (a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}; };
exports.TargetListComponent = React.createClass({ exports.TargetList = React.createClass({
displayName: "TargetListComponent", displayName: "TargetList",
render() { render() {
let client = this.props.client; let { client, debugDisabled } = this.props;
let debugDisabled = this.props.debugDisabled;
let targets = this.props.targets.sort(LocaleCompare).map(target => { let targets = this.props.targets.sort(LocaleCompare).map(target => {
return React.createElement(TargetComponent, return React.createElement(Target, { client, target, debugDisabled });
{ client, target, debugDisabled });
}); });
return ( return (
React.createElement("div", { id: this.props.id, className: "targets" }, React.createElement("div", { id: this.props.id, className: "targets" },

View File

@ -23,12 +23,11 @@ loader.lazyRequireGetter(this, "gDevTools",
const Strings = Services.strings.createBundle( const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"); "chrome://devtools/locale/aboutdebugging.properties");
exports.TargetComponent = React.createClass({ exports.Target = React.createClass({
displayName: "TargetComponent", displayName: "Target",
debug() { debug() {
let client = this.props.client; let { client, target } = this.props;
let target = this.props.target;
switch (target.type) { switch (target.type) {
case "extension": case "extension":
BrowserToolboxProcess.init({ addonID: target.addonID }); BrowserToolboxProcess.init({ addonID: target.addonID });
@ -53,12 +52,11 @@ exports.TargetComponent = React.createClass({
}, },
render() { render() {
let target = this.props.target; let { target, debugDisabled } = this.props;
let debugDisabled = this.props.debugDisabled;
return React.createElement("div", { className: "target" }, return React.createElement("div", { className: "target" },
React.createElement("img", { React.createElement("img", {
className: "target-icon", className: "target-icon",
role: "presentation",
src: target.icon }), src: target.icon }),
React.createElement("div", { className: "target-details" }, React.createElement("div", { className: "target-details" },
React.createElement("div", { className: "target-name" }, target.name), React.createElement("div", { className: "target-name" }, target.name),

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global React, TargetListComponent */ /* global React */
"use strict"; "use strict";
@ -10,8 +10,10 @@ loader.lazyRequireGetter(this, "Ci",
"chrome", true); "chrome", true);
loader.lazyRequireGetter(this, "React", loader.lazyRequireGetter(this, "React",
"devtools/client/shared/vendor/react"); "devtools/client/shared/vendor/react");
loader.lazyRequireGetter(this, "TargetListComponent", loader.lazyRequireGetter(this, "TargetList",
"devtools/client/aboutdebugging/components/target-list", true); "devtools/client/aboutdebugging/components/target-list", true);
loader.lazyRequireGetter(this, "TabHeader",
"devtools/client/aboutdebugging/components/tab-header", true);
loader.lazyRequireGetter(this, "Services"); loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm"); loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
@ -20,8 +22,8 @@ const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"); "chrome://devtools/locale/aboutdebugging.properties");
const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg"; const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
exports.WorkersComponent = React.createClass({ exports.WorkersTab = React.createClass({
displayName: "WorkersComponent", displayName: "WorkersTab",
getInitialState() { getInitialState() {
return { return {
@ -47,22 +49,30 @@ exports.WorkersComponent = React.createClass({
}, },
render() { render() {
let client = this.props.client; let { client } = this.props;
let workers = this.state.workers; let { workers } = this.state;
return React.createElement("div", { className: "inverted-icons" },
React.createElement(TargetListComponent, { return React.createElement(
id: "service-workers", "div", { id: "tab-workers", className: "tab", role: "tabpanel",
name: Strings.GetStringFromName("serviceWorkers"), "aria-labelledby": "tab-workers-header-name" },
targets: workers.service, client }), React.createElement(TabHeader, {
React.createElement(TargetListComponent, { id: "tab-workers-header-name",
id: "shared-workers", name: Strings.GetStringFromName("workers")}),
name: Strings.GetStringFromName("sharedWorkers"), React.createElement(
targets: workers.shared, client }), "div", { id: "workers", className: "inverted-icons" },
React.createElement(TargetListComponent, { React.createElement(TargetList, {
id: "other-workers", id: "service-workers",
name: Strings.GetStringFromName("otherWorkers"), name: Strings.GetStringFromName("serviceWorkers"),
targets: workers.other, client }) targets: workers.service, client }),
); React.createElement(TargetList, {
id: "shared-workers",
name: Strings.GetStringFromName("sharedWorkers"),
targets: workers.shared, client }),
React.createElement(TargetList, {
id: "other-workers",
name: Strings.GetStringFromName("otherWorkers"),
targets: workers.other, client }))
);
}, },
update() { update() {

View File

@ -0,0 +1,55 @@
/* 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/. */
/* eslint-env browser */
/* global DebuggerClient, DebuggerServer, React */
"use strict";
const { loader } = Components.utils.import(
"resource://devtools/shared/Loader.jsm", {});
loader.lazyRequireGetter(this, "DebuggerClient",
"devtools/shared/client/main", true);
loader.lazyRequireGetter(this, "DebuggerServer",
"devtools/server/main", true);
loader.lazyRequireGetter(this, "Telemetry",
"devtools/client/shared/telemetry");
loader.lazyRequireGetter(this, "AboutDebuggingApp",
"devtools/client/aboutdebugging/components/aboutdebugging", true);
var AboutDebugging = {
init() {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
DebuggerServer.allowChromeProcess = true;
this.client = new DebuggerClient(DebuggerServer.connectPipe());
this.client.connect().then(() => {
let client = this.client;
let telemetry = new Telemetry();
React.render(React.createElement(AboutDebuggingApp,
{ client, telemetry, window }), document.querySelector("#body"));
});
},
destroy() {
React.unmountComponentAtNode(document.querySelector("#body"));
this.client.close();
this.client = null;
},
};
window.addEventListener("DOMContentLoaded", function load() {
window.removeEventListener("DOMContentLoaded", load);
AboutDebugging.init();
});
window.addEventListener("unload", function unload() {
window.removeEventListener("unload", unload);
AboutDebugging.destroy();
});

View File

@ -8,6 +8,7 @@ support-files =
service-workers/empty-sw.html service-workers/empty-sw.html
service-workers/empty-sw.js service-workers/empty-sw.js
[browser_addons_debugging_initial_state.js]
[browser_addons_install.js] [browser_addons_install.js]
[browser_addons_toggle_debug.js] [browser_addons_toggle_debug.js]
[browser_service_workers.js] [browser_service_workers.js]

View File

@ -0,0 +1,67 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that addons debugging controls are properly enabled/disabled depending
// on the values of the relevant preferences:
// - devtools.chrome.enabled
// - devtools.debugger.remote-enabled
const ADDON_ID = "test-devtools@mozilla.org";
const TEST_DATA = [
{
chromeEnabled: false,
debuggerRemoteEnable: false,
expected: false,
}, {
chromeEnabled: false,
debuggerRemoteEnable: true,
expected: false,
}, {
chromeEnabled: true,
debuggerRemoteEnable: false,
expected: false,
}, {
chromeEnabled: true,
debuggerRemoteEnable: true,
expected: true,
}
];
add_task(function* () {
for (let testData of TEST_DATA) {
yield testCheckboxState(testData);
}
});
function* testCheckboxState(testData) {
info("Set preferences as defined by the current test data.");
yield new Promise(resolve => {
let options = {"set": [
["devtools.chrome.enabled", testData.chromeEnabled],
["devtools.debugger.remote-enabled", testData.debuggerRemoteEnable],
]};
SpecialPowers.pushPrefEnv(options, resolve);
});
let { tab, document } = yield openAboutDebugging("addons");
info("Install a test addon.");
yield installAddon(document, "addons/unpacked/install.rdf", "test-devtools");
info("Test checkbox checked state.");
let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
is(addonDebugCheckbox.checked, testData.expected,
"Addons debugging checkbox should be in expected state.");
info("Test debug buttons disabled state.");
let debugButtons = [...document.querySelectorAll("#addons .debug-button")];
ok(debugButtons.every(b => b.disabled != testData.expected),
"Debug buttons should be in the expected state");
info("Uninstall test addon installed earlier.");
yield uninstallAddon(ADDON_ID);
yield closeAboutDebugging(tab);
}

Some files were not shown because too many files have changed in this diff Show More