diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/build/mozconfig.automation thunderbird-trunk-39.0~a1~hg20150226r17577.231190/build/mozconfig.automation --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/build/mozconfig.automation 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/build/mozconfig.automation 2015-02-28 15:02:40.000000000 +0000 @@ -17,6 +17,7 @@ mk_add_options "export MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-0}" mk_add_options "export MOZ_AUTOMATION_UPLOAD=${MOZ_AUTOMATION_UPLOAD-1}" mk_add_options "export MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-0}" +mk_add_options "export MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-0}" # If we are also building with MOZ_PKG_PRETTYNAMES, set the corresponding # stages. diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/agenda-listbox.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/agenda-listbox.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/agenda-listbox.js 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/agenda-listbox.js 2015-02-28 15:02:41.000000000 +0000 @@ -14,7 +14,8 @@ agendaListboxControl: null, mPendingRefreshJobs: null, kDefaultTimezone: null, - showsToday: false + showsToday: false, + soonDays: 5 }; /** @@ -30,15 +31,28 @@ this.today = new Synthetic(showTodayHeader, 1); this.addPeriodListItem(this.today, "today-header"); this.tomorrow = new Synthetic(showTomorrowHeader, 1); - var soondays = Preferences.get("calendar.agendaListbox.soondays", 5); - this.soon = new Synthetic(showSoonHeader, soondays); + this.soonDays = getSoondaysPreference(); + this.soon = new Synthetic(showSoonHeader, this.soonDays); this.periods = [this.today, this.tomorrow, this.soon]; this.mPendingRefreshJobs = new Map(); + var prefObserver = { + observe: function aL_observe(aSubject, aTopic, aPrefName) { + switch (aPrefName) { + case "calendar.agendaListbox.soondays": + agendaListbox.soonDays = getSoondaysPreference(); + agendaListbox.updateSoonSection(); + break; + } + } + } + Services.prefs.addObserver("calendar.agendaListbox", prefObserver, false); + // Make sure the agenda listbox is unloaded var self = this; window.addEventListener("unload", function unload_agendaListbox() { + Services.prefs.removeObserver("calendar.agendaListbox", prefObserver); self.uninit(); }, false); @@ -393,7 +407,7 @@ } /** - * Returns the a start or end date of an item according to which of them + * Returns the start or end date of an item according to which of them * must be displayed in a given period of the agenda * * @param aItem The item to compare. @@ -1020,6 +1034,18 @@ }; /** + * Updates the "Soon" section of today pane when preference soondays changes + **/ +agendaListbox.updateSoonSection = +function updateSoonSection() { + let soonHeader = document.getElementById("nextweek-header"); + this.soon.duration = this.soonDays; + this.soon.open = true; + soonHeader.setItem(this.soon, true); + agendaListbox.refreshPeriodDates(now()); +} + +/** * Updates the event considered "current". This goes through all "today" items * and sets the "current" attribute on all list items that are currently * occurring. @@ -1126,3 +1152,24 @@ } gEventTimer.initWithCallback(udCallback, aMsUntil, gEventTimer.TYPE_ONE_SHOT); } + +/** + * Gets a right value for calendar.agendaListbox.soondays preference, avoid + * erroneus values edited in the lightning.js preference file + **/ +function getSoondaysPreference() { + let prefName = "calendar.agendaListbox.soondays"; + let soonpref = Preferences.get(prefName, 5); + + if (soonpref > 0 && soonpref <= 28) { + if (soonpref % 7 != 0) { + let intSoonpref = Math.floor(soonpref / 7) * 7; + soonpref = (intSoonpref == 0 ? soonpref : intSoonpref); + Preferences.set(prefName, soonpref, "INT"); + } + } else { + soonpref = soonpref > 28 ? 28 : 1; + Preferences.set(prefName, soonpref, "INT"); + } + return soonpref; +} diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/agenda-listbox.xml thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/agenda-listbox.xml --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/agenda-listbox.xml 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/agenda-listbox.xml 2015-02-28 15:02:41.000000000 +0000 @@ -52,6 +52,7 @@ + 7) { + this.kCheckbox.label += " (" + unitPluralForm(duration / 7, "weeks") + ")"; + } else { + this.kCheckbox.label += " (" + unitPluralForm(duration, "days") + ")"; + } + } } else { if (synthetic.duration == 1) { this.kCheckbox.label = getDateFormatter().formatDate(synthetic.start); @@ -121,7 +130,9 @@ .getInTimezone(calendarDefaultTimezone()); let endPreviousDay = endDate.clone(); endPreviousDay.day--; - let longPeriod = aPeriod.duration > 1; + let now = cal.now(); + now.isDate = true; + let longPeriod = periodStartDate.subtractDate(now).days > 1; let date = ""; let iconType = ""; @@ -211,7 +222,9 @@ if (endAtMidnight) { endDate.day--; } - let longFormat = aPeriod.duration > 1; + let now = cal.now(); + now.isDate = true; + let longFormat = periodStartDate.subtractDate(now).days > 1; let duration = ""; let iconType = ""; diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/calendar-calendars-list.xul thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/calendar-calendars-list.xul --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/calendar-calendars-list.xul 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/calendar-calendars-list.xul 2015-02-28 15:02:41.000000000 +0000 @@ -34,8 +34,12 @@ accesskey="&calendar.context.findcalendar.accesskey;" oncommand="openCalendarSubscriptionsDialog();"/> 1) { - enableElement("list-calendars-context-delete"); - } enableElement("list-calendars-context-togglevisible"); setElementValue("list-calendars-context-togglevisible", false, "collapsed"); @@ -190,6 +216,11 @@ setElementValue("list-calendars-context-showonly", cal.calGetString("calendar", "showOnlyCalendar", [calendar.name]), "label"); + + setupDeleteMenuitem("list-calendars-context-delete", calendar); + // Only enable the delete calendars item if there is more than one + // calendar. We don't want to have the last calendar deleted. + setElementValue("list-calendars-context-delete", calendars.length < 2 && "true", "disabled"); } else { disableElement("list-calendars-context-edit"); disableElement("list-calendars-context-publish"); @@ -198,11 +229,36 @@ setElementValue("list-calendars-context-togglevisible", true, "collapsed"); disableElement("list-calendars-context-showonly"); setElementValue("list-calendars-context-showonly", true, "collapsed"); + setupDeleteMenuitem("list-calendars-context-delete", null); } return true; } /** + * Changes the "delete calendar" menuitem to have the right label based on the + * removeModes. The menuitem must have the attributes "labelremove", + * "labeldelete" and "labelunsubscribe". + * + * @param aDeleteId The id of the menuitem to delete the calendar + */ +function setupDeleteMenuitem(aDeleteId, aCalendar) { + let calendar = (aCalendar === undefined ? getSelectedCalendar() : aCalendar); + let modes = new Set(calendar ? calendar.getProperty("capabilities.removeModes") || ["unsubscribe"] : []); + + let type = "remove"; + if (modes.has("delete") && !modes.has("unsubscribe")) { + type = "delete"; + } else if (modes.has("unsubscribe") && !modes.has("delete")) { + type = "unsubscribe"; + } + + let deleteItem = document.getElementById(aDeleteId); + setElementValue(deleteItem, deleteItem.getAttribute("label" + type), "label"); + setElementValue(deleteItem, deleteItem.getAttribute("accesskey" + type), "accesskey"); + setElementValue(deleteItem, modes.size == 0 && "true", "disabled"); +} + +/** * Makes sure the passed calendar is visible to the user * * @param aCalendar The calendar to make visible. diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/calendar-ui-utils.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/calendar-ui-utils.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/calendar-ui-utils.js 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/calendar-ui-utils.js 2015-02-28 15:02:41.000000000 +0000 @@ -428,7 +428,8 @@ function unitPluralForm(aLength, aUnit, aIncludeLength=true) { let unitProp = {"minutes": "unitMinutes", "hours": "unitHours", - "days": "unitDays"}[aUnit] || "unitMinutes"; + "days": "unitDays", + "weeks": "unitWeeks"}[aUnit] || "unitMinutes"; return PluralForm.get(aLength, cal.calGetString("calendar", unitProp)) .replace("#1", aIncludeLength ? aLength : "").trim(); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-event-dialog-attendees.xml thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-event-dialog-attendees.xml --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-event-dialog-attendees.xml 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-event-dialog-attendees.xml 2015-02-28 15:02:41.000000000 +0000 @@ -1052,7 +1052,7 @@ } let arrowLength = 1; - if (element.value.contains(",")) { + if (element.value.contains(",") || element.value.match(/^[^"].*[<>@,].*[^"] <.+@.+>$/)) { let strippedAddresses = element.value.replace(/.* >> /, ""); let addresses = MailServices.headerParser.makeFromDisplayAddress(strippedAddresses); element.value = parseHeaderValue(addresses[0]); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-event-dialog-attendees.xul thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-event-dialog-attendees.xul --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-event-dialog-attendees.xul 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-event-dialog-attendees.xul 2015-02-28 15:02:41.000000000 +0000 @@ -101,73 +101,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-event-dialog.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-event-dialog.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-event-dialog.js 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-event-dialog.js 2015-02-28 15:02:41.000000000 +0000 @@ -36,7 +36,6 @@ var gShowTimeAs = null; var gWarning = false; var gPreviousCalendarId = null; -var gIsModifyingItem = false; var eventDialogQuitObserver = { observe: function(aSubject, aTopic, aData) { @@ -50,11 +49,12 @@ }; var eventDialogCalendarObserver = { + target: null, + isObserving: false, + onModifyItem: function(aNewItem, aOldItem) { - if (gIsModifyingItem) { - // the modification was triggered from this dialog, no need to prompt - gIsModifyingItem = false; - } else if (window.calendarItem && window.calendarItem.id == aOldItem.id) { + if (this.isObserving && "calendarItem" in window && + window.calendarItem && window.calendarItem.id == aOldItem.id) { let doUpdate = true; // The item has been modified outside the dialog. We only need to @@ -91,10 +91,8 @@ }, onDeleteItem: function(aDeletedItem) { - if (gIsModifyingItem) { - // the deletion was triggered from this dialog, no need to prompt - gIsModifyingItem = false; - } else if (window.calendarItem && window.calendarItem.id == aDeletedItem.id) { + if (this.isObserving && "calendarItem" in window && + window.calendarItem && window.calendarItem.id == aDeletedItem.id) { gConfirmCancel = false; document.documentElement.cancelDialog(); } @@ -106,7 +104,24 @@ onAddItem: function() {}, onError: function() {}, onPropertyChanged: function() {}, - onPropertyDeleting: function() {} + onPropertyDeleting: function() {}, + + observe: function(aCalendar) { + // use the new calendar if one was passed, otherwise use the last one + this.target = aCalendar || this.target; + if (this.target) { + this.cancel(); + this.target.addObserver(this); + this.isObserving = true; + } + }, + + cancel: function() { + if (this.isObserving && this.target) { + this.target.removeObserver(this); + this.isObserving = false; + } + } }; /** @@ -297,14 +312,13 @@ document.documentElement._hitEnter = function() {}; // set up our calendar event observer - if (item.calendar) { - item.calendar.addObserver(eventDialogCalendarObserver); - } + eventDialogCalendarObserver.observe(item.calendar); } function onEventDialogUnload() { - Services.obs.removeObserver(eventDialogQuitObserver, - "quit-application-requested"); + Services.obs.removeObserver(eventDialogQuitObserver, + "quit-application-requested"); + eventDialogCalendarObserver.cancel(); } /** @@ -405,8 +419,9 @@ loadDateTime(item); // add calendars to the calendar menulist - var calendarList = document.getElementById("item-calendar"); - var indexToSelect = appendCalendarItems(item, calendarList, window.arguments[0].calendar); + let calendarList = document.getElementById("item-calendar"); + removeChildren(calendarList); + let indexToSelect = appendCalendarItems(item, calendarList, item.calendar || window.arguments[0].calendar); if (indexToSelect > -1) { calendarList.selectedIndex = indexToSelect; } @@ -2849,10 +2864,7 @@ return; } - // set flag so the calendar observer expects a modify event - if (window.mode == "modify") { - gIsModifyingItem = true; - } + eventDialogCalendarObserver.cancel(); let originalItem = window.calendarItem; let item = saveItem(); @@ -2895,6 +2907,7 @@ // We now have an item, so we must change to an edit. window.mode = "modify"; updateTitle(); + eventDialogCalendarObserver.observe(window.calendarItem.calendar); } } } @@ -2945,12 +2958,14 @@ if (aId == window.calendarItem.id && Components.isSuccessCode(aStatus)) { gConfirmCancel = false; document.documentElement.cancelDialog(); + } else { + eventDialogCalendarObserver.observe(window.calendarItem.calendar); } } } }; - gIsModifyingItem = true; + eventDialogCalendarObserver.cancel(); if (window.calendarItem.parentItem.recurrenceInfo && window.calendarItem.recurrenceId) { // if this is a single occurrence of a recurring item let newItem = window.calendarItem.parentItem.clone(); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-properties-dialog.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-properties-dialog.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-properties-dialog.js 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-properties-dialog.js 2015-02-28 15:02:41.000000000 +0000 @@ -17,12 +17,10 @@ */ function onLoad() { gCalendar = window.arguments[0].calendar; + let calColor = gCalendar.getProperty('color'); document.getElementById("calendar-name").value = gCalendar.name; - let calColor = gCalendar.getProperty('color'); - if (calColor) { - document.getElementById("calendar-color").color = calColor; - } + document.getElementById("calendar-color").value = calColor || "#A8C2E1"; document.getElementById("calendar-uri").value = gCalendar.uri.spec; document.getElementById("read-only").checked = gCalendar.readOnly; @@ -78,7 +76,7 @@ gCalendar.name = document.getElementById("calendar-name").value; // Save calendar color - gCalendar.setProperty("color", document.getElementById("calendar-color").color); + gCalendar.setProperty("color", document.getElementById("calendar-color").value); // Save readonly state gCalendar.readOnly = document.getElementById("read-only").checked; diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-properties-dialog.xul thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-properties-dialog.xul --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/calendar/base/content/dialogs/calendar-properties-dialog.xul 2015-02-19 00:41:43.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/calendar/base/content/dialogs/calendar-properties-dialog.xul 2015-02-28 15:02:41.000000000 +0000 @@ -25,6 +25,7 @@ onload="onLoad()" persist="screenX screenY" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" width="500"> + addA11yLoadEvent(function() { + SpecialPowers.pushPrefEnv({"set": [['canvas.hitregions.enabled', true]]}, doTest); + }); + + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/events/test_aria_statechange.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/events/test_aria_statechange.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/events/test_aria_statechange.html 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/events/test_aria_statechange.html 2015-02-28 15:03:00.000000000 +0000 @@ -107,19 +107,30 @@ new setAttrOfMixedType(aID, "aria-checked", STATE_CHECKED, aValue); } - function buildQueueForAttrOfMixedType(aQueue, aID, aInvokerFunc) + function buildQueueForAttr(aList, aQueue, aID, aInvokerFunc) { - var list = [ "", "undefined", "false", "true", "mixed" ]; - for (var i = 0; i < list.length; i++) { - for (var j = i + 1; j < list.length; j++) { + for (var i = 0; i < aList.length; i++) { + for (var j = i + 1; j < aList.length; j++) { // XXX: changes from/to "undefined"/"" shouldn't fire state change // events, bug 472142. - aQueue.push(new aInvokerFunc(aID, list[i])); - aQueue.push(new aInvokerFunc(aID, list[j])); + aQueue.push(new aInvokerFunc(aID, aList[i])); + aQueue.push(new aInvokerFunc(aID, aList[j])); } } } + function buildQueueForAttrOfMixedType(aQueue, aID, aInvokerFunc) + { + var list = [ "", "undefined", "false", "true", "mixed" ]; + buildQueueForAttr(list, aQueue, aID, aInvokerFunc); + } + + function buildQueueForAttrOfBoolType(aQueue, aID, aInvokerFunc) + { + var list = [ "", "undefined", "false", "true" ]; + buildQueueForAttr(list, aQueue, aID, aInvokerFunc); + } + function doTests() { gQueue = new eventQueue(); @@ -135,6 +146,7 @@ buildQueueForAttrOfMixedType(gQueue, "pressable", setPressed); buildQueueForAttrOfMixedType(gQueue, "pressable_native", setPressed); buildQueueForAttrOfMixedType(gQueue, "checkable", setChecked); + buildQueueForAttrOfBoolType(gQueue, "checkableBool", setChecked); gQueue.invoke(); // Will call SimpleTest.finish(); } @@ -166,6 +178,11 @@ title="Pressed state is not exposed on a button element with aria-pressed attribute" Mozilla Bug 989958 +

@@ -186,5 +203,6 @@ +
diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/events/test_docload.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/events/test_docload.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/events/test_docload.html 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/events/test_docload.html 2015-02-28 15:03:00.000000000 +0000 @@ -15,6 +15,25 @@ src="../role.js"> + + + @@ -177,6 +196,12 @@ { role: ROLE_CHROME_WINDOW }, + { + role: ROLE_CHROME_WINDOW + }, + { + role: ROLE_CHROME_WINDOW + }, { role: ROLE_CHROME_WINDOW } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/hittest/test_canvas_hitregion.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/hittest/test_canvas_hitregion.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/hittest/test_canvas_hitregion.html 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/hittest/test_canvas_hitregion.html 2015-02-28 15:03:00.000000000 +0000 @@ -13,8 +13,6 @@ src="../layout.js"> + addA11yLoadEvent(function() { + SpecialPowers.pushPrefEnv({"set": [['canvas.hitregions.enabled', true]]}, doTest); + }); + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/jsat/test_content_integration.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/jsat/test_content_integration.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/jsat/test_content_integration.html 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/jsat/test_content_integration.html 2015-02-28 15:03:00.000000000 +0000 @@ -48,7 +48,7 @@ // check checkbox [ContentMessages.activateCurrent(), new ExpectedClickAction({ no_android: true }), - new ExpectedCheckAction(true, { android_todo: true })], + new ExpectedCheckAction(true)], [ContentMessages.simpleMoveNext, new ExpectedCursorChange(['much range', {'string': 'label'}])], [ContentMessages.simpleMoveNext, @@ -74,7 +74,7 @@ // uncheck checkbox [ContentMessages.activateCurrent(), new ExpectedClickAction({ no_android: true }), - new ExpectedCheckAction(false, { android_todo: true })], + new ExpectedCheckAction(false)], [ContentMessages.simpleMovePrevious, new ExpectedCursorChange(['wow', {'string': 'headingLevel', 'args': [1]}])], [ContentMessages.simpleMovePrevious, diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/jsat/test_hints.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/jsat/test_hints.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/jsat/test_hints.html 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/jsat/test_hints.html 2015-02-28 15:03:00.000000000 +0000 @@ -43,6 +43,9 @@ accOrElmOrID: 'nested_link3', expectedHints: [{string: 'link-hint'}, {string: 'pushbutton-hint'}, "Double tap and hold to activate"] + }, { + accOrElmOrID: 'menuitemradio', + expectedHints: [{string: 'radiomenuitem-hint'}] }]; // Test hints. @@ -80,6 +83,7 @@
I am a special link + Item 1 diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/role/test_aria.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/role/test_aria.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/role/test_aria.html 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/role/test_aria.html 2015-02-28 15:03:00.000000000 +0000 @@ -60,6 +60,7 @@ testRole("aria_slider", ROLE_SLIDER); testRole("aria_spinbutton", ROLE_SPINBUTTON); testRole("aria_status", ROLE_STATUSBAR); + testRole("aria_switch", ROLE_SWITCH); testRole("aria_tab", ROLE_PAGETAB); testRole("aria_tablist", ROLE_PAGETABLIST); testRole("aria_tabpanel", ROLE_PROPERTYPAGE); @@ -178,6 +179,11 @@ href="https://bugzilla.mozilla.org/show_bug.cgi?id=735645"> Bug 735645 +

@@ -225,6 +231,7 @@
   
   
   
+  
   
   
   
diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/role.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/role.js
--- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/role.js	2015-02-19 00:42:05.000000000 +0000
+++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/role.js	2015-02-28 15:03:00.000000000 +0000
@@ -70,6 +70,7 @@
 const ROLE_SPINBUTTON = nsIAccessibleRole.ROLE_SPINBUTTON;
 const ROLE_STATICTEXT = nsIAccessibleRole.ROLE_STATICTEXT;
 const ROLE_STATUSBAR = nsIAccessibleRole.ROLE_STATUSBAR;
+const ROLE_SWITCH = nsIAccessibleRole.ROLE_SWITCH;
 const ROLE_TABLE = nsIAccessibleRole.ROLE_TABLE;
 const ROLE_TERM = nsIAccessibleRole.ROLE_TERM;
 const ROLE_TEXT_CONTAINER = nsIAccessibleRole.ROLE_TEXT_CONTAINER;
diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/states/test_aria.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/states/test_aria.html
--- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/states/test_aria.html	2015-02-19 00:42:05.000000000 +0000
+++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/states/test_aria.html	2015-02-28 15:03:00.000000000 +0000
@@ -92,6 +92,8 @@
       // aria-checked
       testStates("aria_checked_checkbox", STATE_CHECKED);
       testStates("aria_mixed_checkbox", STATE_MIXED);
+      testStates("aria_checked_switch", STATE_CHECKED);
+      testStates("aria_mixed_switch", 0, 0, STATE_MIXED); // unsupported
 
       // test disabled group and all its descendants to see if they are
       // disabled, too. See bug 429285.
@@ -350,6 +352,11 @@
      title="Pressed state is not exposed on a button element with aria-pressed attribute"
     Mozilla Bug 989958
   
+  
 
   

@@ -383,6 +390,15 @@ + +
+ I am switched on +
+ +
+ I am unsupported +
+
modal stuff
non modal stuff
div>
diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/states.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/states.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/states.js 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/states.js 2015-02-28 15:03:00.000000000 +0000 @@ -198,6 +198,17 @@ } } +/** + * Fails if no defunct state on the accessible. + */ +function testIsDefunct(aAccessible, aTestName) +{ + var id = prettyName(aAccessible) + (aTestName ? " [" + aTestName + "]" : ""); + var [state, extraState] = getStates(aAccessible); + isState(extraState & EXT_STATE_DEFUNCT, EXT_STATE_DEFUNCT, true, + "no defuct state for " + id + "!"); +} + function getStringStates(aAccOrElmOrID) { var [state, extraState] = getStates(aAccOrElmOrID); @@ -209,7 +220,7 @@ var acc = getAccessible(aAccOrElmOrID); if (!acc) return [0, 0]; - + var state = {}, extraState = {}; acc.getState(state, extraState); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/tree/test_aria_presentation.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/tree/test_aria_presentation.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/tree/test_aria_presentation.html 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/tree/test_aria_presentation.html 2015-02-28 15:03:00.000000000 +0000 @@ -21,14 +21,18 @@ // Presentation role don't allow accessible. var tree = { SECTION: [ // container - { TEXT_LEAF: [ ] } // child text of presentation node + { TEXT_LEAF: [ ] }, // child text of 'presentation' node + { TEXT_LEAF: [ ] } // child text of 'none' node ] }; testAccessibleTree("div_cnt", tree); - // Focusable element, presentation role is ignored. + // Focusable element, 'presentation' and 'none' roles are ignored. tree = { SECTION: [ // container - { PUSHBUTTON: [ // button + { PUSHBUTTON: [ // button having 'presentation' role + { TEXT_LEAF: [ ] } + ] }, + { PUSHBUTTON: [ // button having 'none' role { TEXT_LEAF: [ ] } ] } ] }; @@ -37,18 +41,28 @@ // Presentation table, no table structure is exposed. tree = { SECTION: [ // container - { TEXT_CONTAINER: [ // td generic accessible + { TEXT_CONTAINER: [ // td generic accessible inside 'presentation' table + { TEXT_LEAF: [ ] } // cell text + ] }, + { TEXT_CONTAINER: [ // td generic accessible inside 'none' table { TEXT_LEAF: [ ] } // cell text ] } ] }; testAccessibleTree("tbl_cnt", tree); - // Focusable table, presentation role is ignored. + // Focusable table, 'presentation' and 'none' roles are ignored. tree = { SECTION: [ // container - { TABLE: [ // table + { TABLE: [ // table having 'presentation' role + { ROW: [ // tr + { CELL: [ // td + { TEXT_LEAF: [ ] } + ] } + ] } + ] }, + { TABLE: [ // table having 'none' role { ROW: [ // tr - { CELL: [ //td + { CELL: [ // td { TEXT_LEAF: [ ] } ] } ] } @@ -59,13 +73,17 @@ // Presentation list, expose generic accesisble for list items. tree = { SECTION: [ // container - { PARAGRAPH: [ // li generic accessible + { PARAGRAPH: [ // li generic accessible inside 'presentation' role + { TEXT_LEAF: [ ] } // li text + ] }, + { PARAGRAPH: [ // li generic accessible inside 'none' role { TEXT_LEAF: [ ] } // li text ] } ] }; testAccessibleTree("list_cnt", tree); - // Has ARIA globals or referred by ARIA relationship. + // Has ARIA globals or referred by ARIA relationship, role='presentation' + // and role='none' are ignored. tree = { SECTION: [ // container { LABEL: [ // label, has aria-owns @@ -74,6 +92,14 @@ { TEXT_LEAF: [ ] }, { LABEL: [ // label, referenced by aria-owns { TEXT_LEAF: [ ] } + ] }, + { TEXT_LEAF: [ ] }, + { LABEL: [ // label, has aria-owns + { TEXT_LEAF: [ ] } + ] }, + { TEXT_LEAF: [ ] }, + { LABEL: [ // label, referenced by aria-owns + { TEXT_LEAF: [ ] } ] } ] }; testAccessibleTree("airaglobalprop_cnt", tree); @@ -90,21 +116,26 @@
- Mozilla Bug 548291 + Bug 548291 - Mozilla Bug 666504 + Bug 666504 + + + Bug 971212

   
-
text
+
t
t
-
+
@@ -112,6 +143,11 @@
cell
+ + + + +
cell
@@ -120,17 +156,27 @@ cell + + + + +
cell
  • item
+
    +
  • item
  • +
+ +
diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/treeupdate/a11y.ini thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/treeupdate/a11y.ini --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/treeupdate/a11y.ini 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/treeupdate/a11y.ini 2015-02-28 15:03:00.000000000 +0000 @@ -24,6 +24,7 @@ [test_optgroup.html] [test_recreation.html] [test_select.html] +[test_shutdown.xul] [test_textleaf.html] [test_visibility.html] [test_whitespace.html] diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/treeupdate/test_shutdown.xul thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/treeupdate/test_shutdown.xul --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/treeupdate/test_shutdown.xul 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/treeupdate/test_shutdown.xul 2015-02-28 15:03:00.000000000 +0000 @@ -0,0 +1,132 @@ + + + + + + + + + + + + Bug 503727 +
+

+ +
+      
+ + + + + + + + + + + + + + + + + + +
+ +
diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/treeview.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/treeview.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/tests/mochitest/treeview.js 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/tests/mochitest/treeview.js 2015-02-28 15:03:00.000000000 +0000 @@ -60,7 +60,7 @@ function nsTreeTreeView() { this.__proto__ = new nsTreeView(); - + this.mData = [ new treeItem("row1"), new treeItem("row2_", true, [new treeItem("row2.1_"), new treeItem("row2.2_")]), diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/xul/XULTreeAccessible.cpp thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/xul/XULTreeAccessible.cpp --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/xul/XULTreeAccessible.cpp 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/xul/XULTreeAccessible.cpp 2015-02-28 15:03:00.000000000 +0000 @@ -144,11 +144,8 @@ void XULTreeAccessible::Shutdown() { - // XXX: we don't remove accessible from document cache if shutdown wasn't - // initiated by document destroying. Note, we can't remove accessible from - // document cache here while document is going to be shutdown. Note, this is - // not unique place where we have similar problem. - ClearCache(mAccessibleCache); + if (!mDoc->IsDefunct()) + mAccessibleCache.Enumerate(UnbindCacheEntryFromDocument, nullptr); mTree = nullptr; mTreeView = nullptr; @@ -552,7 +549,8 @@ return; if (!mTreeView) { - ClearCache(mAccessibleCache); + mAccessibleCache.Enumerate(UnbindCacheEntryFromDocument, + nullptr); return; } @@ -610,7 +608,8 @@ return; if (!mTreeView) { - ClearCache(mAccessibleCache); + mAccessibleCache.Enumerate(UnbindCacheEntryFromDocument, + nullptr); return; } @@ -669,7 +668,9 @@ Document()->FireDelayedEvent(reorderEvent); // Clear cache. - ClearCache(mAccessibleCache); + mAccessibleCache.Enumerate(UnbindCacheEntryFromDocument, + nullptr); + mTreeView = aView; } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/xul/XULTreeGridAccessible.cpp thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/xul/XULTreeGridAccessible.cpp --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/accessible/xul/XULTreeGridAccessible.cpp 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/accessible/xul/XULTreeGridAccessible.cpp 2015-02-28 15:03:00.000000000 +0000 @@ -275,7 +275,11 @@ void XULTreeGridRowAccessible::Shutdown() { - ClearCache(mAccessibleCache); + if (!mDoc->IsDefunct()) { + mAccessibleCache.Enumerate(UnbindCacheEntryFromDocument, + nullptr); + } + XULTreeItemAccessibleBase::Shutdown(); } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/CONTRIBUTING.md thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/CONTRIBUTING.md --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/CONTRIBUTING.md 2015-02-19 00:42:05.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/CONTRIBUTING.md 2015-02-28 15:03:00.000000000 +0000 @@ -17,12 +17,14 @@ 3. Make your changes, per the Overview 4. Write a test ([intro][test intro], [API][test API]) 5. Submit pull request with changes and a title in a form of `Bug XXX - description` -6. Copy the pull request link from GitHub and paste it in as an attachment to the bug -7. Each pull request should idealy contain only one commit, so squash the commits if necessary. -8. Flag the attachment for code review from one of the Jetpack reviewers listed below. +6. Make sure that [Travis CI](https://travis-ci.org/mozilla/addon-sdk/branches) tests are passing for your branch. +7. Copy the pull request link from GitHub and paste it in as an attachment to the bug +8. Each pull request should idealy contain only one commit, so squash the commits if necessary. +9. Flag the attachment for code review from one of the Jetpack reviewers listed below. This step is optional, but could speed things up. -9. Address any nits (ie style changes), or other issues mentioned in the review. -10. Finally, once review is approved, a team member will do the merging +10. Address any nits (ie style changes), or other issues mentioned in the review. + +Finally, once review is approved, a team member will do the merging ## Good First Bugs diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/addon/runner.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/addon/runner.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/addon/runner.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/addon/runner.js 2015-02-28 15:03:00.000000000 +0000 @@ -81,14 +81,12 @@ // Exports data to a pseudo module so that api-utils/l10n/core // can get access to it definePseudo(options.loader, '@l10n/data', data ? data : null); - return ready; - }).then(function() { - run(options); + return ready.then(() => run(options, !!data)); }).then(null, console.exception); return void 0; // otherwise we raise a warning, see bug 910304 }); -function run(options) { +function run(options, hasL10n) { try { // Try initializing HTML localization before running main module. Just print // an exception in case of error, instead of preventing addon to be run. @@ -96,7 +94,7 @@ // Do not enable HTML localization while running test as it is hard to // disable. Because unit tests are evaluated in a another Loader who // doesn't have access to this current loader. - if (options.main !== 'sdk/test/runner') { + if (hasL10n && options.main !== 'sdk/test/runner') { require('../l10n/html').enable(); } } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/core/observer.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/core/observer.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/core/observer.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/core/observer.js 2015-02-28 15:03:00.000000000 +0000 @@ -41,10 +41,9 @@ this.delegate = delegate; }, QueryInterface: function(iid) { - const isObserver = iid.equals(Ci.nsIObserver); - const isWeakReference = iid.equals(Ci.nsISupportsWeakReference); - - if (!isObserver && !isWeakReference) + if (!iid.equals(Ci.nsIObserver) && + !iid.equals(Ci.nsISupportsWeakReference) && + !iid.equals(Ci.nsISupports)) throw Cr.NS_ERROR_NO_INTERFACE; return this; diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/panel.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/panel.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/panel.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/panel.js 2015-02-28 15:03:00.000000000 +0000 @@ -295,6 +295,9 @@ let ready = filter(panelEvents, ({type, target}) => getAttachEventType(modelFor(panelFor(target))) === type); +// Panel event emitted when the contents of the panel has been loaded. +let readyToShow = filter(panelEvents, ({type}) => type === "DOMContentLoaded"); + // Styles should be always added as soon as possible, and doesn't makes them // depends on `contentScriptWhen` let start = filter(panelEvents, ({type}) => type === "document-element-inserted"); @@ -317,6 +320,10 @@ let window = domPanel.getContentDocument(target).defaultView; workerFor(panel).attach(window); +}); + +on(readyToShow, "data", ({target}) => { + let panel = panelFor(target); if (!modelFor(panel).ready) { modelFor(panel).ready = true; diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/test/harness.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/test/harness.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/test/harness.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/test/harness.js 2015-02-28 15:03:00.000000000 +0000 @@ -340,7 +340,7 @@ let item = { path: matches[1], principal: details[1], - location: details[2] ? details[2].replace("\\", "/", "g") : undefined, + location: details[2] ? details[2].replace(/\\/g, "/") : undefined, source: details[3] ? details[3].split(" -> ").reverse() : undefined, toString: function() this.location }; @@ -364,8 +364,8 @@ let item = { path: matches[1], - location: details[1].replace("\\", "/", "g"), - source: [details[1].replace("\\", "/", "g")], + location: details[1].replace(/\\/g, "/"), + source: [details[1].replace(/\\/g, "/")], toString: function() this.location }; diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/windows/observer.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/windows/observer.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/sdk/windows/observer.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/sdk/windows/observer.js 2015-02-28 15:03:00.000000000 +0000 @@ -12,6 +12,7 @@ const { WindowTracker, windowIterator } = require("../deprecated/window-utils"); const { DOMEventAssembler } = require("../deprecated/events/assembler"); const { Class } = require("../core/heritage"); +const { Cu } = require("chrome"); // Event emitter objects used to register listeners and emit events on them // when they occur. @@ -42,6 +43,9 @@ * Keyboard event being emitted. */ handleEvent(event) { + // Ignore events from windows in the child process as they can't be top-level + if (Cu.isCrossProcessWrapper(event.target)) + return; emit(this, event.type, event.target, event); } }); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/toolkit/loader.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/toolkit/loader.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/lib/toolkit/loader.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/lib/toolkit/loader.js 2015-02-28 15:03:00.000000000 +0000 @@ -703,7 +703,8 @@ const Module = iced(function Module(id, uri) { return create(null, { id: { enumerable: true, value: id }, - exports: { enumerable: true, writable: true, value: create(null) }, + exports: { enumerable: true, writable: true, value: create(null), + configurable: true }, uri: { value: uri } }); }); @@ -784,6 +785,7 @@ } }, modules); + const builtinModuleExports = modules; modules = keys(modules).reduce(function(result, id) { // We resolve `uri` from `id` since modules are cached by `uri`. let uri = resolveURI(id, mapping); @@ -792,7 +794,16 @@ if (isNative && !uri) uri = id; let module = Module(id, uri); - module.exports = freeze(modules[id]); + + // Lazily expose built-in modules in order to + // allow them to be loaded lazily. + Object.defineProperty(module, "exports", { + enumerable: true, + get: function() { + return builtinModuleExports[id]; + } + }); + result[uri] = freeze(module); return result; }, {}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/python-lib/cuddlefish/prefs.py thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/python-lib/cuddlefish/prefs.py --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/python-lib/cuddlefish/prefs.py 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/python-lib/cuddlefish/prefs.py 2015-02-28 15:03:00.000000000 +0000 @@ -60,6 +60,7 @@ 'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash', 'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report', 'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport', + 'browser.selfsupport.url': 'http://localhost/repair-dummy', 'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash', 'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update', diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/test/addons/private-browsing-supported/sidebar/utils.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/test/addons/private-browsing-supported/sidebar/utils.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/test/addons/private-browsing-supported/sidebar/utils.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/test/addons/private-browsing-supported/sidebar/utils.js 2015-02-28 15:03:00.000000000 +0000 @@ -10,7 +10,8 @@ const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [ 'menu_socialSidebar', 'menu_historySidebar', - 'menu_bookmarksSidebar' + 'menu_bookmarksSidebar', + 'menu_readingListSidebar' ]; function isSidebarShowing(window) { diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/test/addons/private-browsing-supported/test-sidebar.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/test/addons/private-browsing-supported/test-sidebar.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/test/addons/private-browsing-supported/test-sidebar.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/test/addons/private-browsing-supported/test-sidebar.js 2015-02-28 15:03:00.000000000 +0000 @@ -129,8 +129,7 @@ assert.pass('onShow works for Sidebar'); loader.unload(); - let sidebarMI = getSidebarMenuitems(); - for (let mi of sidebarMI) { + for (let mi of getSidebarMenuitems()) { assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar') assert.ok(!isChecked(mi), 'no sidebar menuitem is checked'); } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/test/addons/unsafe-content-script/main.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/test/addons/unsafe-content-script/main.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/addon-sdk/source/test/addons/unsafe-content-script/main.js 2015-02-19 00:42:06.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/addon-sdk/source/test/addons/unsafe-content-script/main.js 2015-02-28 15:03:00.000000000 +0000 @@ -6,10 +6,10 @@ const { create: makeFrame } = require("sdk/frame/utils"); const { window } = require("sdk/addon/window"); const { Loader } = require('sdk/test/loader'); -const loader = Loader(module); -const Worker = loader.require("sdk/content/worker").Worker; exports.testMembranelessMode = function(assert, done) { + const loader = Loader(module); + const Worker = loader.require("sdk/content/worker").Worker; let url = "data:text/html;charset=utf-8," + encodeURIComponent( ' + + + + + + + +
+ + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/browser.ini thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/browser.ini --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/browser.ini 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/browser.ini 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + head.js + +[browser_ui_enable_disable.js] +[browser_sidebar_list.js] +;[browser_sidebar_mouse_nav.js] diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/browser_sidebar_list.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/browser_sidebar_list.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/browser_sidebar_list.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/browser_sidebar_list.js 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,53 @@ +/** + * This tests the basic functionality of the sidebar to list items. + */ + +add_task(function*() { + registerCleanupFunction(function*() { + ReadingListUI.hideSidebar(); + yield RLUtils.cleanup(); + }); + + RLUtils.enabled = true; + + yield ReadingListUI.showSidebar(); + let RLSidebar = RLSidebarUtils.RLSidebar; + let sidebarDoc = SidebarUI.browser.contentDocument; + Assert.equal(RLSidebar.numItems, 0, "Should start with no items"); + Assert.equal(RLSidebar.activeItem, null, "Should start with no active item"); + Assert.equal(RLSidebar.activeItem, null, "Should start with no selected item"); + + info("Adding first item"); + yield RLUtils.addItem({ + id: "c3502a49-bcef-4a94-b222-d4834463de33", + url: "http://example.com/article1", + title: "Article 1", + }); + RLSidebarUtils.expectNumItems(1); + + info("Adding more items"); + yield RLUtils.addItem([{ + id: "e054f5b7-1f4f-463f-bb96-d64c02448c31", + url: "http://example.com/article2", + title: "Article 2", + }, { + id: "4207230b-2364-4e97-9587-01312b0ce4e6", + url: "http://example.com/article3", + title: "Article 3", + }]); + RLSidebarUtils.expectNumItems(3); + + info("Closing sidebar"); + ReadingListUI.hideSidebar(); + + info("Adding another item"); + yield RLUtils.addItem({ + id: "dae0e855-607e-4df3-b27f-73a5e35c94fe", + url: "http://example.com/article4", + title: "Article 4", + }); + + info("Re-eopning sidebar"); + yield ReadingListUI.showSidebar(); + RLSidebarUtils.expectNumItems(4); +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/browser_sidebar_mouse_nav.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/browser_sidebar_mouse_nav.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/browser_sidebar_mouse_nav.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/browser_sidebar_mouse_nav.js 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,84 @@ +/** + * Test mouse navigation for selecting items in the sidebar. + */ + + +function mouseInteraction(mouseEvent, responseEvent, itemNode) { + let eventPromise = BrowserUITestUtils.waitForEvent(RLSidebarUtils.list, responseEvent); + let details = {}; + if (mouseEvent != "click") { + details.type = mouseEvent; + } + + EventUtils.synthesizeMouseAtCenter(itemNode, details, itemNode.ownerDocument.defaultView); + return eventPromise; +} + +add_task(function*() { + registerCleanupFunction(function*() { + ReadingListUI.hideSidebar(); + yield RLUtils.cleanup(); + }); + + RLUtils.enabled = true; + + let itemData = [{ + id: "00bd24c7-3629-40b0-acde-37aa81768735", + url: "http://example.com/article1", + title: "Article 1", + }, { + id: "28bf7f19-cf94-4ceb-876a-ac1878342e0d", + url: "http://example.com/article2", + title: "Article 2", + }, { + id: "7e5064ea-f45d-4fc7-8d8c-c067b7781e78", + url: "http://example.com/article3", + title: "Article 3", + }, { + id: "8e72a472-8db8-4904-ba39-9672f029e2d0", + url: "http://example.com/article4", + title: "Article 4", + }, { + id: "8d332744-37bc-4a1a-a26b-e9953b9f7d91", + url: "http://example.com/article5", + title: "Article 5", + }]; + info("Adding initial mock data"); + yield RLUtils.addItem(itemData); + + info("Opening sidebar"); + yield ReadingListUI.showSidebar(); + RLSidebarUtils.expectNumItems(5); + RLSidebarUtils.expectSelectedId(null); + RLSidebarUtils.expectActiveId(null); + + info("Mouse move over item 1"); + yield mouseInteraction("mousemove", "SelectedItemChanged", RLSidebarUtils.list.children[0]); + RLSidebarUtils.expectSelectedId(itemData[0].id); + RLSidebarUtils.expectActiveId(null); + + info("Mouse move over item 2"); + yield mouseInteraction("mousemove", "SelectedItemChanged", RLSidebarUtils.list.children[1]); + RLSidebarUtils.expectSelectedId(itemData[1].id); + RLSidebarUtils.expectActiveId(null); + + info("Mouse move over item 5"); + yield mouseInteraction("mousemove", "SelectedItemChanged", RLSidebarUtils.list.children[4]); + RLSidebarUtils.expectSelectedId(itemData[4].id); + RLSidebarUtils.expectActiveId(null); + + info("Mouse move over item 1 again"); + yield mouseInteraction("mousemove", "SelectedItemChanged", RLSidebarUtils.list.children[0]); + RLSidebarUtils.expectSelectedId(itemData[0].id); + RLSidebarUtils.expectActiveId(null); + + info("Mouse click on item 1"); + yield mouseInteraction("click", "ActiveItemChanged", RLSidebarUtils.list.children[0]); + RLSidebarUtils.expectSelectedId(itemData[0].id); + RLSidebarUtils.expectActiveId(itemData[0].id); + + info("Mouse click on item 3"); + yield mouseInteraction("click", "ActiveItemChanged", RLSidebarUtils.list.children[2]); + RLSidebarUtils.expectSelectedId(itemData[2].id); + RLSidebarUtils.expectActiveId(itemData[2].id); +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/browser_ui_enable_disable.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/browser_ui_enable_disable.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/browser_ui_enable_disable.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/browser_ui_enable_disable.js 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,68 @@ +/** + * Test enabling/disabling the entire ReadingList feature via the + * browser.readinglist.enabled preference. + */ + +function checkRLState() { + let enabled = RLUtils.enabled; + info("Checking ReadingList UI is " + (enabled ? "enabled" : "disabled")); + + let sidebarBroadcaster = document.getElementById("readingListSidebar"); + let sidebarMenuitem = document.getElementById("menu_readingListSidebar"); + + let bookmarksMenubarItem = document.getElementById("menu_readingList"); + let bookmarksMenubarSeparator = document.getElementById("menu_readingListSeparator"); + + if (enabled) { + Assert.notEqual(sidebarBroadcaster.getAttribute("hidden"), "true", + "Sidebar broadcaster should not be hidden"); + Assert.notEqual(sidebarMenuitem.getAttribute("hidden"), "true", + "Sidebar menuitem should be visible"); + + // Currently disabled on OSX. + if (bookmarksMenubarItem) { + Assert.notEqual(bookmarksMenubarItem.getAttribute("hidden"), "true", + "RL bookmarks submenu in menubar should not be hidden"); + Assert.notEqual(sidebarMenuitem.getAttribute("hidden"), "true", + "RL bookmarks separator in menubar should be visible"); + } + } else { + Assert.equal(sidebarBroadcaster.getAttribute("hidden"), "true", + "Sidebar broadcaster should be hidden"); + Assert.equal(sidebarMenuitem.getAttribute("hidden"), "true", + "Sidebar menuitem should be hidden"); + Assert.equal(ReadingListUI.isSidebarOpen, false, + "ReadingListUI should not think sidebar is open"); + + // Currently disabled on OSX. + if (bookmarksMenubarItem) { + Assert.equal(bookmarksMenubarItem.getAttribute("hidden"), "true", + "RL bookmarks submenu in menubar should not be hidden"); + Assert.equal(sidebarMenuitem.getAttribute("hidden"), "true", + "RL bookmarks separator in menubar should be visible"); + } + } + + if (!enabled) { + Assert.equal(SidebarUI.isOpen, false, "Sidebar should not be open"); + } +} + +add_task(function*() { + info("Start with ReadingList disabled"); + RLUtils.enabled = false; + checkRLState(); + info("Enabling ReadingList"); + RLUtils.enabled = true; + checkRLState(); + + info("Opening ReadingList sidebar"); + yield ReadingListUI.showSidebar(); + Assert.ok(SidebarUI.isOpen, "Sidebar should be open"); + Assert.equal(SidebarUI.currentID, "readingListSidebar", "Sidebar should have ReadingList loaded"); + + info("Disabling ReadingList"); + RLUtils.enabled = false; + Assert.ok(!SidebarUI.isOpen, "Sidebar should be closed"); + checkRLState(); +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/head.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/head.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/browser/head.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/browser/head.js 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,15 @@ +XPCOMUtils.defineLazyModuleGetter(this, "ReadingList", + "resource:///modules/readinglist/ReadingList.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ReadingListTestUtils", + "resource://testing-common/ReadingListTestUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITestUtils", + "resource://testing-common/BrowserUITestUtils.jsm"); + + +XPCOMUtils.defineLazyGetter(this, "RLUtils", () => { + return ReadingListTestUtils; +}); + +XPCOMUtils.defineLazyGetter(this, "RLSidebarUtils", () => { + return new RLUtils.SidebarUtils(window, Assert); +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/ReadingListTestUtils.jsm thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/ReadingListTestUtils.jsm --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/ReadingListTestUtils.jsm 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/ReadingListTestUtils.jsm 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,159 @@ +"use strict"; + +this.EXPORTED_SYMBOLS = [ + "ReadingListTestUtils", +]; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource:///modules/readinglist/ReadingList.jsm"); + + +/** Preference name controlling whether the ReadingList feature is enabled/disabled. */ +const PREF_RL_ENABLED = "browser.readinglist.enabled"; + + +/** + * Utilities for testing the ReadingList sidebar. + */ +function SidebarUtils(window, assert) { + this.window = window; + this.Assert = assert; +} + +SidebarUtils.prototype = { + /** + * Reference to the RLSidebar object controlling the ReadingList sidebar UI. + * @type {object} + */ + get RLSidebar() { + return this.window.SidebarUI.browser.contentWindow.RLSidebar; + }, + + /** + * Reference to the list container element in the sidebar. + * @type {Element} + */ + get list() { + return this.RLSidebar.list; + }, + + /** + * Check that the number of elements in the list matches the expected count. + * @param {number} count - Expected number of items. + */ + expectNumItems(count) { + this.Assert.equal(this.list.childElementCount, count, + "Should have expected number of items in the sidebar list"); + }, + + /** + * Check all items in the sidebar list, ensuring the DOM matches the data. + */ + checkAllItems() { + for (let itemNode of this.list.children) { + this.checkSidebarItem(itemNode); + } + }, + + /** + * Run a series of sanity checks for an element in the list associated with + * an Item, ensuring the DOM matches the data. + */ + checkItem(node) { + let item = this.RLSidebar.getItemFromNode(node); + + this.Assert.ok(node.classList.contains("item"), + "Node should have .item class"); + this.Assert.equal(node.id, "item-" + item.id, + "Node should have correct ID"); + this.Assert.equal(node.getAttribute("title"), item.title + "\n" + item.url.spec, + "Node should have correct title attribute"); + this.Assert.equal(node.querySelector(".item-title").textContent, item.title, + "Node's title element's text should match item title"); + this.Assert.equal(node.querySelector(".item-domain").textContent, item.domain, + "Node's domain element's text should match item title"); + }, + + expectSelectedId(itemId) { + let selectedItem = this.RLSidebar.selectedItem; + if (itemId == null) { + this.Assert.equal(selectedItem, null, "Should have no selected item"); + } else { + this.Assert.notEqual(selectedItem, null, "selectedItem should not be null"); + let selectedId = this.RLSidebar.getItemIdFromNode(selectedItem); + this.Assert.equal(itemId, selectedId, "Should have currect item selected"); + } + }, + + expectActiveId(itemId) { + let activeItem = this.RLSidebar.activeItem; + if (itemId == null) { + this.Assert.equal(activeItem, null, "Should have no active item"); + } else { + this.Assert.notEqual(activeItem, null, "activeItem should not be null"); + let activeId = this.RLSidebar.getItemIdFromNode(activeItem); + this.Assert.equal(itemId, activeId, "Should have correct item active"); + } + }, +}; + + +/** + * Utilities for testing the ReadingList. + */ +this.ReadingListTestUtils = { + /** + * Whether the ReadingList feature is enabled or not. + * @type {boolean} + */ + get enabled() { + return Preferences.get(PREF_RL_ENABLED, false); + }, + set enabled(value) { + Preferences.set(PREF_RL_ENABLED, !!value); + }, + + /** + * Utilities for testing the ReadingList sidebar. + */ + SidebarUtils: SidebarUtils, + + /** + * Synthetically add an item to the ReadingList. + * @param {object|[object]} data - Object or array of objects to pass to the + * Item constructor. + * @return {Promise} Promise that gets fulfilled with the item or items added. + */ + addItem(data) { + if (Array.isArray(data)) { + let promises = []; + for (let itemData of data) { + promises.push(this.addItem(itemData)); + } + return Promise.all(promises); + } + + return new Promise(resolve => { + let item = new ReadingList.Item(data); + ReadingList._items.push(item); + ReadingList._notifyListeners("onItemAdded", item); + resolve(item); + }); + }, + + /** + * Cleanup all data, resetting to a blank state. + */ + cleanup() { + return new Promise(resolve => { + ReadingList._items = []; + ReadingList._listeners.clear(); + Preferences.reset(PREF_RL_ENABLED); + resolve(); + }); + }, +}; diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/xpcshell/head.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/xpcshell/head.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/xpcshell/head.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/xpcshell/head.js 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,7 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/xpcshell/test_scheduler.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/xpcshell/test_scheduler.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/xpcshell/test_scheduler.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/xpcshell/test_scheduler.js 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,146 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout', + 'resource://gre/modules/Timer.jsm'); + +// Setup logging prefs before importing the scheduler module. +Services.prefs.setCharPref("readinglist.log.appender.dump", "Trace"); + +let {createTestableScheduler} = Cu.import("resource:///modules/readinglist/Scheduler.jsm", {}); +Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/Timer.jsm"); + +// Log rotation needs a profile dir. +do_get_profile(); + +let prefs = new Preferences("readinglist.scheduler."); + +function promiseObserver(topic) { + return new Promise(resolve => { + let obs = (subject, topic, data) => { + Services.obs.removeObserver(obs, topic); + resolve(data); + } + Services.obs.addObserver(obs, topic, false); + }); +} + +function createScheduler(options) { + // avoid typos in the test and other footguns in the options. + let allowedOptions = ["expectedDelay", "expectNewTimer", "syncFunction"]; + for (let key of Object.keys(options)) { + if (!allowedOptions.includes(key)) { + throw new Error("Invalid option " + key); + } + } + let scheduler = createTestableScheduler(); + // make our hooks + let syncFunction = options.syncFunction || Promise.resolve; + scheduler._engine.sync = syncFunction; + // we expect _setTimeout to be called *twice* - first is the initial sync, + // and there's no need to test the delay used for that. options.expectedDelay + // is to check the *subsequent* timer. + let numCalls = 0; + scheduler._setTimeout = function(delay) { + ++numCalls; + print("Test scheduler _setTimeout call number " + numCalls + " with delay=" + delay); + switch (numCalls) { + case 1: + // this is the first and boring schedule as it initializes - do nothing + // other than return a timer that fires immediately. + return setTimeout(() => scheduler._doSync(), 0); + break; + case 2: + // This is the one we are interested in, so check things. + if (options.expectedDelay) { + // a little slop is OK as it takes a few ms to actually set the timer + ok(Math.abs(options.expectedDelay * 1000 - delay) < 500, [options.expectedDelay * 1000, delay]); + } + // and return a timeout that "never" fires + return setTimeout(() => scheduler._doSync(), 10000000); + break; + default: + // This is unexpected! + ok(false, numCalls); + } + }; + // And a callback made once we've determined the next delay. This is always + // called even if _setTimeout isn't (due to no timer being created) + scheduler._onAutoReschedule = () => { + // Most tests expect a new timer, so this is "opt out" + let expectNewTimer = options.expectNewTimer === undefined ? true : options.expectNewTimer; + ok(expectNewTimer ? scheduler._timer : !scheduler._timer); + } + // calling .init fires things off... + scheduler.init(); + return scheduler; +} + +add_task(function* testSuccess() { + // promises which resolve once we've got all the expected notifications. + let allNotifications = [ + promiseObserver("readinglist:sync:start"), + promiseObserver("readinglist:sync:finish"), + ]; + // New delay should be "as regularly scheduled". + prefs.set("schedule", 100); + let scheduler = createScheduler({expectedDelay: 100}); + yield Promise.all(allNotifications); + scheduler.finalize(); +}); + +add_task(function* testOffline() { + let scheduler = createScheduler({expectNewTimer: false}); + Services.io.offline = true; + ok(!scheduler._canSync(), "_canSync is false when offline.") + ok(!scheduler._timer, "there is no current timer while offline.") + Services.io.offline = false; + ok(scheduler._canSync(), "_canSync is true when online.") + ok(scheduler._timer, "there is a new timer when back online.") + scheduler.finalize(); +}); + +add_task(function* testRetryableError() { + let allNotifications = [ + promiseObserver("readinglist:sync:start"), + promiseObserver("readinglist:sync:error"), + ]; + prefs.set("retry", 10); + let scheduler = createScheduler({ + expectedDelay: 10, + syncFunction: () => Promise.reject("transient"), + }); + yield Promise.all(allNotifications); + scheduler.finalize(); +}); + +add_task(function* testAuthError() { + prefs.set("retry", 10); + // We expect an auth error to result in no new timer (as it's waiting for + // some indication it can proceed), but with the next delay being a normal + // "retry" interval (so when we can proceed it is probably already stale, so + // is effectively "immediate") + let scheduler = createScheduler({ + expectedDelay: 10, + syncFunction: () => { + return Promise.reject(ReadingListScheduler._engine.ERROR_AUTHENTICATION); + }, + expectNewTimer: false + }); + // XXX - TODO - send an observer that "unblocks" us and ensure we actually + // do unblock. + scheduler.finalize(); +}); + +add_task(function* testBackoff() { + let scheduler = createScheduler({expectedDelay: 1000}); + Services.obs.notifyObservers(null, "readinglist:backoff-requested", 1000); + // XXX - this needs a little love as nothing checks createScheduler actually + // made the checks we think it does. + scheduler.finalize(); +}); + +function run_test() { + run_next_test(); +} diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/xpcshell/xpcshell.ini thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/xpcshell/xpcshell.ini --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/readinglist/test/xpcshell/xpcshell.ini 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/readinglist/test/xpcshell/xpcshell.ini 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,5 @@ +[DEFAULT] +head = head.js +firefox-appdir = browser + +[test_scheduler.js] diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/search/content/search.xml thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/search/content/search.xml --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/search/content/search.xml 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/search/content/search.xml 2015-02-28 15:03:01.000000000 +0000 @@ -826,6 +826,8 @@ element = document.createElementNS(kXULNS, "menuseparator"); cxmenu.appendChild(element); + this.setAttribute("aria-owns", this.popup.id); + var insertLocation = cxmenu.firstChild; while (insertLocation.nextSibling && insertLocation.getAttribute("cmd") != "cmd_paste") @@ -1019,9 +1021,11 @@ if (val && !val.classList.contains("dummy")) { val.setAttribute("selected", "true"); this._selectedButton = val; + this.setAttribute("aria-activedescendant", val.id); return; } + this.removeAttribute("aria-activedescendant"); this._selectedButton = null; ]]> @@ -1063,6 +1067,12 @@ let selectedButton = this.selectedButton; let buttons = this.getSelectableButtons(aCycleEngines); + let suggestionsHidden; + if (!aSkipSuggestions && popup.hasAttribute("showonlysettings")) { + aSkipSuggestions = true; + suggestionsHidden = true; + } + // If the last suggestion is selected, DOWN selects the first button. if (!aSkipSuggestions && aForward && popup.selectedIndex + 1 == popup.view.rowCount) { @@ -1083,7 +1093,7 @@ else this.selectedButton = null; - if (this.selectedButton || aCycleEngines) + if (this.selectedButton || aCycleEngines || suggestionsHidden) return true; // Set the selectedIndex to something that will make @@ -1104,7 +1114,7 @@ return true; } - if (!aForward && (aCycleEngines || + if (!aForward && (aCycleEngines || suggestionsHidden || (!aSkipSuggestions && popup.selectedIndex == -1))) { // the last button. this.selectedButton = buttons[buttons.length - 1]; @@ -1123,15 +1133,6 @@ if (!popup.popupOpen) return; - if (aEvent.keyCode == KeyEvent.DOM_VK_DOWN && - popup.hasAttribute("showonlysettings")) { - // If the suggestion tree is collapsed, don't let the down arrow - // key event reach the tree. - aEvent.preventDefault(); - aEvent.stopPropagation(); - return; - } - let list = document.getAnonymousElementByAttribute(popup, "anonid", "search-panel-one-offs"); if (!list) // remove this check when removing the old search UI. diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/search/test/browser.ini thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/search/test/browser.ini --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/search/test/browser.ini 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/search/test/browser.ini 2015-02-28 15:03:01.000000000 +0000 @@ -44,4 +44,5 @@ [browser_searchbar_openpopup.js] skip-if = os == "linux" || e10s # Linux has different focus behaviours and e10s seems to have timing issues. [browser_searchbar_keyboard_navigation.js] +[browser_searchbar_smallpanel_keyboard_navigation.js] [browser_webapi.js] diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/search/test/browser_searchbar_openpopup.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/search/test/browser_searchbar_openpopup.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/search/test/browser_searchbar_openpopup.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/search/test/browser_searchbar_openpopup.js 2015-02-28 15:03:01.000000000 +0000 @@ -191,6 +191,27 @@ textbox.value = ""; }); +// Moving focus away from the search box should close the small popup +add_task(function* focus_change_closes_small_popup() { + gURLBar.focus(); + + let promise = promiseEvent(searchPopup, "popupshown"); + // For some reason sending the mouse event immediately doesn't open the popup. + SimpleTest.executeSoon(() => { + EventUtils.synthesizeMouseAtCenter(searchIcon, {}); + }); + yield promise; + is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup"); + + is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar"); + + promise = promiseEvent(searchPopup, "popuphidden"); + let promise2 = promiseEvent(searchbar, "blur"); + EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + yield promise; + yield promise2; +}); + // Pressing escape should close the popup. add_task(function* escape_closes_popup() { gURLBar.focus(); @@ -431,6 +452,28 @@ is(textbox.selectionEnd, 9, "Should have moved the caret (selectionEnd after right)"); is(searchPopup.state, "open", "Popup should still be open"); + // Ensure caret movement works while a suggestion is selected. + is(textbox.popup.selectedIndex, -1, "No selected item in list"); + EventUtils.synthesizeKey("VK_DOWN", {}); + is(textbox.popup.selectedIndex, 0, "Selected item in list"); + is(textbox.selectionStart, 9, "Should have moved the caret to the end (selectionStart after selection)"); + is(textbox.selectionEnd, 9, "Should have moved the caret to the end (selectionEnd after selection)"); + + EventUtils.synthesizeKey("VK_LEFT", {}); + is(textbox.selectionStart, 8, "Should have moved the caret again (selectionStart after left)"); + is(textbox.selectionEnd, 8, "Should have moved the caret again (selectionEnd after left)"); + is(searchPopup.state, "open", "Popup should still be open"); + + EventUtils.synthesizeKey("VK_LEFT", {}); + is(textbox.selectionStart, 7, "Should have moved the caret (selectionStart after left)"); + is(textbox.selectionEnd, 7, "Should have moved the caret (selectionEnd after left)"); + is(searchPopup.state, "open", "Popup should still be open"); + + EventUtils.synthesizeKey("VK_RIGHT", {}); + is(textbox.selectionStart, 8, "Should have moved the caret (selectionStart after right)"); + is(textbox.selectionEnd, 8, "Should have moved the caret (selectionEnd after right)"); + is(searchPopup.state, "open", "Popup should still be open"); + if (navigator.platform.indexOf("Mac") == -1) { EventUtils.synthesizeKey("VK_HOME", {}); is(textbox.selectionStart, 0, "Should have moved the caret (selectionStart after home)"); @@ -442,4 +485,6 @@ promise = promiseEvent(searchPopup, "popuphidden"); EventUtils.synthesizeKey("VK_ESCAPE", {}); yield promise; + + textbox.value = ""; }); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js 2015-02-28 15:03:01.000000000 +0000 @@ -0,0 +1,377 @@ +// Tests that keyboard navigation in the search panel works as designed. + +const searchbar = document.getElementById("searchbar"); +const textbox = searchbar._textbox; +const searchPopup = document.getElementById("PopupSearchAutoComplete"); +const searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid", + "searchbar-search-button"); + +const kValues = ["foo1", "foo2", "foo3"]; + +// Get an array of the one-off buttons. +function getOneOffs() { + let oneOffs = []; + let oneOff = document.getAnonymousElementByAttribute(searchPopup, "anonid", + "search-panel-one-offs"); + for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) { + if (oneOff.classList.contains("dummy")) + break; + oneOffs.push(oneOff); + } + + return oneOffs; +} + +function getOpenSearchItems() { + let os = []; + + let addEngineList = + document.getAnonymousElementByAttribute(searchPopup, "anonid", + "add-engines"); + for (let item = addEngineList.firstChild; item; item = item.nextSibling) + os.push(item); + + return os; +} + +add_task(function* init() { + yield promiseNewEngine("testEngine.xml"); + + // First cleanup the form history in case other tests left things there. + yield new Promise((resolve, reject) => { + info("cleanup the search history"); + searchbar.FormHistory.update({op: "remove", fieldname: "searchbar-history"}, + {handleCompletion: resolve, + handleError: reject}); + }); + + yield new Promise((resolve, reject) => { + info("adding search history values: " + kValues); + let ops = kValues.map(value => { return {op: "add", + fieldname: "searchbar-history", + value: value} + }); + searchbar.FormHistory.update(ops, { + handleCompletion: function() { + registerCleanupFunction(() => { + info("removing search history values: " + kValues); + let ops = + kValues.map(value => { return {op: "remove", + fieldname: "searchbar-history", + value: value} + }); + searchbar.FormHistory.update(ops); + }); + resolve(); + }, + handleError: reject + }); + }); +}); + + +add_task(function* test_arrows() { + let promise = promiseEvent(searchPopup, "popupshown"); + info("Opening search panel"); + EventUtils.synthesizeMouseAtCenter(searchIcon, {}); + yield promise; +info("textbox.mController.searchString = " + textbox.mController.searchString); + is(textbox.mController.searchString, "", "The search string should be empty"); + + // Check the initial state of the panel before sending keyboard events. + is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup"); + // Having suggestions populated (but hidden) is important, because if there + // are none we can't ensure the keyboard events don't reach them. + is(searchPopup.view.rowCount, kValues.length, "There should be 3 suggestions"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + + // The tests will be less meaningful if the first, second, last, and + // before-last one-off buttons aren't different. We should always have more + // than 4 default engines, but it's safer to check this assumption. + let oneOffs = getOneOffs(); + ok(oneOffs.length >= 4, "we have at least 4 one-off buttons displayed") + + ok(!textbox.selectedButton, "no one-off button should be selected"); + + // Pressing should select the first one-off. + EventUtils.synthesizeKey("VK_DOWN", {}); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + is(textbox.value, "", "the textfield value should be unmodified"); + + // now cycle through the one-off items, the first one is already selected. + for (let i = 0; i < oneOffs.length; ++i) { + is(textbox.selectedButton, oneOffs[i], + "the one-off button #" + (i + 1) + " should be selected"); + EventUtils.synthesizeKey("VK_DOWN", {}); + } + + is(textbox.selectedButton.getAttribute("anonid"), "search-settings", + "the settings item should be selected"); + EventUtils.synthesizeKey("VK_DOWN", {}); + + // We should now be back to the initial situation. + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + ok(!textbox.selectedButton, "no one-off button should be selected"); + + info("now test the up arrow key"); + EventUtils.synthesizeKey("VK_UP", {}); + is(textbox.selectedButton.getAttribute("anonid"), "search-settings", + "the settings item should be selected"); + + // cycle through the one-off items, the first one is already selected. + for (let i = oneOffs.length; i; --i) { + EventUtils.synthesizeKey("VK_UP", {}); + is(textbox.selectedButton, oneOffs[i - 1], + "the one-off button #" + i + " should be selected"); + } + + // Another press on up should clear the one-off selection. + EventUtils.synthesizeKey("VK_UP", {}); + ok(!textbox.selectedButton, "no one-off button should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + is(textbox.value, "", "the textfield value should be unmodified"); +}); + +add_task(function* test_tab() { + is(Services.focus.focusedElement, textbox.inputField, + "the search bar should be focused"); // from the previous test. + + let oneOffs = getOneOffs(); + ok(!textbox.selectedButton, "no one-off button should be selected"); + + // Pressing tab should select the first one-off without selecting suggestions. + // now cycle through the one-off items, the first one is already selected. + for (let i = 0; i < oneOffs.length; ++i) { + EventUtils.synthesizeKey("VK_TAB", {}); + is(textbox.selectedButton, oneOffs[i], + "the one-off button #" + (i + 1) + " should be selected"); + } + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + is(textbox.value, "", "the textfield value should be unmodified"); + + // One more selects the settings button. + EventUtils.synthesizeKey("VK_TAB", {}); + is(textbox.selectedButton.getAttribute("anonid"), "search-settings", + "the settings item should be selected"); + + // Pressing tab again should close the panel... + let promise = promiseEvent(searchPopup, "popuphidden"); + EventUtils.synthesizeKey("VK_TAB", {}); + yield promise; + + // ... and move the focus out of the searchbox. + isnot(Services.focus.focusedElement, textbox.inputField, + "the search bar no longer be focused"); +}); + +add_task(function* test_shift_tab() { + // First reopen the panel. + let promise = promiseEvent(searchPopup, "popupshown"); + info("Opening search panel"); + SimpleTest.executeSoon(() => { + EventUtils.synthesizeMouseAtCenter(searchIcon, {}); + }); + yield promise; + + let oneOffs = getOneOffs(); + ok(!textbox.selectedButton, "no one-off button should be selected"); + is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup"); + + // Press up once to select the last button. + EventUtils.synthesizeKey("VK_UP", {}); + is(textbox.selectedButton.getAttribute("anonid"), "search-settings", + "the settings item should be selected"); + + // Press up again to select the last one-off button. + EventUtils.synthesizeKey("VK_UP", {}); + + // Pressing shift+tab should cycle through the one-off items. + for (let i = oneOffs.length - 1; i >= 0; --i) { + is(textbox.selectedButton, oneOffs[i], + "the one-off button #" + (i + 1) + " should be selected"); + if (i) + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}); + } + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + is(textbox.value, "", "the textfield value should be unmodified"); + + // Pressing shift+tab again should close the panel... + promise = promiseEvent(searchPopup, "popuphidden"); + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}); + yield promise; + + // ... and move the focus out of the searchbox. + isnot(Services.focus.focusedElement, textbox.inputField, + "the search bar no longer be focused"); +}); + +add_task(function* test_alt_down() { + // First reopen the panel. + let promise = promiseEvent(searchPopup, "popupshown"); + info("Opening search panel"); + SimpleTest.executeSoon(() => { + EventUtils.synthesizeMouseAtCenter(searchIcon, {}); + }); + yield promise; + + // and check it's in a correct initial state. + is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup"); + ok(!textbox.selectedButton, "no one-off button should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + is(textbox.value, "", "the textfield value should be unmodified"); + + // Pressing alt+down should select the first one-off without selecting suggestions + // and cycle through the one-off items. + let oneOffs = getOneOffs(); + for (let i = 0; i < oneOffs.length; ++i) { + EventUtils.synthesizeKey("VK_DOWN", {altKey: true}); + is(textbox.selectedButton, oneOffs[i], + "the one-off button #" + (i + 1) + " should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + } + + // One more alt+down keypress and nothing should be selected. + EventUtils.synthesizeKey("VK_DOWN", {altKey: true}); + ok(!textbox.selectedButton, "no one-off button should be selected"); + + // another one and the first one-off should be selected. + EventUtils.synthesizeKey("VK_DOWN", {altKey: true}); + is(textbox.selectedButton, oneOffs[0], + "the first one-off button should be selected"); + + // Clear the selection with an alt+up keypress + EventUtils.synthesizeKey("VK_UP", {altKey: true}); + ok(!textbox.selectedButton, "no one-off button should be selected"); +}); + +add_task(function* test_alt_up() { + // Check the initial state of the panel + ok(!textbox.selectedButton, "no one-off button should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + is(textbox.value, "", "the textfield value should be unmodified"); + + // Pressing alt+up should select the last one-off without selecting suggestions + // and cycle up through the one-off items. + let oneOffs = getOneOffs(); + for (let i = oneOffs.length - 1; i >= 0; --i) { + EventUtils.synthesizeKey("VK_UP", {altKey: true}); + is(textbox.selectedButton, oneOffs[i], + "the one-off button #" + (i + 1) + " should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + } + + // One more alt+down keypress and nothing should be selected. + EventUtils.synthesizeKey("VK_UP", {altKey: true}); + ok(!textbox.selectedButton, "no one-off button should be selected"); + + // another one and the last one-off should be selected. + EventUtils.synthesizeKey("VK_UP", {altKey: true}); + is(textbox.selectedButton, oneOffs[oneOffs.length - 1], + "the last one-off button should be selected"); + + // Cleanup for the next test. + EventUtils.synthesizeKey("VK_DOWN", {}); + is(textbox.selectedButton.getAttribute("anonid"), "search-settings", + "the settings item should be selected"); + EventUtils.synthesizeKey("VK_DOWN", {}); + ok(!textbox.selectedButton, "no one-off should be selected anymore"); +}); + +add_task(function* test_tab_and_arrows() { + // Check the initial state is as expected. + ok(!textbox.selectedButton, "no one-off button should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + is(textbox.value, "", "the textfield value should be unmodified"); + + // After pressing down, the first one-off should be selected. + let oneOffs = getOneOffs(); + EventUtils.synthesizeKey("VK_DOWN", {}); + is(textbox.selectedButton, oneOffs[0], + "the first one-off button should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + + // After pressing tab, the second one-off should be selected. + EventUtils.synthesizeKey("VK_TAB", {}); + is(textbox.selectedButton, oneOffs[1], + "the second one-off button should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + + // After pressing up, the first one-off should be selected again. + EventUtils.synthesizeKey("VK_UP", {}); + is(textbox.selectedButton, oneOffs[0], + "the first one-off button should be selected"); + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + + // Finally close the panel. + let promise = promiseEvent(searchPopup, "popuphidden"); + searchPopup.hidePopup(); + yield promise; +}); + +add_task(function* test_open_search() { + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + + let deferred = Promise.defer(); + let browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + deferred.resolve(); + }, true); + + let rootDir = getRootDirectory(gTestPath); + content.location = rootDir + "opensearch.html"; + + yield deferred.promise; + + let promise = promiseEvent(searchPopup, "popupshown"); + info("Opening search panel"); + EventUtils.synthesizeMouseAtCenter(searchIcon, {}); + yield promise; + is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup"); + + let engines = getOpenSearchItems(); + is(engines.length, 2, "the opensearch.html page exposes 2 engines") + + // Check that there's initially no selection. + is(searchPopup.selectedIndex, -1, "no suggestion should be selected"); + ok(!textbox.selectedButton, "no button should be selected"); + + // Pressing up once selects the setting button... + EventUtils.synthesizeKey("VK_UP", {}); + is(textbox.selectedButton.getAttribute("anonid"), "search-settings", + "the settings item should be selected"); + + // ...and then pressing up selects open search engines. + for (let i = engines.length; i; --i) { + EventUtils.synthesizeKey("VK_UP", {}); + let selectedButton = textbox.selectedButton; + is(selectedButton, engines[i - 1], + "the engine #" + i + " should be selected"); + ok(selectedButton.classList.contains("addengine-item"), + "the button is themed as an engine item"); + } + + // Pressing up again should select the last one-off button. + EventUtils.synthesizeKey("VK_UP", {}); + is(textbox.selectedButton, getOneOffs().pop(), + "the last one-off button should be selected"); + + info("now check that the down key navigates open search items as expected"); + for (let i = 0; i < engines.length; ++i) { + EventUtils.synthesizeKey("VK_DOWN", {}); + is(textbox.selectedButton, engines[i], + "the engine #" + (i + 1) + " should be selected"); + } + + // Pressing down on the last engine item selects the settings button. + EventUtils.synthesizeKey("VK_DOWN", {}); + is(textbox.selectedButton.getAttribute("anonid"), "search-settings", + "the settings item should be selected"); + + promise = promiseEvent(searchPopup, "popuphidden"); + searchPopup.hidePopup(); + yield promise; + + gBrowser.removeCurrentTab(); +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/sessionstore/SessionStore.jsm thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/sessionstore/SessionStore.jsm --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/sessionstore/SessionStore.jsm 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/sessionstore/SessionStore.jsm 2015-02-28 15:03:01.000000000 +0000 @@ -688,7 +688,6 @@ Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null); } - delete browser.__SS_restore_data; delete browser.__SS_data; SessionStoreInternal._resetLocalTabRestoringState(tab); @@ -2683,26 +2682,16 @@ }, /** - * Restores the specified tab. If the tab can't be restored (eg, no history or - * calling gotoIndex fails), then state changes will be rolled back. - * This method will check if gTabsProgressListener is attached to the tab's - * window, ensuring that we don't get caught without one. - * This method removes the session history listener right before starting to - * attempt a load. This will prevent cases of "stuck" listeners. - * If this method returns false, then it is up to the caller to decide what to - * do. In the common case (restoreNextTab), we will want to then attempt to - * restore the next tab. In the other case (selecting the tab, reloading the - * tab), the caller doesn't actually want to do anything if no page is loaded. + * Kicks off restoring the given tab. * * @param aTab * the tab to restore - * - * @returns true/false indicating whether or not a load actually happened + * @param aLoadArguments + * optional load arguments used for loadURI() */ restoreTabContent: function (aTab, aLoadArguments = null) { let window = aTab.ownerDocument.defaultView; let browser = aTab.linkedBrowser; - let tabData = browser.__SS_data; // Make sure that this tab is removed from the priority queue. TabRestoreQueue.remove(aTab); @@ -2715,17 +2704,6 @@ browser.removeAttribute("pending"); aTab.removeAttribute("pending"); - let activeIndex = tabData.index - 1; - - // Attach data that will be restored on "load" event, after tab is restored. - if (tabData.entries.length) { - // restore those aspects of the currently active documents which are not - // preserved in the plain history entries (mainly scroll state and text data) - browser.__SS_restore_data = tabData.entries[activeIndex] || {}; - } else { - browser.__SS_restore_data = {}; - } - browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent", {loadArguments: aLoadArguments}); }, @@ -2865,7 +2843,7 @@ } var sidebar = aWindow.document.getElementById("sidebar-box"); if (sidebar.getAttribute("sidebarcommand") != aSidebar) { - aWindow.toggleSidebar(aSidebar); + aWindow.SidebarUI.show(aSidebar); } // since resizing/moving a window brings it to the foreground, // we might want to re-focus the last focused window diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/tabview/test/browser_tabview_bug595191.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/tabview/test/browser_tabview_bug595191.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/tabview/test/browser_tabview_bug595191.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/tabview/test/browser_tabview_bug595191.js 2015-02-28 15:03:01.000000000 +0000 @@ -45,7 +45,7 @@ } contentWindow.addEventListener("tabviewsearchdisabled", onSearchDisabled, false); - EventUtils.synthesizeKey("VK_ESCAPE", { }, contentWindow); + EventUtils.synthesizeKey("KEY_Escape", { code: "Escape" }, contentWindow); } function toggleTabViewTest(contentWindow) { @@ -57,5 +57,6 @@ } contentWindow.addEventListener("tabviewhidden", onTabViewHidden, false); // Use keyboard shortcut to toggle back to browser view - EventUtils.synthesizeKey("e", { accelKey: true, shiftKey: true }); + EventUtils.synthesizeKey("e", { accelKey: true, shiftKey: true, + code: "KeyE", keyCode: KeyboardEvent.DOM_VK_E }, contentWindow); } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/tabview/test/browser_tabview_bug595518.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/tabview/test/browser_tabview_bug595518.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/tabview/test/browser_tabview_bug595518.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/tabview/test/browser_tabview_bug595518.js 2015-02-28 15:03:01.000000000 +0000 @@ -28,7 +28,7 @@ // verify that the keyboard combo works (this is the crux of bug 595518) // Prepare the key combo window.addEventListener("tabviewshown", onTabViewShown, false); - EventUtils.synthesizeKey("e", { accelKey: true, shiftKey: true }, contentWindow); + EventUtils.synthesizeKey("e", { accelKey: true, shiftKey: true, code: "KeyE", keyCode: KeyboardEvent.DOM_VK_E }, contentWindow); } let onTabViewShown = function() { diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/uitour/test/browser.ini thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/uitour/test/browser.ini --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/uitour/test/browser.ini 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/uitour/test/browser.ini 2015-02-28 15:03:02.000000000 +0000 @@ -5,6 +5,7 @@ uitour.html ../UITour-lib.js +[browser_no_tabs.js] [browser_UITour.js] skip-if = os == "linux" || e10s # Intermittent failures, bug 951965 [browser_UITour2.js] diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/uitour/test/browser_no_tabs.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/uitour/test/browser_no_tabs.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/uitour/test/browser_no_tabs.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/uitour/test/browser_no_tabs.js 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,110 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const HTML_NS = "http://www.w3.org/1999/xhtml"; +const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + +/** + * Create a frame in the |hiddenDOMWindow| to host a |browser|, then load the URL in the + * latter. + * + * @param aURL + * The URL to open in the browser. + **/ +function createHiddenBrowser(aURL) { + let deferred = Promise.defer(); + let hiddenDoc = Services.appShell.hiddenDOMWindow.document; + + // Create a HTML iframe with a chrome URL, then this can host the browser. + let iframe = hiddenDoc.createElementNS(HTML_NS, "iframe"); + iframe.setAttribute("src", "chrome://global/content/mozilla.xhtml"); + iframe.addEventListener("load", function onLoad() { + iframe.removeEventListener("load", onLoad, true); + + let browser = iframe.contentDocument.createElementNS(XUL_NS, "browser"); + browser.setAttribute("type", "content"); + browser.setAttribute("disableglobalhistory", "true"); + browser.setAttribute("src", aURL); + + iframe.contentDocument.documentElement.appendChild(browser); + deferred.resolve({frame: iframe, browser: browser}); + }, true); + + hiddenDoc.documentElement.appendChild(iframe); + return deferred.promise; +}; + +/** + * Remove the browser and the iframe. + * + * @param aFrame + * The iframe to dismiss. + * @param aBrowser + * The browser to dismiss. + */ +function destroyHiddenBrowser(aFrame, aBrowser) { + // Dispose of the hidden browser. + aBrowser.remove(); + + // Take care of the frame holding our invisible browser. + if (!Cu.isDeadWrapper(aFrame)) { + aFrame.remove(); + } +}; + +/** + * Test that UITour works when called when no tabs are available (e.g., when using windowless + * browsers). + */ +add_task(function* test_windowless_UITour(){ + // Get the URL for the test page. + let pageURL = getRootDirectory(gTestPath) + "uitour.html"; + + // Allow the URL to use the UITour. + info("Adding UITour permission to the test page."); + let pageURI = Services.io.newURI(pageURL, null, null); + Services.perms.add(pageURI, "uitour", Services.perms.ALLOW_ACTION); + + // UITour's ping will resolve this promise. + let deferredPing = Promise.defer(); + + // Create a windowless browser and test that UITour works in it. + let browserPromise = createHiddenBrowser(pageURL); + browserPromise.then(frameInfo => { + isnot(frameInfo.browser, null, "The browser must exist and not be null."); + + // Load UITour frame script. + frameInfo.browser.messageManager.loadFrameScript( + "chrome://browser/content/content-UITour.js", false); + + // When the page loads, try to use UITour API. + frameInfo.browser.addEventListener("load", function loadListener() { + info("The test page was correctly loaded."); + + frameInfo.browser.removeEventListener("load", loadListener, true); + + // Get a reference to the UITour API. + info("Testing access to the UITour API."); + let contentWindow = Cu.waiveXrays(frameInfo.browser.contentDocument.defaultView); + isnot(contentWindow, null, "The content window must exist and not be null."); + + let uitourAPI = contentWindow.Mozilla.UITour; + + // Test the UITour API with a ping. + uitourAPI.ping(function() { + info("Ping response received from the UITour API."); + + // Make sure to clean up. + destroyHiddenBrowser(frameInfo.frame, frameInfo.browser); + + // Resolve our promise. + deferredPing.resolve(); + }); + }, true); + }); + + // Wait for the UITour ping to complete. + yield deferredPing.promise; +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/uitour/UITour.jsm thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/uitour/UITour.jsm --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/components/uitour/UITour.jsm 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/components/uitour/UITour.jsm 2015-02-28 15:03:02.000000000 +0000 @@ -344,6 +344,15 @@ onPageEvent: function(aMessage, aEvent) { let browser = aMessage.target; let window = browser.ownerDocument.defaultView; + + // Does the window have tabs? We need to make sure since windowless browsers do + // not have tabs. + if (!window.gBrowser) { + // When using windowless browsers we don't have a valid |window|. If that's the case, + // use the most recent window as a target for UITour functions (see Bug 1111022). + window = Services.wm.getMostRecentWindow("navigator:browser"); + } + let tab = window.gBrowser.getTabForBrowser(browser); let messageManager = browser.messageManager; @@ -676,10 +685,7 @@ } this.tourBrowsersByWindow.get(window).add(browser); - // We don't have a tab if we're in a without a tab. - if (tab) { - tab.addEventListener("TabClose", this); - } + Services.obs.addObserver(this, "message-manager-disconnect", false); window.addEventListener("SSWindowClosing", this); @@ -695,13 +701,6 @@ break; } - case "TabClose": { - let tab = aEvent.target; - let window = tab.ownerDocument.defaultView; - this.teardownTourForBrowser(window, tab.linkedBrowser, true); - break; - } - case "TabSelect": { let window = aEvent.target.ownerDocument.defaultView; @@ -733,6 +732,37 @@ } }, + observe: function(aSubject, aTopic, aData) { + log.debug("observe: aTopic =", aTopic); + switch (aTopic) { + // The browser message manager is disconnected when the is + // destroyed and we want to teardown at that point. + case "message-manager-disconnect": { + let winEnum = Services.wm.getEnumerator("navigator:browser"); + while (winEnum.hasMoreElements()) { + let window = winEnum.getNext(); + if (window.closed) + continue; + + let tourBrowsers = this.tourBrowsersByWindow.get(window); + if (!tourBrowsers) + continue; + + for (let browser of tourBrowsers) { + let messageManager = browser.messageManager; + if (aSubject != messageManager) { + continue; + } + + this.teardownTourForBrowser(window, browser, true); + return; + } + } + break; + } + } + }, + setTelemetryBucket: function(aPageID) { let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID; BrowserUITelemetry.setBucket(bucket); @@ -766,14 +796,8 @@ } let openTourBrowsers = this.tourBrowsersByWindow.get(aWindow); - if (aTourPageClosing) { - let tab = aWindow.gBrowser.getTabForBrowser(aBrowser); - if (tab) { // Handle standalone - tab.removeEventListener("TabClose", this); - if (openTourBrowsers) { - openTourBrowsers.delete(aBrowser); - } - } + if (aTourPageClosing && openTourBrowsers) { + openTourBrowsers.delete(aBrowser); } this.hideHighlight(aWindow); @@ -814,9 +838,6 @@ let pageID = this.pageIDSourceBrowsers.get(browser); this.setExpiringTelemetryBucket(pageID, "closed"); } - - let tab = aWindow.gBrowser.getTabForBrowser(browser); - tab.removeEventListener("TabClose", this); } } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/config/version.txt thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/config/version.txt --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/config/version.txt 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/config/version.txt 2015-02-28 15:03:02.000000000 +0000 @@ -1 +1 @@ -38.0a1 +39.0a1 diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/app-manager/app-validator.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/app-manager/app-validator.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/app-manager/app-validator.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/app-manager/app-validator.js 2015-02-28 15:03:02.000000000 +0000 @@ -12,8 +12,9 @@ let XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1"); let strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties"); -function AppValidator(project) { - this.project = project; +function AppValidator({ type, location }) { + this.type = type; + this.location = location; this.errors = []; this.warnings = []; } @@ -27,7 +28,7 @@ }; AppValidator.prototype._getPackagedManifestFile = function () { - let manifestFile = FileUtils.File(this.project.location); + let manifestFile = FileUtils.File(this.location); if (!manifestFile.exists()) { this.error(strings.GetStringFromName("validator.nonExistingFolder")); return null; @@ -149,12 +150,12 @@ AppValidator.prototype._getManifest = function () { let manifestURL; - if (this.project.type == "packaged") { + if (this.type == "packaged") { manifestURL = this._getPackagedManifestURL(); if (!manifestURL) return promise.resolve(null); - } else if (this.project.type == "hosted") { - manifestURL = this.project.location; + } else if (this.type == "hosted") { + manifestURL = this.location; try { Services.io.newURI(manifestURL, null, null); } catch(e) { @@ -162,7 +163,7 @@ return promise.resolve(null); } } else { - this.error(strings.formatStringFromName("validator.invalidProjectType", [this.project.type], 1)); + this.error(strings.formatStringFromName("validator.invalidProjectType", [this.type], 1)); return promise.resolve(null); } return this._fetchManifest(manifestURL); @@ -181,11 +182,11 @@ }; AppValidator.prototype._getOriginURL = function () { - if (this.project.type == "packaged") { + if (this.type == "packaged") { let manifestURL = Services.io.newURI(this.manifestURL, null, null); return Services.io.newURI(".", null, manifestURL).spec; - } else if (this.project.type == "hosted") { - return Services.io.newURI(this.project.location, null, null).prePath; + } else if (this.type == "hosted") { + return Services.io.newURI(this.location, null, null).prePath; } }; @@ -203,9 +204,9 @@ } let origin = this._getOriginURL(); let path; - if (this.project.type == "packaged") { + if (this.type == "packaged") { path = "." + ( manifest.launch_path || "/index.html" ); - } else if (this.project.type == "hosted") { + } else if (this.type == "hosted") { path = manifest.launch_path || "/"; } let indexURL; @@ -251,7 +252,7 @@ let appType = manifest.type || "web"; if (["web", "trusted", "privileged", "certified"].indexOf(appType) === -1) { this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1)); - } else if (this.project.type == "hosted" && + } else if (this.type == "hosted" && ["certified", "privileged"].indexOf(appType) !== -1) { this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1)); } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/app-manager/test/test_app_validator.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/app-manager/test/test_app_validator.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/app-manager/test/test_app_validator.html 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/app-manager/test/test_app_validator.html 2015-02-28 15:03:02.000000000 +0000 @@ -109,7 +109,7 @@ let validator = createPackaged("wrong-launch-path"); validator.validate().then(() => { is(validator.errors.length, 1, "app with wrong path got an error"); - let file = nsFile(validator.project.location); + let file = nsFile(validator.location); file.append("wrong-path.html"); let url = Services.io.newFileURI(file); is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPath", [url.spec], 1), diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/callslist.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/callslist.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/callslist.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/callslist.js 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,513 @@ +/* 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"; + +/** + * Functions handling details about a single recorded animation frame snapshot + * (the calls list, rendering preview, thumbnails filmstrip etc.). + */ +let CallsListView = Heritage.extend(WidgetMethods, { + /** + * Initialization function, called when the tool is started. + */ + initialize: function() { + this.widget = new SideMenuWidget($("#calls-list")); + this._slider = $("#calls-slider"); + this._searchbox = $("#calls-searchbox"); + this._filmstrip = $("#snapshot-filmstrip"); + + this._onSelect = this._onSelect.bind(this); + this._onSlideMouseDown = this._onSlideMouseDown.bind(this); + this._onSlideMouseUp = this._onSlideMouseUp.bind(this); + this._onSlide = this._onSlide.bind(this); + this._onSearch = this._onSearch.bind(this); + this._onScroll = this._onScroll.bind(this); + this._onExpand = this._onExpand.bind(this); + this._onStackFileClick = this._onStackFileClick.bind(this); + this._onThumbnailClick = this._onThumbnailClick.bind(this); + + this.widget.addEventListener("select", this._onSelect, false); + this._slider.addEventListener("mousedown", this._onSlideMouseDown, false); + this._slider.addEventListener("mouseup", this._onSlideMouseUp, false); + this._slider.addEventListener("change", this._onSlide, false); + this._searchbox.addEventListener("input", this._onSearch, false); + this._filmstrip.addEventListener("wheel", this._onScroll, false); + }, + + /** + * Destruction function, called when the tool is closed. + */ + destroy: function() { + this.widget.removeEventListener("select", this._onSelect, false); + this._slider.removeEventListener("mousedown", this._onSlideMouseDown, false); + this._slider.removeEventListener("mouseup", this._onSlideMouseUp, false); + this._slider.removeEventListener("change", this._onSlide, false); + this._searchbox.removeEventListener("input", this._onSearch, false); + this._filmstrip.removeEventListener("wheel", this._onScroll, false); + }, + + /** + * Populates this container with a list of function calls. + * + * @param array functionCalls + * A list of function call actors received from the backend. + */ + showCalls: function(functionCalls) { + this.empty(); + + for (let i = 0, len = functionCalls.length; i < len; i++) { + let call = functionCalls[i]; + + let view = document.createElement("vbox"); + view.className = "call-item-view devtools-monospace"; + view.setAttribute("flex", "1"); + + let contents = document.createElement("hbox"); + contents.className = "call-item-contents"; + contents.setAttribute("align", "center"); + contents.addEventListener("dblclick", this._onExpand); + view.appendChild(contents); + + let index = document.createElement("label"); + index.className = "plain call-item-index"; + index.setAttribute("flex", "1"); + index.setAttribute("value", i + 1); + + let gutter = document.createElement("hbox"); + gutter.className = "call-item-gutter"; + gutter.appendChild(index); + contents.appendChild(gutter); + + // Not all function calls have a caller that was stringified (e.g. + // context calls have a "gl" or "ctx" caller preview). + if (call.callerPreview) { + let context = document.createElement("label"); + context.className = "plain call-item-context"; + context.setAttribute("value", call.callerPreview); + contents.appendChild(context); + + let separator = document.createElement("label"); + separator.className = "plain call-item-separator"; + separator.setAttribute("value", "."); + contents.appendChild(separator); + } + + let name = document.createElement("label"); + name.className = "plain call-item-name"; + name.setAttribute("value", call.name); + contents.appendChild(name); + + let argsPreview = document.createElement("label"); + argsPreview.className = "plain call-item-args"; + argsPreview.setAttribute("crop", "end"); + argsPreview.setAttribute("flex", "100"); + // Getters and setters are displayed differently from regular methods. + if (call.type == CallWatcherFront.METHOD_FUNCTION) { + argsPreview.setAttribute("value", "(" + call.argsPreview + ")"); + } else { + argsPreview.setAttribute("value", " = " + call.argsPreview); + } + contents.appendChild(argsPreview); + + let location = document.createElement("label"); + location.className = "plain call-item-location"; + location.setAttribute("value", getFileName(call.file) + ":" + call.line); + location.setAttribute("crop", "start"); + location.setAttribute("flex", "1"); + location.addEventListener("mousedown", this._onExpand); + contents.appendChild(location); + + // Append a function call item to this container. + this.push([view], { + staged: true, + attachment: { + actor: call + } + }); + + // Highlight certain calls that are probably more interesting than + // everything else, making it easier to quickly glance over them. + if (CanvasFront.DRAW_CALLS.has(call.name)) { + view.setAttribute("draw-call", ""); + } + if (CanvasFront.INTERESTING_CALLS.has(call.name)) { + view.setAttribute("interesting-call", ""); + } + } + + // Flushes all the prepared function call items into this container. + this.commit(); + window.emit(EVENTS.CALL_LIST_POPULATED); + + // Resetting the function selection slider's value (shown in this + // container's toolbar) would trigger a selection event, which should be + // ignored in this case. + this._ignoreSliderChanges = true; + this._slider.value = 0; + this._slider.max = functionCalls.length - 1; + this._ignoreSliderChanges = false; + }, + + /** + * Displays an image in the rendering preview of this container, generated + * for the specified draw call in the recorded animation frame snapshot. + * + * @param array screenshot + * A single "snapshot-image" instance received from the backend. + */ + showScreenshot: function(screenshot) { + let { index, width, height, scaling, flipped, pixels } = screenshot; + + let screenshotNode = $("#screenshot-image"); + screenshotNode.setAttribute("flipped", flipped); + drawBackground("screenshot-rendering", width, height, pixels); + + let dimensionsNode = $("#screenshot-dimensions"); + let actualWidth = (width / scaling) | 0; + let actualHeight = (height / scaling) | 0; + dimensionsNode.setAttribute("value", + SHARED_L10N.getFormatStr("dimensions", actualWidth, actualHeight)); + + window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED); + }, + + /** + * Populates this container's footer with a list of thumbnails, one generated + * for each draw call in the recorded animation frame snapshot. + * + * @param array thumbnails + * An array of "snapshot-image" instances received from the backend. + */ + showThumbnails: function(thumbnails) { + while (this._filmstrip.hasChildNodes()) { + this._filmstrip.firstChild.remove(); + } + for (let thumbnail of thumbnails) { + this.appendThumbnail(thumbnail); + } + + window.emit(EVENTS.THUMBNAILS_DISPLAYED); + }, + + /** + * Displays an image in the thumbnails list of this container, generated + * for the specified draw call in the recorded animation frame snapshot. + * + * @param array thumbnail + * A single "snapshot-image" instance received from the backend. + */ + appendThumbnail: function(thumbnail) { + let { index, width, height, flipped, pixels } = thumbnail; + + let thumbnailNode = document.createElementNS(HTML_NS, "canvas"); + thumbnailNode.setAttribute("flipped", flipped); + thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_SIZE, width); + thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_SIZE, height); + drawImage(thumbnailNode, width, height, pixels, { centered: true }); + + thumbnailNode.className = "filmstrip-thumbnail"; + thumbnailNode.onmousedown = e => this._onThumbnailClick(e, index); + thumbnailNode.setAttribute("index", index); + this._filmstrip.appendChild(thumbnailNode); + }, + + /** + * Sets the currently highlighted thumbnail in this container. + * A screenshot will always correlate to a thumbnail in the filmstrip, + * both being identified by the same 'index' of the context function call. + * + * @param number index + * The context function call's index. + */ + set highlightedThumbnail(index) { + let currHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + index + "']"); + if (currHighlightedThumbnail == null) { + return; + } + + let prevIndex = this._highlightedThumbnailIndex + let prevHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + prevIndex + "']"); + if (prevHighlightedThumbnail) { + prevHighlightedThumbnail.removeAttribute("highlighted"); + } + + currHighlightedThumbnail.setAttribute("highlighted", ""); + currHighlightedThumbnail.scrollIntoView(); + this._highlightedThumbnailIndex = index; + }, + + /** + * Gets the currently highlighted thumbnail in this container. + * @return number + */ + get highlightedThumbnail() { + return this._highlightedThumbnailIndex; + }, + + /** + * The select listener for this container. + */ + _onSelect: function({ detail: callItem }) { + if (!callItem) { + return; + } + + // Some of the stepping buttons don't make sense specifically while the + // last function call is selected. + if (this.selectedIndex == this.itemCount - 1) { + $("#resume").setAttribute("disabled", "true"); + $("#step-over").setAttribute("disabled", "true"); + $("#step-out").setAttribute("disabled", "true"); + } else { + $("#resume").removeAttribute("disabled"); + $("#step-over").removeAttribute("disabled"); + $("#step-out").removeAttribute("disabled"); + } + + // Correlate the currently selected item with the function selection + // slider's value. Avoid triggering a redundant selection event. + this._ignoreSliderChanges = true; + this._slider.value = this.selectedIndex; + this._ignoreSliderChanges = false; + + // Can't generate screenshots for function call actors loaded from disk. + // XXX: Bug 984844. + if (callItem.attachment.actor.isLoadedFromDisk) { + return; + } + + // To keep continuous selection buttery smooth (for example, while pressing + // the DOWN key or moving the slider), only display the screenshot after + // any kind of user input stops. + setConditionalTimeout("screenshot-display", SCREENSHOT_DISPLAY_DELAY, () => { + return !this._isSliding; + }, () => { + let frameSnapshot = SnapshotsListView.selectedItem.attachment.actor + let functionCall = callItem.attachment.actor; + frameSnapshot.generateScreenshotFor(functionCall).then(screenshot => { + this.showScreenshot(screenshot); + this.highlightedThumbnail = screenshot.index; + }).catch(Cu.reportError); + }); + }, + + /** + * The mousedown listener for the call selection slider. + */ + _onSlideMouseDown: function() { + this._isSliding = true; + }, + + /** + * The mouseup listener for the call selection slider. + */ + _onSlideMouseUp: function() { + this._isSliding = false; + }, + + /** + * The change listener for the call selection slider. + */ + _onSlide: function() { + // Avoid performing any operations when programatically changing the value. + if (this._ignoreSliderChanges) { + return; + } + let selectedFunctionCallIndex = this.selectedIndex = this._slider.value; + + // While sliding, immediately show the most relevant thumbnail for a + // function call, for a nice diff-like animation effect between draws. + let thumbnails = SnapshotsListView.selectedItem.attachment.thumbnails; + let thumbnail = getThumbnailForCall(thumbnails, selectedFunctionCallIndex); + + // Avoid drawing and highlighting if the selected function call has the + // same thumbnail as the last one. + if (thumbnail.index == this.highlightedThumbnail) { + return; + } + // If a thumbnail wasn't found (e.g. the backend avoids creating thumbnails + // when rendering offscreen), simply defer to the first available one. + if (thumbnail.index == -1) { + thumbnail = thumbnails[0]; + } + + let { index, width, height, flipped, pixels } = thumbnail; + this.highlightedThumbnail = index; + + let screenshotNode = $("#screenshot-image"); + screenshotNode.setAttribute("flipped", flipped); + drawBackground("screenshot-rendering", width, height, pixels); + }, + + /** + * The input listener for the calls searchbox. + */ + _onSearch: function(e) { + let lowerCaseSearchToken = this._searchbox.value.toLowerCase(); + + this.filterContents(e => { + let call = e.attachment.actor; + let name = call.name.toLowerCase(); + let file = call.file.toLowerCase(); + let line = call.line.toString().toLowerCase(); + let args = call.argsPreview.toLowerCase(); + + return name.contains(lowerCaseSearchToken) || + file.contains(lowerCaseSearchToken) || + line.contains(lowerCaseSearchToken) || + args.contains(lowerCaseSearchToken); + }); + }, + + /** + * The wheel listener for the filmstrip that contains all the thumbnails. + */ + _onScroll: function(e) { + this._filmstrip.scrollLeft += e.deltaX; + }, + + /** + * The click/dblclick listener for an item or location url in this container. + * When expanding an item, it's corresponding call stack will be displayed. + */ + _onExpand: function(e) { + let callItem = this.getItemForElement(e.target); + let view = $(".call-item-view", callItem.target); + + // If the call stack nodes were already created, simply re-show them + // or jump to the corresponding file and line in the Debugger if a + // location link was clicked. + if (view.hasAttribute("call-stack-populated")) { + let isExpanded = view.getAttribute("call-stack-expanded") == "true"; + + // If clicking on the location, jump to the Debugger. + if (e.target.classList.contains("call-item-location")) { + let { file, line } = callItem.attachment.actor; + viewSourceInDebugger(file, line); + return; + } + // Otherwise hide the call stack. + else { + view.setAttribute("call-stack-expanded", !isExpanded); + $(".call-item-stack", view).hidden = isExpanded; + return; + } + } + + let list = document.createElement("vbox"); + list.className = "call-item-stack"; + view.setAttribute("call-stack-populated", ""); + view.setAttribute("call-stack-expanded", "true"); + view.appendChild(list); + + /** + * Creates a function call nodes in this container for a stack. + */ + let display = stack => { + for (let i = 1; i < stack.length; i++) { + let call = stack[i]; + + let contents = document.createElement("hbox"); + contents.className = "call-item-stack-fn"; + contents.style.MozPaddingStart = (i * STACK_FUNC_INDENTATION) + "px"; + + let name = document.createElement("label"); + name.className = "plain call-item-stack-fn-name"; + name.setAttribute("value", "↳ " + call.name + "()"); + contents.appendChild(name); + + let spacer = document.createElement("spacer"); + spacer.setAttribute("flex", "100"); + contents.appendChild(spacer); + + let location = document.createElement("label"); + location.className = "plain call-item-stack-fn-location"; + location.setAttribute("value", getFileName(call.file) + ":" + call.line); + location.setAttribute("crop", "start"); + location.setAttribute("flex", "1"); + location.addEventListener("mousedown", e => this._onStackFileClick(e, call)); + contents.appendChild(location); + + list.appendChild(contents); + } + + window.emit(EVENTS.CALL_STACK_DISPLAYED); + }; + + // If this animation snapshot is loaded from disk, there are no corresponding + // backend actors available and the data is immediately available. + let functionCall = callItem.attachment.actor; + if (functionCall.isLoadedFromDisk) { + display(functionCall.stack); + } + // ..otherwise we need to request the function call stack from the backend. + else { + callItem.attachment.actor.getDetails().then(fn => display(fn.stack)); + } + }, + + /** + * The click listener for a location link in the call stack. + * + * @param string file + * The url of the source owning the function. + * @param number line + * The line of the respective function. + */ + _onStackFileClick: function(e, { file, line }) { + viewSourceInDebugger(file, line); + }, + + /** + * The click listener for a thumbnail in the filmstrip. + * + * @param number index + * The function index in the recorded animation frame snapshot. + */ + _onThumbnailClick: function(e, index) { + this.selectedIndex = index; + }, + + /** + * The click listener for the "resume" button in this container's toolbar. + */ + _onResume: function() { + // Jump to the next draw call in the recorded animation frame snapshot. + let drawCall = getNextDrawCall(this.items, this.selectedItem); + if (drawCall) { + this.selectedItem = drawCall; + return; + } + + // If there are no more draw calls, just jump to the last context call. + this._onStepOut(); + }, + + /** + * The click listener for the "step over" button in this container's toolbar. + */ + _onStepOver: function() { + this.selectedIndex++; + }, + + /** + * The click listener for the "step in" button in this container's toolbar. + */ + _onStepIn: function() { + if (this.selectedIndex == -1) { + this._onResume(); + return; + } + let callItem = this.selectedItem; + let { file, line } = callItem.attachment.actor; + viewSourceInDebugger(file, line); + }, + + /** + * The click listener for the "step out" button in this container's toolbar. + */ + _onStepOut: function() { + this.selectedIndex = this.itemCount - 1; + } +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/canvasdebugger.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/canvasdebugger.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/canvasdebugger.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/canvasdebugger.js 2015-02-28 15:03:02.000000000 +0000 @@ -9,6 +9,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource:///modules/devtools/SideMenuWidget.jsm"); Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); +Cu.import("resource://gre/modules/devtools/Console.jsm"); +Cu.import("resource:///modules/devtools/gDevTools.jsm"); const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; @@ -18,6 +20,8 @@ const Telemetry = require("devtools/shared/telemetry"); const telemetry = new Telemetry(); +const CANVAS_ACTOR_RECORDING_ATTEMPT = gDevTools.testing ? 500 : 5000; + XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); @@ -41,9 +45,12 @@ // When all the animation frame snapshots are removed by the user. SNAPSHOTS_LIST_CLEARED: "CanvasDebugger:SnapshotsListCleared", - // When an animation frame snapshot starts/finishes being recorded. + // When an animation frame snapshot starts/finishes being recorded, and + // whether it was completed succesfully or cancelled. SNAPSHOT_RECORDING_STARTED: "CanvasDebugger:SnapshotRecordingStarted", SNAPSHOT_RECORDING_FINISHED: "CanvasDebugger:SnapshotRecordingFinished", + SNAPSHOT_RECORDING_COMPLETED: "CanvasDebugger:SnapshotRecordingCompleted", + SNAPSHOT_RECORDING_CANCELLED: "CanvasDebugger:SnapshotRecordingCancelled", // When an animation frame snapshot was selected and all its data displayed. SNAPSHOT_RECORDING_SELECTED: "CanvasDebugger:SnapshotRecordingSelected", @@ -157,7 +164,7 @@ $("#reload-notice").hidden = true; $("#empty-notice").hidden = false; - $("#import-notice").hidden = true; + $("#waiting-notice").hidden = true; $("#debugging-pane-contents").hidden = true; $("#screenshot-container").hidden = true; @@ -168,894 +175,6 @@ }; /** - * Functions handling the recorded animation frame snapshots UI. - */ -let SnapshotsListView = Heritage.extend(WidgetMethods, { - /** - * Initialization function, called when the tool is started. - */ - initialize: function() { - this.widget = new SideMenuWidget($("#snapshots-list"), { - showArrows: true - }); - - this._onSelect = this._onSelect.bind(this); - this._onClearButtonClick = this._onClearButtonClick.bind(this); - this._onRecordButtonClick = this._onRecordButtonClick.bind(this); - this._onImportButtonClick = this._onImportButtonClick.bind(this); - this._onSaveButtonClick = this._onSaveButtonClick.bind(this); - - this.emptyText = L10N.getStr("noSnapshotsText"); - this.widget.addEventListener("select", this._onSelect, false); - }, - - /** - * Destruction function, called when the tool is closed. - */ - destroy: function() { - this.widget.removeEventListener("select", this._onSelect, false); - }, - - /** - * Adds a snapshot entry to this container. - * - * @return object - * The newly inserted item. - */ - addSnapshot: function() { - let contents = document.createElement("hbox"); - contents.className = "snapshot-item"; - - let thumbnail = document.createElementNS(HTML_NS, "canvas"); - thumbnail.className = "snapshot-item-thumbnail"; - thumbnail.width = CanvasFront.THUMBNAIL_SIZE; - thumbnail.height = CanvasFront.THUMBNAIL_SIZE; - - let title = document.createElement("label"); - title.className = "plain snapshot-item-title"; - title.setAttribute("value", - L10N.getFormatStr("snapshotsList.itemLabel", this.itemCount + 1)); - - let calls = document.createElement("label"); - calls.className = "plain snapshot-item-calls"; - calls.setAttribute("value", - L10N.getStr("snapshotsList.loadingLabel")); - - let save = document.createElement("label"); - save.className = "plain snapshot-item-save"; - save.addEventListener("click", this._onSaveButtonClick, false); - - let spacer = document.createElement("spacer"); - spacer.setAttribute("flex", "1"); - - let footer = document.createElement("hbox"); - footer.className = "snapshot-item-footer"; - footer.appendChild(save); - - let details = document.createElement("vbox"); - details.className = "snapshot-item-details"; - details.appendChild(title); - details.appendChild(calls); - details.appendChild(spacer); - details.appendChild(footer); - - contents.appendChild(thumbnail); - contents.appendChild(details); - - // Append a recorded snapshot item to this container. - return this.push([contents], { - attachment: { - // The snapshot and function call actors, along with the thumbnails - // will be available as soon as recording finishes. - actor: null, - calls: null, - thumbnails: null, - screenshot: null - } - }); - }, - - /** - * Customizes a shapshot in this container. - * - * @param Item snapshotItem - * An item inserted via `SnapshotsListView.addSnapshot`. - * @param object snapshotActor - * The frame snapshot actor received from the backend. - * @param object snapshotOverview - * Additional data about the snapshot received from the backend. - */ - customizeSnapshot: function(snapshotItem, snapshotActor, snapshotOverview) { - // Make sure the function call actors are stored on the item, - // to be used when populating the CallsListView. - snapshotItem.attachment.actor = snapshotActor; - let functionCalls = snapshotItem.attachment.calls = snapshotOverview.calls; - let thumbnails = snapshotItem.attachment.thumbnails = snapshotOverview.thumbnails; - let screenshot = snapshotItem.attachment.screenshot = snapshotOverview.screenshot; - - let lastThumbnail = thumbnails[thumbnails.length - 1]; - let { width, height, flipped, pixels } = lastThumbnail; - - let thumbnailNode = $(".snapshot-item-thumbnail", snapshotItem.target); - thumbnailNode.setAttribute("flipped", flipped); - drawImage(thumbnailNode, width, height, pixels, { centered: true }); - - let callsNode = $(".snapshot-item-calls", snapshotItem.target); - let drawCalls = functionCalls.filter(e => CanvasFront.DRAW_CALLS.has(e.name)); - - let drawCallsStr = PluralForm.get(drawCalls.length, - L10N.getStr("snapshotsList.drawCallsLabel")); - let funcCallsStr = PluralForm.get(functionCalls.length, - L10N.getStr("snapshotsList.functionCallsLabel")); - - callsNode.setAttribute("value", - drawCallsStr.replace("#1", drawCalls.length) + ", " + - funcCallsStr.replace("#1", functionCalls.length)); - - let saveNode = $(".snapshot-item-save", snapshotItem.target); - saveNode.setAttribute("disabled", !!snapshotItem.isLoadedFromDisk); - saveNode.setAttribute("value", snapshotItem.isLoadedFromDisk - ? L10N.getStr("snapshotsList.loadedLabel") - : L10N.getStr("snapshotsList.saveLabel")); - - // Make sure there's always a selected item available. - if (!this.selectedItem) { - this.selectedIndex = 0; - } - }, - - /** - * The select listener for this container. - */ - _onSelect: function({ detail: snapshotItem }) { - if (!snapshotItem) { - return; - } - let { calls, thumbnails, screenshot } = snapshotItem.attachment; - - $("#reload-notice").hidden = true; - $("#empty-notice").hidden = true; - $("#import-notice").hidden = false; - - $("#debugging-pane-contents").hidden = true; - $("#screenshot-container").hidden = true; - $("#snapshot-filmstrip").hidden = true; - - Task.spawn(function*() { - // Wait for a few milliseconds between presenting the function calls, - // screenshot and thumbnails, to allow each component being - // sequentially drawn. This gives the illusion of snappiness. - - yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY); - CallsListView.showCalls(calls); - $("#debugging-pane-contents").hidden = false; - $("#import-notice").hidden = true; - - yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY); - CallsListView.showThumbnails(thumbnails); - $("#snapshot-filmstrip").hidden = false; - - yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY); - CallsListView.showScreenshot(screenshot); - $("#screenshot-container").hidden = false; - - window.emit(EVENTS.SNAPSHOT_RECORDING_SELECTED); - }); - }, - - /** - * The click listener for the "clear" button in this container. - */ - _onClearButtonClick: function() { - Task.spawn(function*() { - SnapshotsListView.empty(); - CallsListView.empty(); - - $("#reload-notice").hidden = true; - $("#empty-notice").hidden = true; - $("#import-notice").hidden = true; - - if (yield gFront.isInitialized()) { - $("#empty-notice").hidden = false; - } else { - $("#reload-notice").hidden = false; - } - - $("#debugging-pane-contents").hidden = true; - $("#screenshot-container").hidden = true; - $("#snapshot-filmstrip").hidden = true; - - window.emit(EVENTS.SNAPSHOTS_LIST_CLEARED); - }); - }, - - /** - * The click listener for the "record" button in this container. - */ - _onRecordButtonClick: function() { - Task.spawn(function*() { - $("#record-snapshot").setAttribute("checked", "true"); - $("#record-snapshot").setAttribute("disabled", "true"); - - // Insert a "dummy" snapshot item in the view, to hint that recording - // has now started. However, wait for a few milliseconds before actually - // starting the recording, since that might block rendering and prevent - // the dummy snapshot item from being drawn. - let snapshotItem = this.addSnapshot(); - - // If this is the first item, immediately show the "Loading…" notice. - if (this.itemCount == 1) { - $("#empty-notice").hidden = true; - $("#import-notice").hidden = false; - } - - yield DevToolsUtils.waitForTime(SNAPSHOT_START_RECORDING_DELAY); - window.emit(EVENTS.SNAPSHOT_RECORDING_STARTED); - - let snapshotActor = yield gFront.recordAnimationFrame(); - let snapshotOverview = yield snapshotActor.getOverview(); - this.customizeSnapshot(snapshotItem, snapshotActor, snapshotOverview); - - $("#record-snapshot").removeAttribute("checked"); - $("#record-snapshot").removeAttribute("disabled"); - - window.emit(EVENTS.SNAPSHOT_RECORDING_FINISHED); - }.bind(this)); - }, - - /** - * The click listener for the "import" button in this container. - */ - _onImportButtonClick: function() { - let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - fp.init(window, L10N.getStr("snapshotsList.saveDialogTitle"), Ci.nsIFilePicker.modeOpen); - fp.appendFilter(L10N.getStr("snapshotsList.saveDialogJSONFilter"), "*.json"); - fp.appendFilter(L10N.getStr("snapshotsList.saveDialogAllFilter"), "*.*"); - - if (fp.show() != Ci.nsIFilePicker.returnOK) { - return; - } - - let channel = NetUtil.newChannel2(fp.file, - null, - null, - window.document, - null, // aLoadingPrincipal - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); - channel.contentType = "text/plain"; - - NetUtil.asyncFetch2(channel, (inputStream, status) => { - if (!Components.isSuccessCode(status)) { - console.error("Could not import recorded animation frame snapshot file."); - return; - } - try { - let string = NetUtil.readInputStreamToString(inputStream, inputStream.available()); - var data = JSON.parse(string); - } catch (e) { - console.error("Could not read animation frame snapshot file."); - return; - } - if (data.fileType != CALLS_LIST_SERIALIZER_IDENTIFIER) { - console.error("Unrecognized animation frame snapshot file."); - return; - } - - // Add a `isLoadedFromDisk` flag on everything to avoid sending invalid - // requests to the backend, since we're not dealing with actors anymore. - let snapshotItem = this.addSnapshot(); - snapshotItem.isLoadedFromDisk = true; - data.calls.forEach(e => e.isLoadedFromDisk = true); - - this.customizeSnapshot(snapshotItem, data.calls, data); - }); - }, - - /** - * The click listener for the "save" button of each item in this container. - */ - _onSaveButtonClick: function(e) { - let snapshotItem = this.getItemForElement(e.target); - - let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - fp.init(window, L10N.getStr("snapshotsList.saveDialogTitle"), Ci.nsIFilePicker.modeSave); - fp.appendFilter(L10N.getStr("snapshotsList.saveDialogJSONFilter"), "*.json"); - fp.appendFilter(L10N.getStr("snapshotsList.saveDialogAllFilter"), "*.*"); - fp.defaultString = "snapshot.json"; - - // Start serializing all the function call actors for the specified snapshot, - // while the nsIFilePicker dialog is being opened. Snappy. - let serialized = Task.spawn(function*() { - let data = { - fileType: CALLS_LIST_SERIALIZER_IDENTIFIER, - version: CALLS_LIST_SERIALIZER_VERSION, - calls: [], - thumbnails: [], - screenshot: null - }; - let functionCalls = snapshotItem.attachment.calls; - let thumbnails = snapshotItem.attachment.thumbnails; - let screenshot = snapshotItem.attachment.screenshot; - - // Prepare all the function calls for serialization. - yield DevToolsUtils.yieldingEach(functionCalls, (call, i) => { - let { type, name, file, line, argsPreview, callerPreview } = call; - return call.getDetails().then(({ stack }) => { - data.calls[i] = { - type: type, - name: name, - file: file, - line: line, - stack: stack, - argsPreview: argsPreview, - callerPreview: callerPreview - }; - }); - }); - - // Prepare all the thumbnails for serialization. - yield DevToolsUtils.yieldingEach(thumbnails, (thumbnail, i) => { - let { index, width, height, flipped, pixels } = thumbnail; - data.thumbnails.push({ index, width, height, flipped, pixels }); - }); - - // Prepare the screenshot for serialization. - let { index, width, height, flipped, pixels } = screenshot; - data.screenshot = { index, width, height, flipped, pixels }; - - let string = JSON.stringify(data); - let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. - createInstance(Ci.nsIScriptableUnicodeConverter); - - converter.charset = "UTF-8"; - return converter.convertToInputStream(string); - }); - - // Open the nsIFilePicker and wait for the function call actors to finish - // being serialized, in order to save the generated JSON data to disk. - fp.open({ done: result => { - if (result == Ci.nsIFilePicker.returnCancel) { - return; - } - let footer = $(".snapshot-item-footer", snapshotItem.target); - let save = $(".snapshot-item-save", snapshotItem.target); - - // Show a throbber and a "Saving…" label if serializing isn't immediate. - setNamedTimeout("call-list-save", CALLS_LIST_SLOW_SAVE_DELAY, () => { - footer.classList.add("devtools-throbber"); - save.setAttribute("disabled", "true"); - save.setAttribute("value", L10N.getStr("snapshotsList.savingLabel")); - }); - - serialized.then(inputStream => { - let outputStream = FileUtils.openSafeFileOutputStream(fp.file); - - NetUtil.asyncCopy(inputStream, outputStream, status => { - if (!Components.isSuccessCode(status)) { - console.error("Could not save recorded animation frame snapshot file."); - } - clearNamedTimeout("call-list-save"); - footer.classList.remove("devtools-throbber"); - save.removeAttribute("disabled"); - save.setAttribute("value", L10N.getStr("snapshotsList.saveLabel")); - }); - }); - }}); - } -}); - -/** - * Functions handling details about a single recorded animation frame snapshot - * (the calls list, rendering preview, thumbnails filmstrip etc.). - */ -let CallsListView = Heritage.extend(WidgetMethods, { - /** - * Initialization function, called when the tool is started. - */ - initialize: function() { - this.widget = new SideMenuWidget($("#calls-list")); - this._slider = $("#calls-slider"); - this._searchbox = $("#calls-searchbox"); - this._filmstrip = $("#snapshot-filmstrip"); - - this._onSelect = this._onSelect.bind(this); - this._onSlideMouseDown = this._onSlideMouseDown.bind(this); - this._onSlideMouseUp = this._onSlideMouseUp.bind(this); - this._onSlide = this._onSlide.bind(this); - this._onSearch = this._onSearch.bind(this); - this._onScroll = this._onScroll.bind(this); - this._onExpand = this._onExpand.bind(this); - this._onStackFileClick = this._onStackFileClick.bind(this); - this._onThumbnailClick = this._onThumbnailClick.bind(this); - - this.widget.addEventListener("select", this._onSelect, false); - this._slider.addEventListener("mousedown", this._onSlideMouseDown, false); - this._slider.addEventListener("mouseup", this._onSlideMouseUp, false); - this._slider.addEventListener("change", this._onSlide, false); - this._searchbox.addEventListener("input", this._onSearch, false); - this._filmstrip.addEventListener("wheel", this._onScroll, false); - }, - - /** - * Destruction function, called when the tool is closed. - */ - destroy: function() { - this.widget.removeEventListener("select", this._onSelect, false); - this._slider.removeEventListener("mousedown", this._onSlideMouseDown, false); - this._slider.removeEventListener("mouseup", this._onSlideMouseUp, false); - this._slider.removeEventListener("change", this._onSlide, false); - this._searchbox.removeEventListener("input", this._onSearch, false); - this._filmstrip.removeEventListener("wheel", this._onScroll, false); - }, - - /** - * Populates this container with a list of function calls. - * - * @param array functionCalls - * A list of function call actors received from the backend. - */ - showCalls: function(functionCalls) { - this.empty(); - - for (let i = 0, len = functionCalls.length; i < len; i++) { - let call = functionCalls[i]; - - let view = document.createElement("vbox"); - view.className = "call-item-view devtools-monospace"; - view.setAttribute("flex", "1"); - - let contents = document.createElement("hbox"); - contents.className = "call-item-contents"; - contents.setAttribute("align", "center"); - contents.addEventListener("dblclick", this._onExpand); - view.appendChild(contents); - - let index = document.createElement("label"); - index.className = "plain call-item-index"; - index.setAttribute("flex", "1"); - index.setAttribute("value", i + 1); - - let gutter = document.createElement("hbox"); - gutter.className = "call-item-gutter"; - gutter.appendChild(index); - contents.appendChild(gutter); - - // Not all function calls have a caller that was stringified (e.g. - // context calls have a "gl" or "ctx" caller preview). - if (call.callerPreview) { - let context = document.createElement("label"); - context.className = "plain call-item-context"; - context.setAttribute("value", call.callerPreview); - contents.appendChild(context); - - let separator = document.createElement("label"); - separator.className = "plain call-item-separator"; - separator.setAttribute("value", "."); - contents.appendChild(separator); - } - - let name = document.createElement("label"); - name.className = "plain call-item-name"; - name.setAttribute("value", call.name); - contents.appendChild(name); - - let argsPreview = document.createElement("label"); - argsPreview.className = "plain call-item-args"; - argsPreview.setAttribute("crop", "end"); - argsPreview.setAttribute("flex", "100"); - // Getters and setters are displayed differently from regular methods. - if (call.type == CallWatcherFront.METHOD_FUNCTION) { - argsPreview.setAttribute("value", "(" + call.argsPreview + ")"); - } else { - argsPreview.setAttribute("value", " = " + call.argsPreview); - } - contents.appendChild(argsPreview); - - let location = document.createElement("label"); - location.className = "plain call-item-location"; - location.setAttribute("value", getFileName(call.file) + ":" + call.line); - location.setAttribute("crop", "start"); - location.setAttribute("flex", "1"); - location.addEventListener("mousedown", this._onExpand); - contents.appendChild(location); - - // Append a function call item to this container. - this.push([view], { - staged: true, - attachment: { - actor: call - } - }); - - // Highlight certain calls that are probably more interesting than - // everything else, making it easier to quickly glance over them. - if (CanvasFront.DRAW_CALLS.has(call.name)) { - view.setAttribute("draw-call", ""); - } - if (CanvasFront.INTERESTING_CALLS.has(call.name)) { - view.setAttribute("interesting-call", ""); - } - } - - // Flushes all the prepared function call items into this container. - this.commit(); - window.emit(EVENTS.CALL_LIST_POPULATED); - - // Resetting the function selection slider's value (shown in this - // container's toolbar) would trigger a selection event, which should be - // ignored in this case. - this._ignoreSliderChanges = true; - this._slider.value = 0; - this._slider.max = functionCalls.length - 1; - this._ignoreSliderChanges = false; - }, - - /** - * Displays an image in the rendering preview of this container, generated - * for the specified draw call in the recorded animation frame snapshot. - * - * @param array screenshot - * A single "snapshot-image" instance received from the backend. - */ - showScreenshot: function(screenshot) { - let { index, width, height, scaling, flipped, pixels } = screenshot; - - let screenshotNode = $("#screenshot-image"); - screenshotNode.setAttribute("flipped", flipped); - drawBackground("screenshot-rendering", width, height, pixels); - - let dimensionsNode = $("#screenshot-dimensions"); - let actualWidth = (width / scaling) | 0; - let actualHeight = (height / scaling) | 0; - dimensionsNode.setAttribute("value", - SHARED_L10N.getFormatStr("dimensions", actualWidth, actualHeight)); - - window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED); - }, - - /** - * Populates this container's footer with a list of thumbnails, one generated - * for each draw call in the recorded animation frame snapshot. - * - * @param array thumbnails - * An array of "snapshot-image" instances received from the backend. - */ - showThumbnails: function(thumbnails) { - while (this._filmstrip.hasChildNodes()) { - this._filmstrip.firstChild.remove(); - } - for (let thumbnail of thumbnails) { - this.appendThumbnail(thumbnail); - } - - window.emit(EVENTS.THUMBNAILS_DISPLAYED); - }, - - /** - * Displays an image in the thumbnails list of this container, generated - * for the specified draw call in the recorded animation frame snapshot. - * - * @param array thumbnail - * A single "snapshot-image" instance received from the backend. - */ - appendThumbnail: function(thumbnail) { - let { index, width, height, flipped, pixels } = thumbnail; - - let thumbnailNode = document.createElementNS(HTML_NS, "canvas"); - thumbnailNode.setAttribute("flipped", flipped); - thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_SIZE, width); - thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_SIZE, height); - drawImage(thumbnailNode, width, height, pixels, { centered: true }); - - thumbnailNode.className = "filmstrip-thumbnail"; - thumbnailNode.onmousedown = e => this._onThumbnailClick(e, index); - thumbnailNode.setAttribute("index", index); - this._filmstrip.appendChild(thumbnailNode); - }, - - /** - * Sets the currently highlighted thumbnail in this container. - * A screenshot will always correlate to a thumbnail in the filmstrip, - * both being identified by the same 'index' of the context function call. - * - * @param number index - * The context function call's index. - */ - set highlightedThumbnail(index) { - let currHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + index + "']"); - if (currHighlightedThumbnail == null) { - return; - } - - let prevIndex = this._highlightedThumbnailIndex - let prevHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + prevIndex + "']"); - if (prevHighlightedThumbnail) { - prevHighlightedThumbnail.removeAttribute("highlighted"); - } - - currHighlightedThumbnail.setAttribute("highlighted", ""); - currHighlightedThumbnail.scrollIntoView(); - this._highlightedThumbnailIndex = index; - }, - - /** - * Gets the currently highlighted thumbnail in this container. - * @return number - */ - get highlightedThumbnail() { - return this._highlightedThumbnailIndex; - }, - - /** - * The select listener for this container. - */ - _onSelect: function({ detail: callItem }) { - if (!callItem) { - return; - } - - // Some of the stepping buttons don't make sense specifically while the - // last function call is selected. - if (this.selectedIndex == this.itemCount - 1) { - $("#resume").setAttribute("disabled", "true"); - $("#step-over").setAttribute("disabled", "true"); - $("#step-out").setAttribute("disabled", "true"); - } else { - $("#resume").removeAttribute("disabled"); - $("#step-over").removeAttribute("disabled"); - $("#step-out").removeAttribute("disabled"); - } - - // Correlate the currently selected item with the function selection - // slider's value. Avoid triggering a redundant selection event. - this._ignoreSliderChanges = true; - this._slider.value = this.selectedIndex; - this._ignoreSliderChanges = false; - - // Can't generate screenshots for function call actors loaded from disk. - // XXX: Bug 984844. - if (callItem.attachment.actor.isLoadedFromDisk) { - return; - } - - // To keep continuous selection buttery smooth (for example, while pressing - // the DOWN key or moving the slider), only display the screenshot after - // any kind of user input stops. - setConditionalTimeout("screenshot-display", SCREENSHOT_DISPLAY_DELAY, () => { - return !this._isSliding; - }, () => { - let frameSnapshot = SnapshotsListView.selectedItem.attachment.actor - let functionCall = callItem.attachment.actor; - frameSnapshot.generateScreenshotFor(functionCall).then(screenshot => { - this.showScreenshot(screenshot); - this.highlightedThumbnail = screenshot.index; - }).catch(Cu.reportError); - }); - }, - - /** - * The mousedown listener for the call selection slider. - */ - _onSlideMouseDown: function() { - this._isSliding = true; - }, - - /** - * The mouseup listener for the call selection slider. - */ - _onSlideMouseUp: function() { - this._isSliding = false; - }, - - /** - * The change listener for the call selection slider. - */ - _onSlide: function() { - // Avoid performing any operations when programatically changing the value. - if (this._ignoreSliderChanges) { - return; - } - let selectedFunctionCallIndex = this.selectedIndex = this._slider.value; - - // While sliding, immediately show the most relevant thumbnail for a - // function call, for a nice diff-like animation effect between draws. - let thumbnails = SnapshotsListView.selectedItem.attachment.thumbnails; - let thumbnail = getThumbnailForCall(thumbnails, selectedFunctionCallIndex); - - // Avoid drawing and highlighting if the selected function call has the - // same thumbnail as the last one. - if (thumbnail.index == this.highlightedThumbnail) { - return; - } - // If a thumbnail wasn't found (e.g. the backend avoids creating thumbnails - // when rendering offscreen), simply defer to the first available one. - if (thumbnail.index == -1) { - thumbnail = thumbnails[0]; - } - - let { index, width, height, flipped, pixels } = thumbnail; - this.highlightedThumbnail = index; - - let screenshotNode = $("#screenshot-image"); - screenshotNode.setAttribute("flipped", flipped); - drawBackground("screenshot-rendering", width, height, pixels); - }, - - /** - * The input listener for the calls searchbox. - */ - _onSearch: function(e) { - let lowerCaseSearchToken = this._searchbox.value.toLowerCase(); - - this.filterContents(e => { - let call = e.attachment.actor; - let name = call.name.toLowerCase(); - let file = call.file.toLowerCase(); - let line = call.line.toString().toLowerCase(); - let args = call.argsPreview.toLowerCase(); - - return name.contains(lowerCaseSearchToken) || - file.contains(lowerCaseSearchToken) || - line.contains(lowerCaseSearchToken) || - args.contains(lowerCaseSearchToken); - }); - }, - - /** - * The wheel listener for the filmstrip that contains all the thumbnails. - */ - _onScroll: function(e) { - this._filmstrip.scrollLeft += e.deltaX; - }, - - /** - * The click/dblclick listener for an item or location url in this container. - * When expanding an item, it's corresponding call stack will be displayed. - */ - _onExpand: function(e) { - let callItem = this.getItemForElement(e.target); - let view = $(".call-item-view", callItem.target); - - // If the call stack nodes were already created, simply re-show them - // or jump to the corresponding file and line in the Debugger if a - // location link was clicked. - if (view.hasAttribute("call-stack-populated")) { - let isExpanded = view.getAttribute("call-stack-expanded") == "true"; - - // If clicking on the location, jump to the Debugger. - if (e.target.classList.contains("call-item-location")) { - let { file, line } = callItem.attachment.actor; - viewSourceInDebugger(file, line); - return; - } - // Otherwise hide the call stack. - else { - view.setAttribute("call-stack-expanded", !isExpanded); - $(".call-item-stack", view).hidden = isExpanded; - return; - } - } - - let list = document.createElement("vbox"); - list.className = "call-item-stack"; - view.setAttribute("call-stack-populated", ""); - view.setAttribute("call-stack-expanded", "true"); - view.appendChild(list); - - /** - * Creates a function call nodes in this container for a stack. - */ - let display = stack => { - for (let i = 1; i < stack.length; i++) { - let call = stack[i]; - - let contents = document.createElement("hbox"); - contents.className = "call-item-stack-fn"; - contents.style.MozPaddingStart = (i * STACK_FUNC_INDENTATION) + "px"; - - let name = document.createElement("label"); - name.className = "plain call-item-stack-fn-name"; - name.setAttribute("value", "↳ " + call.name + "()"); - contents.appendChild(name); - - let spacer = document.createElement("spacer"); - spacer.setAttribute("flex", "100"); - contents.appendChild(spacer); - - let location = document.createElement("label"); - location.className = "plain call-item-stack-fn-location"; - location.setAttribute("value", getFileName(call.file) + ":" + call.line); - location.setAttribute("crop", "start"); - location.setAttribute("flex", "1"); - location.addEventListener("mousedown", e => this._onStackFileClick(e, call)); - contents.appendChild(location); - - list.appendChild(contents); - } - - window.emit(EVENTS.CALL_STACK_DISPLAYED); - }; - - // If this animation snapshot is loaded from disk, there are no corresponding - // backend actors available and the data is immediately available. - let functionCall = callItem.attachment.actor; - if (functionCall.isLoadedFromDisk) { - display(functionCall.stack); - } - // ..otherwise we need to request the function call stack from the backend. - else { - callItem.attachment.actor.getDetails().then(fn => display(fn.stack)); - } - }, - - /** - * The click listener for a location link in the call stack. - * - * @param string file - * The url of the source owning the function. - * @param number line - * The line of the respective function. - */ - _onStackFileClick: function(e, { file, line }) { - viewSourceInDebugger(file, line); - }, - - /** - * The click listener for a thumbnail in the filmstrip. - * - * @param number index - * The function index in the recorded animation frame snapshot. - */ - _onThumbnailClick: function(e, index) { - this.selectedIndex = index; - }, - - /** - * The click listener for the "resume" button in this container's toolbar. - */ - _onResume: function() { - // Jump to the next draw call in the recorded animation frame snapshot. - let drawCall = getNextDrawCall(this.items, this.selectedItem); - if (drawCall) { - this.selectedItem = drawCall; - return; - } - - // If there are no more draw calls, just jump to the last context call. - this._onStepOut(); - }, - - /** - * The click listener for the "step over" button in this container's toolbar. - */ - _onStepOver: function() { - this.selectedIndex++; - }, - - /** - * The click listener for the "step in" button in this container's toolbar. - */ - _onStepIn: function() { - if (this.selectedIndex == -1) { - this._onResume(); - return; - } - let callItem = this.selectedItem; - let { file, line } = callItem.attachment.actor; - viewSourceInDebugger(file, line); - }, - - /** - * The click listener for the "step out" button in this container's toolbar. - */ - _onStepOut: function() { - this.selectedIndex = this.itemCount - 1; - } -}); - -/** * Localization convenience methods. */ let L10N = new ViewHelpers.L10N(STRINGS_URI); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/canvasdebugger.xul thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/canvasdebugger.xul --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/canvasdebugger.xul 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/canvasdebugger.xul 2015-02-28 15:03:02.000000000 +0000 @@ -15,6 +15,8 @@ + + + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/test/doc_raf-no-canvas.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/test/doc_raf-no-canvas.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/test/doc_raf-no-canvas.html 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/test/doc_raf-no-canvas.html 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,18 @@ + + + + + + + Canvas inspector test page + + + + + + + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/test/doc_settimeout.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/test/doc_settimeout.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/test/doc_settimeout.html 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/test/doc_settimeout.html 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,37 @@ + + + + + + + Canvas inspector test page + + + + + + + + + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/test/head.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/test/head.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/canvasdebugger/test/head.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/canvasdebugger/test/head.js 2015-02-28 15:03:02.000000000 +0000 @@ -20,6 +20,7 @@ let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {}); let { CallWatcherFront } = devtools.require("devtools/server/actors/call-watcher"); let { CanvasFront } = devtools.require("devtools/server/actors/canvas"); +let { setTimeout } = devtools.require("sdk/timers"); let TiltGL = devtools.require("devtools/tilt/tilt-gl"); let TargetFactory = devtools.TargetFactory; let Toolbox = devtools.Toolbox; @@ -27,18 +28,24 @@ const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js"; const EXAMPLE_URL = "http://example.com/browser/browser/devtools/canvasdebugger/test/"; +const SET_TIMEOUT_URL = EXAMPLE_URL + "doc_settimeout.html"; +const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html"; +const RAF_NO_CANVAS_URL = EXAMPLE_URL + "doc_raf-no-canvas.html"; const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html"; const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html"; const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html"; const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html"; const WEBGL_ENUM_URL = EXAMPLE_URL + "doc_webgl-enum.html"; const WEBGL_BINDINGS_URL = EXAMPLE_URL + "doc_webgl-bindings.html"; +const RAF_BEGIN_URL = EXAMPLE_URL + "doc_raf-begin.html"; // All tests are asynchronous. waitForExplicitFinish(); let gToolEnabled = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled"); +gDevTools.testing = true; + registerCleanupFunction(() => { info("finish() was called, cleaning up..."); Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); @@ -275,3 +282,22 @@ let item = aSources.getItemForAttachment(a => a.source.url === aURL); return item ? item.value : null; } + +/** + * Waits until a predicate returns true. + * + * @param function predicate + * Invoked once in a while until it returns true. + * @param number interval [optional] + * How often the predicate is invoked, in milliseconds. + */ +function *waitUntil (predicate, interval = 10) { + if (yield predicate()) { + return Promise.resolve(true); + } + let deferred = Promise.defer(); + setTimeout(function() { + waitUntil(predicate).then(() => deferred.resolve(true)); + }, interval); + return deferred.promise; +} diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/debugger-panes.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/debugger-panes.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/debugger-panes.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/debugger-panes.js 2015-02-28 15:03:02.000000000 +0000 @@ -136,9 +136,8 @@ * - staged: true to stage the item to be appended later */ addSource: function(aSource, aOptions = {}) { - if (!(aSource.url || aSource.introductionUrl)) { - // These would be most likely eval scripts introduced in inline - // JavaScript in HTML, and we don't show those yet (bug 1097873) + if (!aSource.url) { + // We don't show any unnamed eval scripts yet (see bug 1124106) return; } @@ -170,21 +169,10 @@ }, _parseUrl: function(aSource) { - let fullUrl = aSource.url || aSource.introductionUrl; + let fullUrl = aSource.url; let url = fullUrl.split(" -> ").pop(); let label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url); - let group; - - if (!aSource.url && aSource.introductionUrl) { - label += ' > ' + aSource.introductionType; - group = L10N.getStr("evalGroupLabel"); - } - else if(aSource.addonID) { - group = aSource.addonID; - } - else { - group = SourceUtils.getSourceGroup(url); - } + let group = aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url); return { label: label, diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/browser_dbg_breakpoints-eval.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/browser_dbg_breakpoints-eval.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/browser_dbg_breakpoints-eval.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/browser_dbg_breakpoints-eval.js 2015-02-28 15:03:02.000000000 +0000 @@ -28,16 +28,16 @@ function run() { return Task.spawn(function*() { let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE); - callInTab(gTab, "evalSource"); + callInTab(gTab, "evalSourceWithSourceURL"); yield newSource; - yield gPanel.addBreakpoint({ actor: gSources.values[1], line: 2 }); + yield gPanel.addBreakpoint({ actor: gSources.values[0], line: 2 }); yield ensureThreadClientState(gPanel, "resumed"); const paused = waitForThreadEvents(gPanel, "paused"); callInTab(gTab, "bar"); let frame = (yield paused).frame; - is(frame.where.source.actor, gSources.values[1], "Should have broken on the eval'ed source"); + is(frame.where.source.actor, gSources.values[0], "Should have broken on the eval'ed source"); is(frame.where.line, 2, "Should break on line 2"); yield resumeDebuggerThenCloseAndFinish(gPanel); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/browser_dbg_sources-bookmarklet.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/browser_dbg_sources-bookmarklet.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/browser_dbg_sources-bookmarklet.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/browser_dbg_sources-bookmarklet.js 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure javascript bookmarklet scripts appear and load correctly in the source list + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-bookmarklet.html"; + +const BOOKMARKLET_SCRIPT_CODE = "console.log('bookmarklet executed');"; + +function test() { + let gTab, gPanel, gDebugger; + let gSources, gBreakpoints; + + initDebugger(TAB_URL).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gBreakpoints = gDebugger.DebuggerController.Breakpoints; + + return Task.spawn(function*() { + let waitForSource = waitForDebuggerEvents(gPanel, gPanel.panelWin.EVENTS.NEW_SOURCE, 1); + + // NOTE: devtools debugger panel needs to be already open, + // or the bookmarklet script will not be shown in the sources panel + callInTab(gTab, "injectBookmarklet", BOOKMARKLET_SCRIPT_CODE); + + yield waitForSource; + + is(gSources.values.length, 2, "Should have 2 source"); + + let item = gSources.getItemForAttachment(e => { + return e.label.indexOf("javascript:") === 0; + }); + ok(item, "Source label is incorrect."); + + let res = yield promiseInvoke(gDebugger.DebuggerController.client, + gDebugger.DebuggerController.client.request, + { to: item.value, type: "source"}); + + ok(res && res.source == BOOKMARKLET_SCRIPT_CODE, "SourceActor reply received"); + is(res.source, BOOKMARKLET_SCRIPT_CODE, "source is correct"); + is(res.contentType, "text/javascript", "contentType is correct"); + + yield closeDebuggerAndFinish(gPanel); + }); + }); +} diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js 2015-02-28 15:03:02.000000000 +0000 @@ -58,7 +58,7 @@ is(objectVar.target.querySelector(".name").getAttribute("value"), "largeObject", "Should have the right property name for 'largeObject'."); - is(objectVar.target.querySelector(".value").getAttribute("value"), "Object", + is(objectVar.target.querySelector(".value").getAttribute("value"), "Object[10000]", "Should have the right property value for 'largeObject'."); ok(objectVar.target.querySelector(".value").className.contains("token-other"), "Should have the right token class for 'largeObject'."); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/browser.ini thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/browser.ini --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/browser.ini 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/browser.ini 2015-02-28 15:03:02.000000000 +0000 @@ -86,6 +86,7 @@ doc_scope-variable-3.html doc_scope-variable-4.html doc_script-eval.html + doc_script-bookmarklet.html doc_script-switching-01.html doc_script-switching-02.html doc_split-console-paused-reload.html @@ -102,71 +103,45 @@ [browser_dbg_aaa_run_first_leaktest.js] skip-if = e10s && debug [browser_dbg_addonactor.js] -skip-if = e10s && debug [browser_dbg_addon-sources.js] -skip-if = e10s && debug [browser_dbg_addon-modules.js] skip-if = e10s # TODO [browser_dbg_addon-modules-unpacked.js] skip-if = e10s # TODO [browser_dbg_addon-panels.js] -skip-if = e10s && debug [browser_dbg_addon-console.js] skip-if = e10s && debug || os == 'win' # bug 1005274 [browser_dbg_auto-pretty-print-01.js] -skip-if = e10s && debug [browser_dbg_auto-pretty-print-02.js] -skip-if = e10s && debug [browser_dbg_bfcache.js] skip-if = e10s || true # bug 1113935 [browser_dbg_blackboxing-01.js] -skip-if = e10s && debug [browser_dbg_blackboxing-02.js] -skip-if = e10s && debug [browser_dbg_blackboxing-03.js] -skip-if = e10s && debug [browser_dbg_blackboxing-04.js] -skip-if = e10s && debug [browser_dbg_blackboxing-05.js] -skip-if = e10s && debug [browser_dbg_blackboxing-06.js] -skip-if = e10s && debug [browser_dbg_breadcrumbs-access.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-01.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-02.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-03.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-04.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-05.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-06.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-07.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-08.js] -skip-if = e10s && debug [browser_dbg_break-on-dom-event-01.js] skip-if = e10s || os == "mac" || e10s # Bug 895426 [browser_dbg_break-on-dom-event-02.js] skip-if = e10s # TODO [browser_dbg_breakpoints-actual-location.js] -skip-if = e10s && debug [browser_dbg_breakpoints-actual-location2.js] -skip-if = e10s && debug [browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js] skip-if = e10s # Bug 1093535 [browser_dbg_breakpoints-button-01.js] -skip-if = e10s && debug [browser_dbg_breakpoints-button-02.js] -skip-if = e10s && debug [browser_dbg_breakpoints-contextmenu-add.js] -skip-if = e10s && debug [browser_dbg_breakpoints-contextmenu.js] -skip-if = e10s && debug [browser_dbg_breakpoints-disabled-reload.js] skip-if = e10s # Bug 1093535 [browser_dbg_breakpoints-editor.js] @@ -406,13 +381,15 @@ [browser_dbg_sources-cache.js] skip-if = e10s && debug [browser_dbg_sources-eval-01.js] -skip-if = e10s && debug +skip-if = true # non-named eval sources turned off for now, bug 1124106 [browser_dbg_sources-eval-02.js] skip-if = e10s && debug [browser_dbg_sources-labels.js] skip-if = e10s && debug [browser_dbg_sources-sorting.js] skip-if = e10s && debug +[browser_dbg_sources-bookmarklet.js] +skip-if = e10s && debug [browser_dbg_split-console-paused-reload.js] skip-if = e10s && debug [browser_dbg_stack-01.js] diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/doc_script-bookmarklet.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/doc_script-bookmarklet.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/debugger/test/doc_script-bookmarklet.html 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/debugger/test/doc_script-bookmarklet.html 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,14 @@ + + + + + + + Debugger test page + + + + + + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/framework/target.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/framework/target.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/framework/target.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/framework/target.js 2015-02-28 15:03:02.000000000 +0000 @@ -184,7 +184,13 @@ * Returns a promise for the protocol description from the root actor. * Used internally with `target.actorHasMethod`. Takes advantage of * caching if definition was fetched previously with the corresponding - * actor information. Must be a remote target. + * actor information. Actors are lazily loaded, so not only must the tool using + * a specific actor be in use, the actors are only registered after invoking + * a method (for performance reasons, added in bug 988237), so to use these actor + * detection methods, one must already be communicating with a specific actor of + * that type. + * + * Must be a remote target. * * @return {Promise} * { @@ -250,7 +256,8 @@ /** * Queries the protocol description to see if an actor has - * an available method. The actor must already be lazily-loaded, + * an available method. The actor must already be lazily-loaded (read + * the restrictions in the `getActorDescription` comments), * so this is for use inside of tool. Returns a promise that * resolves to a boolean. Must be a remote target. * diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/framework/toolbox-options.xul thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/framework/toolbox-options.xul --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/framework/toolbox-options.xul 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/framework/toolbox-options.xul 2015-02-28 15:03:02.000000000 +0000 @@ -149,8 +149,8 @@ label="&options.disableJavaScript.label;" tooltiptext="&options.disableJavaScript.tooltip;"/> - diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/jar.mn thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/jar.mn --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/jar.mn 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/jar.mn 2015-02-28 15:03:02.000000000 +0000 @@ -76,6 +76,8 @@ content/browser/devtools/shadereditor.js (shadereditor/shadereditor.js) content/browser/devtools/canvasdebugger.xul (canvasdebugger/canvasdebugger.xul) content/browser/devtools/canvasdebugger.js (canvasdebugger/canvasdebugger.js) + content/browser/devtools/canvasdebugger/snapshotslist.js (canvasdebugger/snapshotslist.js) + content/browser/devtools/canvasdebugger/callslist.js (canvasdebugger/callslist.js) content/browser/devtools/d3.js (shared/d3.js) content/browser/devtools/webaudioeditor.xul (webaudioeditor/webaudioeditor.xul) content/browser/devtools/dagre-d3.js (webaudioeditor/lib/dagre-d3.js) diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/layoutview/test/browser_layoutview.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/layoutview/test/browser_layoutview.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/layoutview/test/browser_layoutview.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/layoutview/test/browser_layoutview.js 2015-02-28 15:03:02.000000000 +0000 @@ -9,8 +9,8 @@ // Expected values: let res1 = [ - {selector: "#element-size", value: "160" + "\u00D7" + "160"}, - {selector: ".size > span", value: "100" + "\u00D7" + "100"}, + {selector: "#element-size", value: "160" + "\u00D7" + "160.117"}, + {selector: ".size > span", value: "100" + "\u00D7" + "100.117"}, {selector: ".margin.top > span", value: 30}, {selector: ".margin.left > span", value: "auto"}, {selector: ".margin.bottom > span", value: 30}, @@ -43,7 +43,7 @@ ]; add_task(function*() { - let style = "div { position: absolute; top: 42px; left: 42px; height: 100px; width: 100px; border: 10px solid black; padding: 20px; margin: 30px auto;}"; + let style = "div { position: absolute; top: 42px; left: 42px; height: 100.111px; width: 100px; border: 10px solid black; padding: 20px; margin: 30px auto;}"; let html = "
" yield addTab("data:text/html," + encodeURIComponent(html)); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/layoutview/view.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/layoutview/view.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/layoutview/view.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/layoutview/view.js 2015-02-28 15:03:02.000000000 +0000 @@ -422,7 +422,7 @@ // might be missing. continue; } - let parsedValue = parseInt(layout[property]); + let parsedValue = parseFloat(layout[property]); if (Number.isNaN(parsedValue)) { // Not a number. We use the raw string. // Useful for "position" for example. @@ -451,9 +451,10 @@ width -= this.map.borderLeft.value + this.map.borderRight.value + this.map.paddingLeft.value + this.map.paddingRight.value; - + width = parseFloat(width.toPrecision(6)); height -= this.map.borderTop.value + this.map.borderBottom.value + this.map.paddingTop.value + this.map.paddingBottom.value; + height = parseFloat(height.toPrecision(6)); let newValue = width + "\u00D7" + height; if (this.sizeLabel.textContent != newValue) { diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/main.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/main.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/main.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/main.js 2015-02-28 15:03:02.000000000 +0000 @@ -219,7 +219,7 @@ tooltip: l10n("ToolboxShaderEditor.tooltip", shaderEditorStrings), isTargetSupported: function(target) { - return target.hasActor("webgl"); + return target.hasActor("webgl") && !target.chrome; }, build: function(iframeWindow, toolbox) { diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/markup-view.css thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/markup-view.css --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/markup-view.css 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/markup-view.css 2015-02-28 15:03:02.000000000 +0000 @@ -14,6 +14,11 @@ min-width: 100%; } +body.dragging .tag-line { + cursor: grabbing; + -moz-user-select: none; +} + #root-wrapper:after { content: ""; display: block; @@ -63,6 +68,40 @@ padding-left: 1001em; } +/* Normally this element takes space in the layout even if it's position: relative + * by adding height: 0 we let surrounding elements to fill the blank space */ +.child.dragging { + position: relative; + pointer-events: none; + opacity: 0.7; + height: 0; +} + +/* Indicates a tag-line in the markup-view as being an active drop target by + * drawing a horizontal line where the dragged element would be inserted if + * dropped here */ +.tag-line.drop-target::before, .tag-line.drag-target::before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; +} + +.tag-line.drag-target::before { + border-top: 2px dashed var(--theme-contrast-background); +} + +.tag-line.drop-target::before { + border-top: 2px dashed var(--theme-content-color1); +} + +/* In case the indicator is put on the closing .tag-line, the indentation level + * will become misleading, so we push it forward to match the indentation level */ +ul.children + .tag-line::before { + margin-left: 14px; +} + .tag-line { min-height: 1.4em; line-height: 1.4em; diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/markup-view.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/markup-view.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/markup-view.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/markup-view.js 2015-02-28 15:03:02.000000000 +0000 @@ -14,6 +14,10 @@ const COLLAPSE_DATA_URL_REGEX = /^data.+base64/; const COLLAPSE_DATA_URL_LENGTH = 60; const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000; +const GRAB_DELAY = 400; +const DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE = 50; +const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 5; +const DRAG_DROP_MAX_AUTOSCROLL_SPEED = 15; const {UndoStack} = require("devtools/shared/undo"); const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor"); @@ -62,6 +66,7 @@ this._inspector = aInspector; this.walker = this._inspector.walker; this._frame = aFrame; + this.win = this._frame.contentWindow; this.doc = this._frame.contentDocument; this._elt = this.doc.querySelector("#root"); this.htmlEditor = new HTMLEditor(this.doc); @@ -94,6 +99,9 @@ this._onMouseClick = this._onMouseClick.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this.doc.body.addEventListener("mouseup", this._onMouseUp); + this._boundOnNewSelection = this._onNewSelection.bind(this); this._inspector.selection.on("new-node-front", this._boundOnNewSelection); this._onNewSelection(); @@ -156,7 +164,49 @@ } }, + isDragging: false, + _onMouseMove: function(event) { + if (this.isDragging) { + event.preventDefault(); + this._dragStartEl = event.target; + + let docEl = this.doc.documentElement; + + if (this._scrollInterval) { + this.win.clearInterval(this._scrollInterval); + } + + // Auto-scroll when the mouse approaches top/bottom edge + let distanceFromBottom = docEl.clientHeight - event.pageY + this.win.scrollY, + distanceFromTop = event.pageY - this.win.scrollY; + + if (distanceFromBottom <= DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE) { + // Map our distance from 0-50 to 5-15 range so the speed is kept + // in a range not too fast, not too slow + let speed = map(distanceFromBottom, 0, DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE, + DRAG_DROP_MIN_AUTOSCROLL_SPEED, DRAG_DROP_MAX_AUTOSCROLL_SPEED); + // Here, we use minus because the value of speed - 15 is always negative + // and it makes the speed relative to the distance between mouse and edge + // the closer to the edge, the faster + this._scrollInterval = this.win.setInterval(() => { + docEl.scrollTop -= speed - DRAG_DROP_MAX_AUTOSCROLL_SPEED; + }, 0); + } + + if (distanceFromTop <= DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE) { + // refer to bottom edge's comments for more info + let speed = map(distanceFromTop, 0, DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE, + DRAG_DROP_MIN_AUTOSCROLL_SPEED, DRAG_DROP_MAX_AUTOSCROLL_SPEED); + + this._scrollInterval = this.win.setInterval(() => { + docEl.scrollTop += speed - DRAG_DROP_MAX_AUTOSCROLL_SPEED; + }, 0); + } + + return; + }; + let target = event.target; // Search target for a markupContainer reference, if not found, walk up @@ -198,6 +248,18 @@ } }, + _onMouseUp: function() { + if (this._lastDropTarget) { + this.indicateDropTarget(null); + } + if (this._lastDragTarget) { + this.indicateDragTarget(null); + } + if (this._scrollInterval) { + this.win.clearInterval(this._scrollInterval); + } + }, + _hoveredNode: null, /** @@ -218,6 +280,11 @@ }, _onMouseLeave: function() { + if (this._scrollInterval) { + this.win.clearInterval(this._scrollInterval); + } + if (this.isDragging) return; + this._hideBoxModel(true); if (this._hoveredNode) { this.getContainer(this._hoveredNode).hovered = false; @@ -790,6 +857,10 @@ */ _expandContainer: function(aContainer) { return this._updateChildren(aContainer, {expand: true}).then(() => { + if (this._destroyer) { + console.warn("Could not expand the node, the markup-view was destroyed"); + return; + } aContainer.expanded = true; }); }, @@ -1361,6 +1432,12 @@ this.tooltip.destroy(); this.tooltip = null; + this.win = null; + this.doc = null; + + this._lastDropTarget = null; + this._lastDragTarget = null; + return this._destroyer; }, @@ -1448,6 +1525,80 @@ this._updatePreview(); this._previewBar.classList.remove("hide"); }, 1000); + }, + + /** + * Takes an element as it's only argument and marks the element + * as the drop target + */ + indicateDropTarget: function(el) { + if (this._lastDropTarget) { + this._lastDropTarget.classList.remove("drop-target"); + } + + if (!el) return; + + let target = el.classList.contains("tag-line") ? + el : el.querySelector(".tag-line") || el.closest(".tag-line"); + if (!target) return; + + target.classList.add("drop-target"); + this._lastDropTarget = target; + }, + + /** + * Takes an element to mark it as indicator of dragging target's initial place + */ + indicateDragTarget: function(el) { + if (this._lastDragTarget) { + this._lastDragTarget.classList.remove("drag-target"); + } + + if (!el) return; + + let target = el.classList.contains("tag-line") ? + el : el.querySelector(".tag-line") || el.closest(".tag-line"); + + if (!target) return; + + target.classList.add("drag-target"); + this._lastDragTarget = target; + }, + + /** + * Used to get the nodes required to modify the markup after dragging the element (parent/nextSibling) + */ + get dropTargetNodes() { + let target = this._lastDropTarget; + + if (!target) { + return null; + } + + let parent, nextSibling; + + if (this._lastDropTarget.previousElementSibling && + this._lastDropTarget.previousElementSibling.nodeName.toLowerCase() === "ul") { + parent = target.parentNode.container.node; + nextSibling = null; + } else { + parent = target.parentNode.container.node.parentNode(); + nextSibling = target.parentNode.container.node; + } + + if (nextSibling && nextSibling.isBeforePseudoElement) { + nextSibling = target.parentNode.parentNode.children[1].container.node; + } + if (nextSibling && nextSibling.isAfterPseudoElement) { + parent = target.parentNode.container.node.parentNode(); + nextSibling = null; + } + + if (parent.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) { + return null; + } + + return {parent, nextSibling}; } }; @@ -1481,6 +1632,7 @@ this.markup = markupView; this.node = node; this.undo = this.markup.undo; + this.win = this.markup._frame.contentWindow; // The template will fill the following properties this.elt = null; @@ -1491,15 +1643,16 @@ this.markup.template(templateID, this); this.elt.container = this; - // Binding event listeners this._onMouseDown = this._onMouseDown.bind(this); - this.elt.addEventListener("mousedown", this._onMouseDown, false); - this._onToggle = this._onToggle.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); - // Expanding/collapsing the node on dblclick of the whole tag-line element + // Binding event listeners + this.elt.addEventListener("mousedown", this._onMouseDown, false); + this.markup.doc.body.addEventListener("mouseup", this._onMouseUp, true); + this.markup.doc.body.addEventListener("mousemove", this._onMouseMove, true); this.elt.addEventListener("dblclick", this._onToggle, false); - if (this.expander) { this.expander.addEventListener("click", this._onToggle, false); } @@ -1618,24 +1771,109 @@ return this.elt.parentNode ? this.elt.parentNode.container : null; }, + _isMouseDown: false, + _isDragging: false, + _dragStartY: 0, + + set isDragging(isDragging) { + this._isDragging = isDragging; + this.markup.isDragging = isDragging; + + if (isDragging) { + this.elt.classList.add("dragging"); + this.markup.doc.body.classList.add("dragging"); + } else { + this.elt.classList.remove("dragging"); + this.markup.doc.body.classList.remove("dragging"); + } + }, + + get isDragging() { + return this._isDragging; + }, + _onMouseDown: function(event) { let target = event.target; - // Target may be a resource link (generated by the output-parser) + // The "show more nodes" button (already has its onclick). + if (target.nodeName === "button") { + return; + } + + // output-parser generated links handling. if (target.nodeName === "a") { event.stopPropagation(); event.preventDefault(); let browserWin = this.markup._inspector.target .tab.ownerDocument.defaultView; browserWin.openUILinkIn(target.href, "tab"); + return; } - // Or it may be the "show more nodes" button (which already has its onclick) - // Else, it's the container itself - else if (target.nodeName !== "button") { - this.hovered = false; - this.markup.navigate(this); - event.stopPropagation(); + + // target is the MarkupContainer itself. + this._isMouseDown = true; + this.hovered = false; + this.markup.navigate(this); + event.stopPropagation(); + + // Start dragging the container after a delay. + this.markup._dragStartEl = target; + this.win.setTimeout(() => { + // Make sure the mouse is still down and on target. + if (!this._isMouseDown || this.markup._dragStartEl !== target || + this.node.isPseudoElement || this.node.isAnonymous || + !this.win.getSelection().isCollapsed) { + return; + } + this.isDragging = true; + + this._dragStartY = event.pageY; + this.markup.indicateDropTarget(this.elt); + + // If this is the last child, use the closing of parent as indicator + this.markup.indicateDragTarget(this.elt.nextElementSibling || + this.markup.getContainer(this.node.parentNode()).closeTagLine); + }, GRAB_DELAY); + }, + + /** + * On mouse up, stop dragging. + */ + _onMouseUp: function(event) { + this._isMouseDown = false; + + if (!this.isDragging) { + return; + } + + this.isDragging = false; + this.elt.style.removeProperty("top"); + + let dropTargetNodes = this.markup.dropTargetNodes; + + if(!dropTargetNodes) { + return; + } + + this.markup.walker.insertBefore(this.node, dropTargetNodes.parent, + dropTargetNodes.nextSibling); + }, + + /** + * On mouse move, move the dragged element if any and indicate the drop target. + */ + _onMouseMove: function(event) { + if (!this.isDragging) { + return; } + + let diff = event.pageY - this._dragStartY; + this.elt.style.top = diff + "px"; + + let el = this.markup.doc.elementFromPoint(event.pageX - this.win.scrollX, + event.pageY - this.win.scrollY); + + this.markup.indicateDropTarget(el); }, /** @@ -1644,7 +1882,7 @@ */ flashMutation: function() { if (!this.selected) { - let contentWin = this.markup._frame.contentWindow; + let contentWin = this.win; this.flashed = true; if (this._flashMutationTimer) { contentWin.clearTimeout(this._flashMutationTimer); @@ -1777,6 +2015,10 @@ // Remove event listeners this.elt.removeEventListener("mousedown", this._onMouseDown, false); this.elt.removeEventListener("dblclick", this._onToggle, false); + this.markup.doc.body.removeEventListener("mouseup", this._onMouseUp, true); + this.markup.doc.body.removeEventListener("mousemove", this._onMouseMove, true); + + this.win = null; if (this.expander) { this.expander.removeEventListener("click", this._onToggle, false); @@ -2515,6 +2757,17 @@ return attributes.reverse(); } +/** + * Map a number from one range to another. + */ +function map(value, oldMin, oldMax, newMin, newMax) { + let ratio = oldMax - oldMin; + if (ratio == 0) { + return value; + } + return newMin + (newMax - newMin) * ((value - oldMin) / ratio); +} + loader.lazyGetter(MarkupView.prototype, "strings", () => Services.strings.createBundle( "chrome://browser/locale/devtools/inspector.properties" )); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser.ini thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser.ini --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser.ini 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser.ini 2015-02-28 15:03:02.000000000 +0000 @@ -2,6 +2,8 @@ subsuite = devtools support-files = doc_markup_anonymous.html + doc_markup_dragdrop.html + doc_markup_dragdrop_autoscroll.html doc_markup_edit.html doc_markup_events.html doc_markup_events_jquery.html @@ -37,6 +39,11 @@ [browser_markupview_anonymous_04.js] [browser_markupview_copy_image_data.js] [browser_markupview_css_completion_style_attribute.js] +[browser_markupview_dragdrop_autoscroll.js] +[browser_markupview_dragdrop_invalidNodes.js] +[browser_markupview_dragdrop_isDragging.js] +[browser_markupview_dragdrop_reorder.js] +[browser_markupview_dragdrop_textSelection.js] [browser_markupview_events.js] skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible [browser_markupview_events-overflow.js] diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_autoscroll.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_autoscroll.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_autoscroll.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_autoscroll.js 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,61 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test: Dragging nodes near top/bottom edges of inspector +// should auto-scroll + +const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop_autoscroll.html"; +const GRAB_DELAY = 400; + +add_task(function*() { + let {inspector} = yield addTab(TEST_URL).then(openInspector); + + let markup = inspector.markup; + + let container = yield getContainerForSelector("#first", inspector); + let rect = container.elt.getBoundingClientRect(); + + info("Simulating mouseDown on #first"); + container._onMouseDown({ + target: container.tagLine, + pageX: 10, + pageY: rect.top, + stopPropagation: function() {}, + preventDefault: function() {} + }); + + yield wait(GRAB_DELAY + 1); + + let clientHeight = markup.doc.documentElement.clientHeight; + info("Simulating mouseMove on #first with pageY: " + clientHeight); + + let ev = { + target: container.tagLine, + pageX: 10, + pageY: clientHeight, + preventDefault: function() {} + }; + + info("Listening on scroll event"); + let scroll = onScroll(markup.win); + + markup._onMouseMove(ev); + + yield scroll; + + container._onMouseUp(ev); + markup._onMouseUp(ev); + + ok("Scroll event fired"); +}); + +function onScroll(win) { + return new Promise((resolve, reject) => { + win.onscroll = function(e) { + resolve(e); + } + }); +}; \ No newline at end of file diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_invalidNodes.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_invalidNodes.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_invalidNodes.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_invalidNodes.js 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,58 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test: pseudo-elements and anonymous nodes should not be draggable + +const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html"; +const GRAB_DELAY = 400; + +add_task(function*() { + Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true); + + let {inspector} = yield addTab(TEST_URL).then(openInspector); + + info("Expanding #test"); + let parentFront = yield getNodeFront("#test", inspector); + yield inspector.markup.expandNode(parentFront); + yield waitForMultipleChildrenUpdates(inspector); + + let parentContainer = yield getContainerForNodeFront(parentFront, inspector); + let beforePseudo = parentContainer.elt.children[1].firstChild.container; + + parentContainer.elt.scrollIntoView(true); + + info("Simulating mouseDown on #test::before"); + beforePseudo._onMouseDown({ + target: beforePseudo.tagLine, + stopPropagation: function() {}, + preventDefault: function() {} + }); + + info("Waiting " + (GRAB_DELAY + 1) + "ms") + yield wait(GRAB_DELAY + 1); + is(beforePseudo.isDragging, false, "[pseudo-element] isDragging is false after GRAB_DELAY has passed"); + + let inputFront = yield getNodeFront("#anonymousParent", inspector); + + yield inspector.markup.expandNode(inputFront); + yield waitForMultipleChildrenUpdates(inspector); + + let inputContainer = yield getContainerForNodeFront(inputFront, inspector); + let anonymousDiv = inputContainer.elt.children[1].firstChild.container; + + inputContainer.elt.scrollIntoView(true); + + info("Simulating mouseDown on input#anonymousParent div"); + anonymousDiv._onMouseDown({ + target: anonymousDiv.tagLine, + stopPropagation: function() {}, + preventDefault: function() {} + }); + + info("Waiting " + (GRAB_DELAY + 1) + "ms") + yield wait(GRAB_DELAY + 1); + is(anonymousDiv.isDragging, false, "[anonymous element] isDragging is false after GRAB_DELAY has passed"); +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_isDragging.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_isDragging.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_isDragging.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_isDragging.js 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,41 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test drag mode's delay, it shouldn't enable dragging before +// GRAB_DELAY = 400 has passed + +const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html"; +const GRAB_DELAY = 400; + +add_task(function*() { + let {inspector} = yield addTab(TEST_URL).then(openInspector); + + let el = yield getContainerForSelector("#test", inspector); + let rect = el.tagLine.getBoundingClientRect(); + + info("Simulating mouseDown on #test"); + el._onMouseDown({ + target: el.tagLine, + pageX: rect.x, + pageY: rect.y, + stopPropagation: function() {} + }); + + is(el.isDragging, false, "isDragging should not be set to true immedietly"); + + info("Waiting " + (GRAB_DELAY + 1) + "ms"); + yield wait(GRAB_DELAY + 1); + ok(el.isDragging, "isDragging true after GRAB_DELAY has passed"); + + info("Simulating mouseUp on #test"); + el._onMouseUp({ + target: el.tagLine, + pageX: rect.x, + pageY: rect.y + }); + + is(el.isDragging, false, "isDragging false after mouseUp"); +}); diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_reorder.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_reorder.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_reorder.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_reorder.js 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,109 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test different kinds of drag and drop node re-ordering + +const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html"; +const GRAB_DELAY = 400; + +add_task(function*() { + let {inspector} = yield addTab(TEST_URL).then(openInspector); + + info("Expanding #test"); + let parentFront = yield getNodeFront("#test", inspector); + let parent = yield getNode("#test"); + let parentContainer = yield getContainerForNodeFront(parentFront, inspector); + + yield inspector.markup.expandNode(parentFront); + yield waitForMultipleChildrenUpdates(inspector); + + parentContainer.elt.scrollIntoView(true); + + info("Testing switching elements inside their parent"); + yield moveElementDown("#firstChild", "#middleChild", inspector); + + is(parent.children[0].id, "middleChild", "#firstChild is now the second child of #test"); + is(parent.children[1].id, "firstChild", "#middleChild is now the first child of #test"); + + info("Testing switching elements with a last child"); + yield moveElementDown("#firstChild", "#lastChild", inspector); + + is(parent.children[1].id, "lastChild", "#lastChild is now the second child of #test"); + is(parent.children[2].id, "firstChild", "#firstChild is now the last child of #test"); + + info("Testing appending element to a parent"); + yield moveElementDown("#before", "#test", inspector); + + is(parent.children.length, 4, "New element appended to #test"); + is(parent.children[0].id, "before", "New element is appended at the right place (currently first child)"); + + info("Testing moving element to after it's parent"); + yield moveElementDown("#firstChild", "#test", inspector); + + is(parent.children.length, 3, "#firstChild is no longer #test's child"); + is(parent.nextElementSibling.id, "firstChild", "#firstChild is now #test's nextElementSibling"); +}); + +function* dragContainer(selector, targetOffset, inspector) { + info("Dragging the markup-container for node " + selector); + + let container = yield getContainerForSelector(selector, inspector); + + let updated = inspector.once("inspector-updated"); + + let rect = { + x: container.tagLine.offsetLeft, + y: container.tagLine.offsetTop + }; + + info("Simulating mouseDown on " + selector); + container._onMouseDown({ + target: container.tagLine, + pageX: rect.x, + pageY: rect.y, + stopPropagation: function() {} + }); + + let targetX = rect.x + targetOffset.x, + targetY = rect.y + targetOffset.y; + + setTimeout(() => { + info("Simulating mouseMove on " + selector + + " with pageX: " + targetX + " pageY: " + targetY); + container._onMouseMove({ + target: container.tagLine, + pageX: targetX, + pageY: targetY + }); + + info("Simulating mouseUp on " + selector + + " with pageX: " + targetX + " pageY: " + targetY); + container._onMouseUp({ + target: container.tagLine, + pageX: targetX, + pageY: targetY + }); + + container.markup._onMouseUp(); + }, GRAB_DELAY+1); + + return updated; +}; + +function* moveElementDown(selector, next, inspector) { + let onMutated = inspector.once("markupmutation"); + let uiUpdate = inspector.once("inspector-updated"); + + let el = yield getContainerForSelector(next, inspector); + let height = el.tagLine.getBoundingClientRect().height; + + info("Switching " + selector + ' with ' + next); + + yield dragContainer(selector, {x: 0, y: Math.round(height) + 2}, inspector); + + yield onMutated; + yield uiUpdate; +}; \ No newline at end of file diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_textSelection.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_textSelection.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_textSelection.js 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_dragdrop_textSelection.js 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,48 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test: Nodes should not be draggable if there is a text selected +// (trying to move selected text around shouldn't trigger node drag and drop) + +const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html"; +const GRAB_DELAY = 400; + +add_task(function*() { + let {inspector} = yield addTab(TEST_URL).then(openInspector); + let markup = inspector.markup; + + info("Expanding span#before"); + let spanFront = yield getNodeFront("#before", inspector); + let spanContainer = yield getContainerForNodeFront(spanFront, inspector); + let span = yield getNode("#before"); + + yield inspector.markup.expandNode(spanFront); + yield waitForMultipleChildrenUpdates(inspector); + + spanContainer.elt.scrollIntoView(true); + + info("Selecting #before's text content"); + + let textContent = spanContainer.elt.children[1].firstChild.container; + + let selectRange = markup.doc.createRange(); + selectRange.selectNode(textContent.editor.elt.querySelector('[tabindex]')); + markup.doc.getSelection().addRange(selectRange); + + info("Simulating mouseDown on #before"); + + spanContainer._onMouseDown({ + pageX: 0, + pageY: 0, + target: spanContainer.tagLine, + stopPropagation: function() {}, + preventDefault: function() {} + }); + + yield wait(GRAB_DELAY + 1); + + is(spanContainer.isDragging, false, "isDragging should be false if there is a text selected"); +}); \ No newline at end of file diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_textcontent_edit_01.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_textcontent_edit_01.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_textcontent_edit_01.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_textcontent_edit_01.js 2015-02-28 15:03:02.000000000 +0000 @@ -31,15 +31,3 @@ yield inspector.once("inspector-updated"); }); - -// The expand all operation of the markup-view calls itself recursively and -// there's not one event we can wait for to know when it's done -function* waitForMultipleChildrenUpdates(inspector) { - // As long as child updates are queued up while we wait for an update already - // wait again - if (inspector.markup._queuedChildUpdates && - inspector.markup._queuedChildUpdates.size) { - yield waitForChildrenUpdated(inspector); - return yield waitForMultipleChildrenUpdates(inspector); - } -} diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_toggle_03.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_toggle_03.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/browser_markupview_toggle_03.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/browser_markupview_toggle_03.js 2015-02-28 15:03:02.000000000 +0000 @@ -33,15 +33,3 @@ "Container for node " + nodeFront.tagName + " is expanded"); } }); - -// The expand all operation of the markup-view calls itself recursively and -// there's not one event we can wait for to know when it's done -function* waitForMultipleChildrenUpdates(inspector) { - // As long as child updates are queued up while we wait for an update already - // wait again - if (inspector.markup._queuedChildUpdates && - inspector.markup._queuedChildUpdates.size) { - yield waitForChildrenUpdated(inspector); - return yield waitForMultipleChildrenUpdates(inspector); - } -} diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/doc_markup_dragdrop_autoscroll.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/doc_markup_dragdrop_autoscroll.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/doc_markup_dragdrop_autoscroll.html 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/doc_markup_dragdrop_autoscroll.html 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,51 @@ + + + + + + Test for Bug 858038 - Autoscroll + + +
+ Mozilla Bug 858038 +

Test

+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/doc_markup_dragdrop.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/doc_markup_dragdrop.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/doc_markup_dragdrop.html 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/doc_markup_dragdrop.html 2015-02-28 15:03:02.000000000 +0000 @@ -0,0 +1,31 @@ + + + + + + Test for Bug 858038 + + + + Mozilla Bug 858038 +

+ + + + Before +
+    First
+    Middle
+    Last
+  
+ After + + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/head.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/head.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/markupview/test/head.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/markupview/test/head.js 2015-02-28 15:03:02.000000000 +0000 @@ -561,3 +561,16 @@ editMode ? "input": "span", editMode ? attrName + " is in edit mode" : attrName + " is not in edit mode"); } + +// The expand all operation of the markup-view calls itself recursively and +// there's not one event we can wait for to know when it's done +// so use this helper function to wait until all recursive children updates are done. +function* waitForMultipleChildrenUpdates(inspector) { + // As long as child updates are queued up while we wait for an update already + // wait again + if (inspector.markup._queuedChildUpdates && + inspector.markup._queuedChildUpdates.size) { + yield waitForChildrenUpdated(inspector); + return yield waitForMultipleChildrenUpdates(inspector); + } +} diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/netmonitor/netmonitor.css thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/netmonitor/netmonitor.css --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/netmonitor/netmonitor.css 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/netmonitor/netmonitor.css 2015-02-28 15:03:02.000000000 +0000 @@ -28,7 +28,7 @@ overflow: auto; } -#headers-summary-url-value .textbox-input { +.cropped-textbox .textbox-input { /* workaround for textbox not supporting the @crop attribute */ text-overflow: ellipsis; } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/netmonitor/netmonitor-view.js thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/netmonitor/netmonitor-view.js --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/netmonitor/netmonitor-view.js 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/netmonitor/netmonitor-view.js 2015-02-28 15:03:02.000000000 +0000 @@ -2743,21 +2743,22 @@ } /** - * A helper that sets label text to specified value. + * A helper that sets value and tooltiptext attributes of an element to + * specified value. * * @param string selector - * A selector for the label. + * A selector for the element. * @param string value - * The value label should have. If this evaluates to false a - * placeholder string is used instead. + * The value to set. If this evaluates to false a placeholder string + * is used instead. */ - function setLabel(selector, value) { + function setValue(selector, value) { let label = $(selector); if (!value) { - label.value = L10N.getStr("netmonitor.security.notAvailable"); - label.setAttribute("tooltiptext", label.value); + label.setAttribute("value", L10N.getStr("netmonitor.security.notAvailable")); + label.setAttribute("tooltiptext", label.getAttribute("value")); } else { - label.value = value; + label.setAttribute("value", value); label.setAttribute("tooltiptext", value); } } @@ -2785,43 +2786,43 @@ let disabledLabel = L10N.getStr("netmonitor.security.disabled"); // Connection parameters - setLabel("#security-protocol-version-value", securityInfo.protocolVersion); - setLabel("#security-ciphersuite-value", securityInfo.cipherSuite); + setValue("#security-protocol-version-value", securityInfo.protocolVersion); + setValue("#security-ciphersuite-value", securityInfo.cipherSuite); // Host header let domain = NetMonitorView.RequestsMenu._getUriHostPort(url); let hostHeader = L10N.getFormatStr("netmonitor.security.hostHeader", domain); - setLabel("#security-info-host-header", hostHeader); + setValue("#security-info-host-header", hostHeader); // Parameters related to the domain - setLabel("#security-http-strict-transport-security-value", + setValue("#security-http-strict-transport-security-value", securityInfo.hsts ? enabledLabel : disabledLabel); - setLabel("#security-public-key-pinning-value", + setValue("#security-public-key-pinning-value", securityInfo.hpkp ? enabledLabel : disabledLabel); // Certificate parameters let cert = securityInfo.cert; - setLabel("#security-cert-subject-cn", cert.subject.commonName); - setLabel("#security-cert-subject-o", cert.subject.organization); - setLabel("#security-cert-subject-ou", cert.subject.organizationalUnit); - - setLabel("#security-cert-issuer-cn", cert.issuer.commonName); - setLabel("#security-cert-issuer-o", cert.issuer.organization); - setLabel("#security-cert-issuer-ou", cert.issuer.organizationalUnit); + setValue("#security-cert-subject-cn", cert.subject.commonName); + setValue("#security-cert-subject-o", cert.subject.organization); + setValue("#security-cert-subject-ou", cert.subject.organizationalUnit); + + setValue("#security-cert-issuer-cn", cert.issuer.commonName); + setValue("#security-cert-issuer-o", cert.issuer.organization); + setValue("#security-cert-issuer-ou", cert.issuer.organizationalUnit); - setLabel("#security-cert-validity-begins", cert.validity.start); - setLabel("#security-cert-validity-expires", cert.validity.end); + setValue("#security-cert-validity-begins", cert.validity.start); + setValue("#security-cert-validity-expires", cert.validity.end); - setLabel("#security-cert-sha1-fingerprint", cert.fingerprint.sha1); - setLabel("#security-cert-sha256-fingerprint", cert.fingerprint.sha256); + setValue("#security-cert-sha1-fingerprint", cert.fingerprint.sha1); + setValue("#security-cert-sha256-fingerprint", cert.fingerprint.sha256); } else { infobox.hidden = true; errorbox.hidden = false; // Strip any HTML from the message. let plain = DOMParser.parseFromString(securityInfo.errorMessage, "text/html"); - $("#security-error-message").textContent = plain.body.textContent; + setValue("#security-error-message", plain.body.textContent); } }), diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/netmonitor/netmonitor.xul thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/netmonitor/netmonitor.xul --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/browser/devtools/netmonitor/netmonitor.xul 2015-02-19 00:42:07.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/browser/devtools/netmonitor/netmonitor.xul 2015-02-28 15:03:02.000000000 +0000 @@ -300,7 +300,7 @@
@@ -488,7 +488,14 @@ flex="1">
diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/dom/base/test/test_messagemanager_assertpermission.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/dom/base/test/test_messagemanager_assertpermission.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/dom/base/test/test_messagemanager_assertpermission.html 2015-02-19 00:42:11.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/dom/base/test/test_messagemanager_assertpermission.html 2015-02-28 15:03:05.000000000 +0000 @@ -27,8 +27,6 @@ .getService(SpecialPowers.Ci.nsIAppsService); function setUp() { - SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true); - SpecialPowers.setBoolPref("dom.ipc.browser_frames.oop_by_default", true); SpecialPowers.addPermission("browser", true, window.document); SpecialPowers.addPermission("embed-apps", true, window.document); @@ -36,7 +34,8 @@ SpecialPowers.addPermission("foobar", true, { url: APP_URL, appId: appId, isInBrowserElement: false }); - runNextTest(); + SpecialPowers.pushPrefEnv({"set":[['dom.mozBrowserFramesEnabled', true], + ['dom.ipc.browser_frames.oop_by_default', true]]}, runNextTest); } /** @@ -168,8 +167,6 @@ } function tearDown() { - SpecialPowers.clearUserPref("dom.mozBrowserFramesEnabled"); - SpecialPowers.clearUserPref("dom.ipc.browser_frames.oop_by_default"); SimpleTest.finish(); } diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/dom/base/test/test_simplecontentpolicy.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/dom/base/test/test_simplecontentpolicy.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/dom/base/test/test_simplecontentpolicy.html 1970-01-01 00:00:00.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/dom/base/test/test_simplecontentpolicy.html 2015-02-28 15:03:05.000000000 +0000 @@ -0,0 +1,149 @@ + + + + Test for nsISimpleContentPolicy + + + + +Mozilla Bug 1128798 +

+ +
+
+
+ + + diff -Nru thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/dom/base/test/test_websocket_permessage_deflate.html thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/dom/base/test/test_websocket_permessage_deflate.html --- thunderbird-trunk-38.0~a1~hg20150218r17506.229588/mozilla/dom/base/test/test_websocket_permessage_deflate.html 2015-02-19 00:42:11.000000000 +0000 +++ thunderbird-trunk-39.0~a1~hg20150226r17577.231190/mozilla/dom/base/test/test_websocket_permessage_deflate.html 2015-02-28 15:03:05.000000000 +0000 @@ -5,7 +5,7 @@ - + Mozilla Bug