зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound, a=merge
MozReview-Commit-ID: 4CJDJBAcVPL
This commit is contained in:
Коммит
5ee13ebe8a
|
@ -8,6 +8,7 @@
|
|||
|
||||
# UITour
|
||||
origin uitour 1 https://www.mozilla.org
|
||||
origin uitour 1 https://screenshots.firefox.com
|
||||
origin uitour 1 https://support.mozilla.org
|
||||
origin uitour 1 https://addons.mozilla.org
|
||||
origin uitour 1 https://discovery.addons.mozilla.org
|
||||
|
|
|
@ -1153,7 +1153,7 @@ var PlacesToolbarHelper = {
|
|||
return document.getElementById("PlacesToolbar");
|
||||
},
|
||||
|
||||
init: function PTH_init(forceToolbarOverflowCheck) {
|
||||
init: function PTH_init() {
|
||||
let viewElt = this._viewElt;
|
||||
if (!viewElt || viewElt._placesView)
|
||||
return;
|
||||
|
@ -1180,15 +1180,12 @@ var PlacesToolbarHelper = {
|
|||
}
|
||||
|
||||
new PlacesToolbar(this._place);
|
||||
if (forceToolbarOverflowCheck) {
|
||||
viewElt._placesView.updateOverflowStatus();
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "toolbarvisibilitychange":
|
||||
if (event.target == this._viewElt.parentNode.parentNode)
|
||||
if (event.target == this._getParentToolbar(this._viewElt))
|
||||
this._resetView();
|
||||
break;
|
||||
}
|
||||
|
@ -1214,7 +1211,7 @@ var PlacesToolbarHelper = {
|
|||
|
||||
customizeDone: function PTH_customizeDone() {
|
||||
this._isCustomizing = false;
|
||||
this.init(true);
|
||||
this.init();
|
||||
},
|
||||
|
||||
onPlaceholderCommand() {
|
||||
|
@ -1265,7 +1262,7 @@ var PlacesToolbarHelper = {
|
|||
if (this._viewElt._placesView) {
|
||||
this._viewElt._placesView.uninit();
|
||||
}
|
||||
this.init(true);
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7494,20 +7494,20 @@ var gIdentityHandler = {
|
|||
}
|
||||
|
||||
// Push the appropriate strings out to the UI
|
||||
this._connectionIcon.tooltipText = tooltip;
|
||||
this._connectionIcon.setAttribute("tooltiptext", tooltip);
|
||||
|
||||
if (this._isExtensionPage) {
|
||||
let extensionName = extensionNameFromURI(this._uri);
|
||||
this._extensionIcon.tooltipText = gNavigatorBundle.getFormattedString(
|
||||
"identity.extension.tooltip", [extensionName]);
|
||||
this._extensionIcon.setAttribute("tooltiptext",
|
||||
gNavigatorBundle.getFormattedString("identity.extension.tooltip", [extensionName]));
|
||||
}
|
||||
|
||||
this._identityIconLabels.tooltipText = tooltip;
|
||||
this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip");
|
||||
this._identityIconLabel.value = icon_label;
|
||||
this._identityIconCountryLabel.value = icon_country_label;
|
||||
this._identityIconLabels.setAttribute("tooltiptext", tooltip);
|
||||
this._identityIcon.setAttribute("tooltiptext", gNavigatorBundle.getString("identity.icon.tooltip"));
|
||||
this._identityIconLabel.setAttribute("value", icon_label);
|
||||
this._identityIconCountryLabel.setAttribute("value", icon_country_label);
|
||||
// Set cropping and direction
|
||||
this._identityIconLabel.crop = icon_country_label ? "end" : "center";
|
||||
this._identityIconLabel.setAttribute("crop", icon_country_label ? "end" : "center");
|
||||
this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
|
||||
// Hide completely if the organization label is empty
|
||||
this._identityIconLabel.parentNode.collapsed = !icon_label;
|
||||
|
|
|
@ -35,6 +35,8 @@ input[type=button] {
|
|||
transition: opacity 100ms ease-out;
|
||||
-moz-box-align: center;
|
||||
-moz-box-pack: center;
|
||||
position: relative;
|
||||
left: -50%;
|
||||
}
|
||||
|
||||
#newtab-undo-container[undo-disabled] {
|
||||
|
@ -65,7 +67,7 @@ input[type=button] {
|
|||
|
||||
#newtab-margin-undo-container {
|
||||
display: -moz-box;
|
||||
left: 6px;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
z-index: 1;
|
||||
|
|
|
@ -6,8 +6,6 @@ skip-if = asan || debug # Bug 1382809, bug 1369959
|
|||
[browser_startup.js]
|
||||
[browser_startup_content.js]
|
||||
skip-if = !e10s
|
||||
[browser_startup_images.js]
|
||||
skip-if = !debug
|
||||
[browser_tabclose_grow_reflows.js]
|
||||
[browser_tabclose_reflows.js]
|
||||
[browser_tabopen_reflows.js]
|
||||
|
|
|
@ -42,7 +42,13 @@ const whitelist = [
|
|||
{
|
||||
file: "chrome://browser/skin/tabbrowser/tabDragIndicator.png",
|
||||
hidpi: "chrome://browser/skin/tabbrowser/tabDragIndicator@2x.png",
|
||||
platforms: ["linux", "win", "macosx"],
|
||||
platforms: ["macosx"],
|
||||
},
|
||||
|
||||
{
|
||||
file: "chrome://browser/skin/tabbrowser/tabDragIndicator.png",
|
||||
hidpi: "<not loaded>",
|
||||
platforms: ["linux", "win"],
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -68,6 +74,10 @@ const whitelist = [
|
|||
];
|
||||
|
||||
add_task(async function() {
|
||||
if (!AppConstants.DEBUG) {
|
||||
ok(false, "You need to run this test on a debug build.");
|
||||
}
|
||||
|
||||
let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
|
||||
await startupRecorder.done;
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
[DEFAULT]
|
||||
prefs =
|
||||
layout.css.devPixelsPerPx='2'
|
||||
|
||||
[../browser_startup_images.js]
|
||||
skip-if = !debug || (os == 'win' && os_version == '6.1') # hidpi results in the toolbar overflowing on Win 7
|
|
@ -0,0 +1,6 @@
|
|||
[DEFAULT]
|
||||
prefs =
|
||||
layout.css.devPixelsPerPx='1'
|
||||
|
||||
[../browser_startup_images.js]
|
||||
skip-if = !debug
|
|
@ -213,5 +213,19 @@ var tests = [
|
|||
notification.remove();
|
||||
goNext();
|
||||
}
|
||||
}
|
||||
},
|
||||
// the main action button should apply non-default(no highlight) style.
|
||||
{ id: "Test#11",
|
||||
run() {
|
||||
this.notifyObj = new BasicNotification(this.id);
|
||||
this.notifyObj.mainAction.disableHighlight = true;
|
||||
this.notifyObj.secondaryActions = undefined;
|
||||
this.notification = showNotification(this.notifyObj);
|
||||
},
|
||||
onShown(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
dismissNotification(popup);
|
||||
},
|
||||
onHidden() { }
|
||||
},
|
||||
];
|
||||
|
|
|
@ -210,6 +210,9 @@ function checkPopup(popup, notifyObj) {
|
|||
"main action label matches");
|
||||
is(notification.getAttribute("buttonaccesskey"),
|
||||
notifyObj.mainAction.accessKey, "main action accesskey matches");
|
||||
is(notification.getAttribute("buttonhighlight"),
|
||||
(!notifyObj.mainAction.disableHighlight).toString(),
|
||||
"main action highlight matches");
|
||||
}
|
||||
if (notifyObj.secondaryActions && notifyObj.secondaryActions.length > 0) {
|
||||
let secondaryAction = notifyObj.secondaryActions[0];
|
||||
|
|
|
@ -28,6 +28,8 @@ BROWSER_CHROME_MANIFESTS += [
|
|||
'content/test/newtab/browser.ini',
|
||||
'content/test/pageinfo/browser.ini',
|
||||
'content/test/performance/browser.ini',
|
||||
'content/test/performance/hidpi/browser.ini',
|
||||
'content/test/performance/lowdpi/browser.ini',
|
||||
'content/test/permissions/browser.ini',
|
||||
'content/test/plugins/browser.ini',
|
||||
'content/test/popupNotifications/browser.ini',
|
||||
|
|
|
@ -879,7 +879,7 @@ this.PanelMultiView = class {
|
|||
// direction that the panel originally opened in. This property resets
|
||||
// every time the popup closes, which is why we have to set it each time.
|
||||
this._panel.autoPosition = false;
|
||||
if (this.panelViews) {
|
||||
if (this.panelViews && !this.node.hasAttribute("disablekeynav")) {
|
||||
this.window.addEventListener("keydown", this);
|
||||
this._panel.addEventListener("mousemove", this);
|
||||
}
|
||||
|
|
|
@ -332,7 +332,7 @@
|
|||
position="bottomcenter topright"
|
||||
photon="true"
|
||||
hidden="true">
|
||||
<photonpanelmultiview mainViewId="widget-overflow-mainView">
|
||||
<photonpanelmultiview mainViewId="widget-overflow-mainView" disablekeynav="true">
|
||||
<panelview id="widget-overflow-mainView"
|
||||
context="toolbar-context-menu">
|
||||
<vbox class="panel-subview-body">
|
||||
|
|
|
@ -11,6 +11,7 @@ const SESSION = {
|
|||
|
||||
add_task(async function() {
|
||||
SessionStore.setBrowserState(JSON.stringify(SESSION));
|
||||
await promiseWindowRestored(window);
|
||||
const tab = gBrowser.tabs[1];
|
||||
|
||||
is(tab.getAttribute("pending"), "true", "The tab is pending restore");
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
* promiseContentDimensions alterContent
|
||||
* promisePrefChangeObserved openContextMenuInFrame
|
||||
* promiseAnimationFrame getCustomizableUIPanelID
|
||||
* promiseWindowRestored
|
||||
*/
|
||||
|
||||
// There are shutdown issues for which multiple rejections are left uncaught.
|
||||
|
@ -448,3 +449,7 @@ function promisePrefChangeObserved(pref) {
|
|||
resolve();
|
||||
}));
|
||||
}
|
||||
|
||||
function promiseWindowRestored(window) {
|
||||
return new Promise(resolve => window.addEventListener("SSWindowRestored", resolve, {once: true}));
|
||||
}
|
||||
|
|
|
@ -1678,7 +1678,7 @@ BrowserGlue.prototype = {
|
|||
|
||||
// eslint-disable-next-line complexity
|
||||
_migrateUI: function BG__migrateUI() {
|
||||
const UI_VERSION = 52;
|
||||
const UI_VERSION = 53;
|
||||
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
|
||||
|
||||
let currentUIVersion;
|
||||
|
@ -2056,6 +2056,19 @@ BrowserGlue.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
// Update user customizations that will interfere with the Safe Browsing V2
|
||||
// to V4 migration (bug 1395419).
|
||||
if (currentUIVersion < 53) {
|
||||
const MALWARE_PREF = "urlclassifier.malwareTable";
|
||||
if (Services.prefs.prefHasUserValue(MALWARE_PREF)) {
|
||||
let malwareList = Services.prefs.getCharPref(MALWARE_PREF);
|
||||
if (malwareList.indexOf("goog-malware-shavar") != -1) {
|
||||
malwareList.replace("goog-malware-shavar", "goog-malware-proto");
|
||||
Services.prefs.setCharPref(MALWARE_PREF, malwareList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the migration version.
|
||||
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
|
||||
},
|
||||
|
|
|
@ -1061,6 +1061,8 @@ PlacesToolbar.prototype = {
|
|||
this._insertNewItem(this._resultNode.getChild(i), fragment);
|
||||
}
|
||||
this._rootElt.appendChild(fragment);
|
||||
|
||||
this.updateNodesVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1158,7 +1160,7 @@ PlacesToolbar.prototype = {
|
|||
// and the chevron popup when a window resize does not change
|
||||
// the overflow status of the toolbar.
|
||||
if (aEvent.target == aEvent.currentTarget) {
|
||||
this.updateChevron();
|
||||
this.updateNodesVisibility();
|
||||
}
|
||||
break;
|
||||
case "overflow":
|
||||
|
@ -1173,7 +1175,7 @@ PlacesToolbar.prototype = {
|
|||
break;
|
||||
case "TabOpen":
|
||||
case "TabClose":
|
||||
this.updateChevron();
|
||||
this.updateNodesVisibility();
|
||||
break;
|
||||
case "dragstart":
|
||||
this._onDragStart(aEvent);
|
||||
|
@ -1210,14 +1212,6 @@ PlacesToolbar.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
updateOverflowStatus() {
|
||||
if (this._rootElt.scrollLeftMin != this._rootElt.scrollLeftMax) {
|
||||
this._onOverflow();
|
||||
} else {
|
||||
this._onUnderflow();
|
||||
}
|
||||
},
|
||||
|
||||
_isOverflowStateEventRelevant: function PT_isOverflowStateEventRelevant(aEvent) {
|
||||
// Ignore events not aimed at ourselves, as well as purely vertical ones:
|
||||
return aEvent.target == aEvent.currentTarget && aEvent.detail > 0;
|
||||
|
@ -1231,28 +1225,24 @@ PlacesToolbar.prototype = {
|
|||
this._chevronPopup.setAttribute("type", "places");
|
||||
}
|
||||
this._chevron.collapsed = false;
|
||||
this.updateChevron();
|
||||
this.updateNodesVisibility();
|
||||
},
|
||||
|
||||
_onUnderflow: function PT_onUnderflow() {
|
||||
this.updateChevron();
|
||||
this.updateNodesVisibility();
|
||||
this._chevron.collapsed = true;
|
||||
},
|
||||
|
||||
updateChevron: function PT_updateChevron() {
|
||||
// If the chevron is collapsed there's nothing to update.
|
||||
if (this._chevron.collapsed)
|
||||
return;
|
||||
|
||||
updateNodesVisibility: function PT_updateNodesVisibility() {
|
||||
// Update the chevron on a timer. This will avoid repeated work when
|
||||
// lot of changes happen in a small timeframe.
|
||||
if (this._updateChevronTimer)
|
||||
this._updateChevronTimer.cancel();
|
||||
if (this._updateNodesVisibilityTimer)
|
||||
this._updateNodesVisibilityTimer.cancel();
|
||||
|
||||
this._updateChevronTimer = this._setTimer(100);
|
||||
this._updateNodesVisibilityTimer = this._setTimer(100);
|
||||
},
|
||||
|
||||
_updateChevronTimerCallback: function PT__updateChevronTimerCallback() {
|
||||
_updateNodesVisibilityTimerCallback: function PT__updateNodesVisibilityTimerCallback() {
|
||||
let scrollRect = this._rootElt.getBoundingClientRect();
|
||||
let childOverflowed = false;
|
||||
for (let child of this._rootElt.childNodes) {
|
||||
|
@ -1276,7 +1266,7 @@ PlacesToolbar.prototype = {
|
|||
|
||||
// We rebuild the chevron on popupShowing, so if it is open
|
||||
// we must update it.
|
||||
if (this._chevron.open)
|
||||
if (!this._chevron.collapsed && this._chevron.open)
|
||||
this._updateChevronPopupNodesVisibility();
|
||||
let event = new CustomEvent("BookmarksToolbarVisibilityUpdated", {bubbles: true});
|
||||
this._viewElt.dispatchEvent(event);
|
||||
|
@ -1314,7 +1304,7 @@ PlacesToolbar.prototype = {
|
|||
let icon = aPlacesNode.icon;
|
||||
if (icon)
|
||||
button.setAttribute("image", icon);
|
||||
this.updateChevron();
|
||||
this.updateNodesVisibility();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1343,7 +1333,7 @@ PlacesToolbar.prototype = {
|
|||
this._rootElt);
|
||||
}
|
||||
if (!overflowed)
|
||||
this.updateChevron();
|
||||
this.updateNodesVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1391,12 +1381,12 @@ PlacesToolbar.prototype = {
|
|||
|
||||
// The chevron view may get nodeMoved after the toolbar. In such a case,
|
||||
// we should ensure (by manually swapping menuitems) that the actual nodes
|
||||
// are in the final position before updateChevron tries to updates their
|
||||
// visibility, or the chevron may go out of sync.
|
||||
// Luckily updateChevron runs on a timer, so, by the time it updates
|
||||
// are in the final position before updateNodesVisibility tries to update
|
||||
// their visibility, or the chevron may go out of sync.
|
||||
// Luckily updateNodesVisibility runs on a timer, so, by the time it updates
|
||||
// nodes, the menu has already handled the notification.
|
||||
|
||||
this.updateChevron();
|
||||
this.updateNodesVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1446,7 +1436,7 @@ PlacesToolbar.prototype = {
|
|||
|
||||
if (elt.parentNode == this._rootElt) { // Node is on the toolbar.
|
||||
if (elt.style.visibility != "hidden")
|
||||
this.updateChevron();
|
||||
this.updateNodesVisibility();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1605,13 +1595,13 @@ PlacesToolbar.prototype = {
|
|||
},
|
||||
|
||||
notify: function PT_notify(aTimer) {
|
||||
if (aTimer == this._updateChevronTimer) {
|
||||
this._updateChevronTimer = null;
|
||||
if (aTimer == this._updateNodesVisibilityTimer) {
|
||||
this._updateNodesVisibilityTimer = null;
|
||||
// TODO: This should use promiseLayoutFlushed("layout"), so that
|
||||
// _updateChevronTimerCallback can use getBoundsWithoutFlush. But for
|
||||
// yet unknown reasons doing that causes intermittent failures, apparently
|
||||
// due the flush not happening in a meaningful time.
|
||||
window.requestAnimationFrame(this._updateChevronTimerCallback.bind(this));
|
||||
// _updateNodesVisibilityTimerCallback can use getBoundsWithoutFlush. But
|
||||
// for yet unknown reasons doing that causes intermittent failures,
|
||||
// apparently due the flush not happening in a meaningful time.
|
||||
window.requestAnimationFrame(this._updateNodesVisibilityTimerCallback.bind(this));
|
||||
} else if (aTimer == this._ibTimer) {
|
||||
// * Timer to turn off indicator bar.
|
||||
this._dropIndicator.collapsed = true;
|
||||
|
|
|
@ -35,6 +35,7 @@ add_task(async function setup() {
|
|||
await promiseSetToolbarVisibility(gToolbar, false);
|
||||
}
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
await PlacesUtils.history.clear();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -67,6 +68,36 @@ add_task(async function() {
|
|||
"visible", undefined);
|
||||
});
|
||||
|
||||
add_task(async function test_newWindow_noOverflow() {
|
||||
info("Check toolbar in a new widow when it was already visible and not overflowed");
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
// Add a single bookmark.
|
||||
await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
url: "http://toolbar.overflow/",
|
||||
title: "Example"
|
||||
});
|
||||
// Add a favicon for the bookmark.
|
||||
let favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
|
||||
"AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
|
||||
await PlacesTestUtils.addFavicons(new Map([["http://toolbar.overflow/", favicon]]));
|
||||
|
||||
let win = await BrowserTestUtils.openNewBrowserWindow();
|
||||
try {
|
||||
let toolbar = win.document.getElementById("PersonalToolbar");
|
||||
Assert.ok(!toolbar.collapsed, "The toolbar is not collapsed");
|
||||
let content = win.document.getElementById("PlacesToolbarItems");
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
return content.childNodes.length == 1 &&
|
||||
content.childNodes[0].hasAttribute("image");
|
||||
});
|
||||
let chevron = win.document.getElementById("PlacesChevron");
|
||||
Assert.ok(chevron.collapsed, "The chevron should be collapsed");
|
||||
} finally {
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
}
|
||||
});
|
||||
|
||||
async function test_index(desc, index, expected) {
|
||||
info(desc);
|
||||
let children = gToolbarContent.childNodes;
|
||||
|
|
|
@ -208,6 +208,15 @@ function debug(aMsg) {
|
|||
*/
|
||||
var gResistFingerprintingEnabled = false;
|
||||
|
||||
/**
|
||||
* Return a promise that will be resolved once it receives event
|
||||
* |SSBrowserWindowShowing| which is dispatched in onBrowserWindowShown.
|
||||
*/
|
||||
function promiseWindowShowing(window) {
|
||||
return new Promise(resolve => window.addEventListener("SSBrowserWindowShowing",
|
||||
() => resolve(window), {once: true}));
|
||||
}
|
||||
|
||||
this.SessionStore = {
|
||||
get promiseInitialized() {
|
||||
return SessionStoreInternal.promiseInitialized;
|
||||
|
@ -692,8 +701,9 @@ var SessionStoreInternal = {
|
|||
delete state.windows[0].hidden;
|
||||
// Since nothing is hidden in the first window, it cannot be a popup
|
||||
delete state.windows[0].isPopup;
|
||||
// We don't want to minimize and then open a window at startup.
|
||||
if (state.windows[0].sizemode == "minimized")
|
||||
// We don't want to minimize and then open a window at startup. However,
|
||||
// when we're restoring, we should preserve previous windows sizemode.
|
||||
if (state.windows[0].sizemode == "minimized" && !ss.doRestore())
|
||||
state.windows[0].sizemode = "normal";
|
||||
// clear any lastSessionWindowID attributes since those don't matter
|
||||
// during normal restore
|
||||
|
@ -1179,9 +1189,11 @@ var SessionStoreInternal = {
|
|||
}
|
||||
// this window was opened by _openWindowWithState
|
||||
} else if (!this._isWindowLoaded(aWindow)) {
|
||||
let state = this._statesToRestore[aWindow.__SS_restoreID];
|
||||
let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1};
|
||||
this.restoreWindow(aWindow, state.windows[0], options);
|
||||
// We used to restore window when it is opened. However, we
|
||||
// want to restore windows after all windows are opened
|
||||
// (bug 1034036). So the restoration process has been moved to
|
||||
// function restoreWindowsFeaturesAndTabs.
|
||||
|
||||
// The user opened another, non-private window after starting up with
|
||||
// a single private one. Let's restore the session we actually wanted to
|
||||
// restore at startup.
|
||||
|
@ -1273,6 +1285,10 @@ var SessionStoreInternal = {
|
|||
// Register the window.
|
||||
this.onLoad(aWindow);
|
||||
|
||||
// Dispatch a custom event to tell that a new window is about to be shown.
|
||||
let evt = new aWindow.CustomEvent("SSBrowserWindowShowing");
|
||||
aWindow.dispatchEvent(evt);
|
||||
|
||||
// Just call initializeWindow() directly if we're initialized already.
|
||||
if (this._sessionInitialized) {
|
||||
this.initializeWindow(aWindow);
|
||||
|
@ -1347,7 +1363,7 @@ var SessionStoreInternal = {
|
|||
aWindow.__SSi = this._generateWindowID();
|
||||
}
|
||||
|
||||
this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID];
|
||||
this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID].windows[0];
|
||||
delete this._statesToRestore[aWindow.__SS_restoreID];
|
||||
delete aWindow.__SS_restoreID;
|
||||
}
|
||||
|
@ -2513,6 +2529,7 @@ var SessionStoreInternal = {
|
|||
|
||||
let window = this._openWindowWithState(state);
|
||||
this.windowToFocus = window;
|
||||
promiseWindowShowing(window).then(win => this.restoreWindows(win, state, {overwriteTabs: true}));
|
||||
|
||||
// Notify of changes to closed objects.
|
||||
this._notifyOfClosedObjectsChange();
|
||||
|
@ -2766,6 +2783,9 @@ var SessionStoreInternal = {
|
|||
// Restore session cookies.
|
||||
SessionCookies.restore(lastSessionState.cookies || []);
|
||||
|
||||
let openedWindows = [];
|
||||
let remainingWindows = [];
|
||||
|
||||
// Restore into windows or open new ones as needed.
|
||||
for (let i = 0; i < lastSessionState.windows.length; i++) {
|
||||
let winState = lastSessionState.windows[i];
|
||||
|
@ -2794,17 +2814,28 @@ var SessionStoreInternal = {
|
|||
curWinState._closedTabs.splice(this._max_tabs_undo, curWinState._closedTabs.length);
|
||||
}
|
||||
|
||||
// Restore into that window - pretend it's a followup since we'll already
|
||||
// have a focused window.
|
||||
// XXXzpao This is going to merge extData together (taking what was in
|
||||
// winState over what is in the window already.
|
||||
let options = {overwriteTabs: canOverwriteTabs, isFollowUp: true};
|
||||
this.restoreWindow(windowToUse, winState, options);
|
||||
// We don't restore window right away, just store its data.
|
||||
// Later, these windows will be restored with newly opened windows.
|
||||
if ("zIndex" in winState) {
|
||||
windowToUse.__SS_zIndex = winState.zIndex;
|
||||
}
|
||||
|
||||
this._updateWindowRestoreState(windowToUse, {windows: [winState]});
|
||||
windowToUse.__SS_restoreOptions = {overwriteTabs: canOverwriteTabs};
|
||||
openedWindows.push(windowToUse);
|
||||
} else {
|
||||
this._openWindowWithState({ windows: [winState] });
|
||||
remainingWindows.push(winState);
|
||||
}
|
||||
}
|
||||
|
||||
// Actually restore windows in reversed z-order.
|
||||
this.openWindows({windows: remainingWindows}).then(wins => {
|
||||
wins = openedWindows.concat(wins);
|
||||
this.restoreWindowsInReversedZOrder(wins, this._statesToRestore, false);
|
||||
});
|
||||
|
||||
// Merge closed windows from this session with ones from last session
|
||||
if (lastSessionState._closedWindows) {
|
||||
this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
|
||||
|
@ -3055,6 +3086,19 @@ var SessionStoreInternal = {
|
|||
|
||||
/* ........ Saving Functionality .............. */
|
||||
|
||||
/**
|
||||
* Return z-index of a window.
|
||||
* If a window is minimized, its z-index is -1.
|
||||
*
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
* @return z-index of that window
|
||||
*/
|
||||
_getZIndex(window) {
|
||||
let isMinimized = this._getWindowDimension(window, "sizemode") == "minimized";
|
||||
return isMinimized ? -1 : window.__SS_zIndex;
|
||||
},
|
||||
|
||||
/**
|
||||
* Store window dimensions, visibility, sidebar
|
||||
* @param aWindow
|
||||
|
@ -3067,6 +3111,15 @@ var SessionStoreInternal = {
|
|||
winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
|
||||
}, this);
|
||||
|
||||
// We only update zIndex when a valid zIndex is found in a window.
|
||||
// There will be a case (flushAllWindowsAsync) where this function
|
||||
// is called outside |forEachBrowserWindow|, therefore, no zIndex
|
||||
// is found.
|
||||
let zIndex = this._getZIndex(aWindow);
|
||||
if (zIndex) {
|
||||
winData.zIndex = this._getZIndex(aWindow);
|
||||
}
|
||||
|
||||
var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
|
||||
return aWindow[aItem] && !aWindow[aItem].visible;
|
||||
});
|
||||
|
@ -3260,6 +3313,24 @@ var SessionStoreInternal = {
|
|||
|
||||
/* ........ Restoring Functionality .............. */
|
||||
|
||||
/**
|
||||
* Open windows with data
|
||||
*
|
||||
* @param root
|
||||
* Windows data
|
||||
* @returns a promise resolved when all windows have been opened
|
||||
*/
|
||||
openWindows(root) {
|
||||
let openedWindowPromises = [];
|
||||
for (let winData of root.windows) {
|
||||
if (winData && winData.tabs && winData.tabs[0]) {
|
||||
let window = this._openWindowWithState({ windows: [winData] });
|
||||
openedWindowPromises.push(promiseWindowShowing(window));
|
||||
}
|
||||
}
|
||||
return Promise.all(openedWindowPromises);
|
||||
},
|
||||
|
||||
/**
|
||||
* restore features to a single window
|
||||
* @param aWindow
|
||||
|
@ -3268,20 +3339,14 @@ var SessionStoreInternal = {
|
|||
* JS object
|
||||
* @param aOptions
|
||||
* {overwriteTabs: true} to overwrite existing tabs w/ new ones
|
||||
* {isFollowUp: true} if this is not the restoration of the 1st window
|
||||
* {firstWindow: true} if this is the first non-private window we're
|
||||
* restoring in this session, that might open an
|
||||
* external link as well
|
||||
*/
|
||||
restoreWindow: function ssi_restoreWindow(aWindow, winData, aOptions = {}) {
|
||||
let overwriteTabs = aOptions && aOptions.overwriteTabs;
|
||||
let isFollowUp = aOptions && aOptions.isFollowUp;
|
||||
let firstWindow = aOptions && aOptions.firstWindow;
|
||||
|
||||
if (isFollowUp) {
|
||||
this.windowToFocus = aWindow;
|
||||
}
|
||||
|
||||
// initialize window if necessary
|
||||
if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
|
||||
this.onLoad(aWindow);
|
||||
|
@ -3418,7 +3483,6 @@ var SessionStoreInternal = {
|
|||
aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
|
||||
|
||||
if (overwriteTabs) {
|
||||
this.restoreWindowFeatures(aWindow, winData);
|
||||
delete this._windows[aWindow.__SSi].extData;
|
||||
}
|
||||
|
||||
|
@ -3516,6 +3580,55 @@ var SessionStoreInternal = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function will restore window features and then retore window data.
|
||||
*
|
||||
* @param windows
|
||||
* array of windows to be restored into
|
||||
* @param statesToRestore
|
||||
* states of windows to be restored
|
||||
*/
|
||||
restoreWindowsFeaturesAndTabs(windows, statesToRestore) {
|
||||
// First, we restore window features, so that when users
|
||||
// interacting with a window, we don't steal the window focus.
|
||||
windows.forEach((window) => {
|
||||
let state = statesToRestore[window.__SS_restoreID];
|
||||
this.restoreWindowFeatures(window, state.windows[0]);
|
||||
});
|
||||
|
||||
// Then we restore data into windows.
|
||||
for (let i = 0; i < windows.length; ++i) {
|
||||
let state = statesToRestore[windows[i].__SS_restoreID];
|
||||
let option = windows[i].__SS_restoreOptions || {overwriteTabs: true};
|
||||
this.restoreWindow(windows[i], state.windows[0], option);
|
||||
delete windows[i].__SS_restoreOptions;
|
||||
delete windows[i].__SS_zIndex;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function will restore window in reversed z-index, so that users
|
||||
* will be presented with most recently used window first.
|
||||
*
|
||||
* @param windows
|
||||
* array of windows to be restored into
|
||||
* @param statesToRestore
|
||||
* states of windows to be restored
|
||||
* @param areFollowUps
|
||||
* a flag indicate these windows are follow-up windows
|
||||
*/
|
||||
restoreWindowsInReversedZOrder(windows, statesToRestore, areFollowUps) {
|
||||
if (windows.some(window => !!window.__SS_zIndex)) {
|
||||
windows.sort((a, b) => b.__SS_zIndex - a.__SS_zIndex);
|
||||
}
|
||||
|
||||
if (!areFollowUps) {
|
||||
this.windowToFocus = windows[0];
|
||||
}
|
||||
|
||||
this.restoreWindowsFeaturesAndTabs(windows, statesToRestore);
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore multiple windows using the provided state.
|
||||
* @param aWindow
|
||||
|
@ -3525,18 +3638,11 @@ var SessionStoreInternal = {
|
|||
* JS object or JSON string
|
||||
* @param aOptions
|
||||
* {overwriteTabs: true} to overwrite existing tabs w/ new ones
|
||||
* {isFollowUp: true} if this is not the restoration of the 1st window
|
||||
* {firstWindow: true} if this is the first non-private window we're
|
||||
* restoring in this session, that might open an
|
||||
* external link as well
|
||||
*/
|
||||
restoreWindows: function ssi_restoreWindows(aWindow, aState, aOptions = {}) {
|
||||
let isFollowUp = aOptions && aOptions.isFollowUp;
|
||||
|
||||
if (isFollowUp) {
|
||||
this.windowToFocus = aWindow;
|
||||
}
|
||||
|
||||
// initialize window if necessary
|
||||
if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
|
||||
this.onLoad(aWindow);
|
||||
|
@ -3562,24 +3668,28 @@ var SessionStoreInternal = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
|
||||
root.selectedWindow = 0;
|
||||
// Store z-index to current window so that it can be restored in reversed z-order.
|
||||
let firstWindowData = root.windows.splice(0, 1);
|
||||
if ("zIndex" in firstWindowData[0]) {
|
||||
aWindow.__SS_zIndex = firstWindowData[0].zIndex;
|
||||
}
|
||||
|
||||
// open new windows for all further window entries of a multi-window session
|
||||
// (unless they don't contain any tab data)
|
||||
let winData;
|
||||
for (var w = 1; w < root.windows.length; w++) {
|
||||
winData = root.windows[w];
|
||||
if (winData && winData.tabs && winData.tabs[0]) {
|
||||
var window = this._openWindowWithState({ windows: [winData] });
|
||||
if (w == root.selectedWindow - 1) {
|
||||
this.windowToFocus = window;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Store the restore state and restore option of the current window,
|
||||
// so that the window can be restored in reversed z-order.
|
||||
this._updateWindowRestoreState(aWindow, {windows: firstWindowData});
|
||||
aWindow.__SS_restoreOptions = aOptions;
|
||||
|
||||
this.restoreWindow(aWindow, root.windows[0], aOptions);
|
||||
// Begin the restoration: First open all windows in creation order.
|
||||
// After all windows are opened, we restore states to windows in
|
||||
// reversed z-order.
|
||||
this.openWindows(root).then(windows => {
|
||||
// We want to add current window to opened window, so that this window will be
|
||||
// restored in reversed z-order. (We add the window to first position, in case
|
||||
// no z-indices are found, that window will be restored first.)
|
||||
windows.unshift(aWindow);
|
||||
|
||||
this.restoreWindowsInReversedZOrder(windows, this._statesToRestore, false);
|
||||
});
|
||||
|
||||
DevToolsShim.restoreDevToolsSession(aState);
|
||||
},
|
||||
|
@ -4167,18 +4277,42 @@ var SessionStoreInternal = {
|
|||
},
|
||||
|
||||
/**
|
||||
* call a callback for all currently opened browser windows
|
||||
* A boolean flag indicates whether we can iterate over all windows
|
||||
* in their z order.
|
||||
*/
|
||||
get isWMZOrderBroken() {
|
||||
let broken_wm_z_order = AppConstants.platform != "macosx" && AppConstants.platform != "win";
|
||||
delete this.isWMZOrderBroken;
|
||||
return this.isWMZOrderBroken = broken_wm_z_order;
|
||||
},
|
||||
|
||||
/**
|
||||
* Call a callback for all currently opened browser windows.
|
||||
* This will iterate the windows in z-index from front to back,
|
||||
* and assign z-index to the window.
|
||||
* (might miss the most recent one)
|
||||
* @param aFunc
|
||||
* Callback each window is passed to
|
||||
*/
|
||||
_forEachBrowserWindow: function ssi_forEachBrowserWindow(aFunc) {
|
||||
var windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
let windowsEnum = this.isWMZOrderBroken ?
|
||||
Services.wm.getEnumerator("navigator:browser") :
|
||||
Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", false);
|
||||
let mostRecentWindow = this.isWMZOrderBroken ? this._getMostRecentBrowserWindow() : null;
|
||||
|
||||
// We want to start zIndex at 1, so that, in _updateWindowFeatures, if no z-index is found
|
||||
// in a window, we can just check with a simple condition if: `if (zIndex)`.
|
||||
let zIndex = 1;
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
var window = windowsEnum.getNext();
|
||||
let window = windowsEnum.getNext();
|
||||
if (window.__SSi && !window.closed) {
|
||||
if (this.isWMZOrderBroken) {
|
||||
window.__SS_zIndex = mostRecentWindow.__SSi === window.__SSi ? 2 : 1;
|
||||
} else {
|
||||
window.__SS_zIndex = zIndex++;
|
||||
}
|
||||
aFunc.call(this, window);
|
||||
delete window.__SS_zIndex;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4209,6 +4343,23 @@ var SessionStoreInternal = {
|
|||
return Promise.all(promises);
|
||||
},
|
||||
|
||||
/**
|
||||
* Store a restore state of a window to this._statesToRestore. The window
|
||||
* will be given an id that can be used to get the restore state from
|
||||
* this._statesToRestore.
|
||||
*
|
||||
* @param window
|
||||
* a reference to a window that has a state to restore
|
||||
* @param state
|
||||
* an object containing session data
|
||||
*/
|
||||
_updateWindowRestoreState(window, state) {
|
||||
do {
|
||||
var ID = "window" + Math.random();
|
||||
} while (ID in this._statesToRestore);
|
||||
this._statesToRestore[(window.__SS_restoreID = ID)] = state;
|
||||
},
|
||||
|
||||
/**
|
||||
* open a new browser window for a given session state
|
||||
* called when restoring a multi-window session
|
||||
|
@ -4237,10 +4388,12 @@ var SessionStoreInternal = {
|
|||
Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
|
||||
"_blank", features, argString);
|
||||
|
||||
do {
|
||||
var ID = "window" + Math.random();
|
||||
} while (ID in this._statesToRestore);
|
||||
this._statesToRestore[(window.__SS_restoreID = ID)] = aState;
|
||||
// Store z-index, so that windows can be restored in reversed z-order.
|
||||
if ("zIndex" in aState.windows[0]) {
|
||||
window.__SS_zIndex = aState.windows[0].zIndex;
|
||||
}
|
||||
|
||||
this._updateWindowRestoreState(window, aState);
|
||||
|
||||
return window;
|
||||
},
|
||||
|
|
|
@ -265,4 +265,4 @@ skip-if = !crashreporter || !e10s # Tabs can't crash without e10s
|
|||
[browser_cookies_legacy.js]
|
||||
[browser_cookies_privacy.js]
|
||||
[browser_speculative_connect.js]
|
||||
|
||||
[browser_restore_reversed_z_order.js]
|
||||
|
|
|
@ -37,6 +37,7 @@ add_task(async function() {
|
|||
|
||||
// restore the window state
|
||||
ss.setBrowserState(state);
|
||||
await promiseWindowRestored(window);
|
||||
|
||||
// at this point, the cookie should be restored...
|
||||
enumerator = Services.cookies.enumerator;
|
||||
|
|
|
@ -38,47 +38,49 @@ function test() {
|
|||
test_state.windows[0]._closedTabs.length);
|
||||
ss.setWindowState(newWin, JSON.stringify(test_state), true);
|
||||
|
||||
let closedTabs = SessionStore.getClosedTabData(newWin, false);
|
||||
promiseWindowRestored(newWin).then(() => {
|
||||
let closedTabs = SessionStore.getClosedTabData(newWin, false);
|
||||
|
||||
// Verify that non JSON serialized data is the same as JSON serialized data.
|
||||
is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
|
||||
"Non-serialized data is the same as serialized data")
|
||||
// Verify that non JSON serialized data is the same as JSON serialized data.
|
||||
is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
|
||||
"Non-serialized data is the same as serialized data")
|
||||
|
||||
is(closedTabs.length, test_state.windows[0]._closedTabs.length,
|
||||
"Closed tab list has the expected length");
|
||||
is(countByTitle(closedTabs, FORGET),
|
||||
test_state.windows[0]._closedTabs.length - remember_count,
|
||||
"The correct amout of tabs are to be forgotten");
|
||||
is(countByTitle(closedTabs, REMEMBER), remember_count,
|
||||
"Everything is set up");
|
||||
is(closedTabs.length, test_state.windows[0]._closedTabs.length,
|
||||
"Closed tab list has the expected length");
|
||||
is(countByTitle(closedTabs, FORGET),
|
||||
test_state.windows[0]._closedTabs.length - remember_count,
|
||||
"The correct amout of tabs are to be forgotten");
|
||||
is(countByTitle(closedTabs, REMEMBER), remember_count,
|
||||
"Everything is set up");
|
||||
|
||||
// All of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE.
|
||||
ok(testForError(() => ss.forgetClosedTab({}, 0)),
|
||||
"Invalid window for forgetClosedTab throws");
|
||||
ok(testForError(() => ss.forgetClosedTab(newWin, -1)),
|
||||
"Invalid tab for forgetClosedTab throws");
|
||||
ok(testForError(() => ss.forgetClosedTab(newWin, test_state.windows[0]._closedTabs.length + 1)),
|
||||
"Invalid tab for forgetClosedTab throws");
|
||||
// All of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE.
|
||||
ok(testForError(() => ss.forgetClosedTab({}, 0)),
|
||||
"Invalid window for forgetClosedTab throws");
|
||||
ok(testForError(() => ss.forgetClosedTab(newWin, -1)),
|
||||
"Invalid tab for forgetClosedTab throws");
|
||||
ok(testForError(() => ss.forgetClosedTab(newWin, test_state.windows[0]._closedTabs.length + 1)),
|
||||
"Invalid tab for forgetClosedTab throws");
|
||||
|
||||
// Remove third tab, then first tab.
|
||||
ss.forgetClosedTab(newWin, 2);
|
||||
ss.forgetClosedTab(newWin, null);
|
||||
// Remove third tab, then first tab.
|
||||
ss.forgetClosedTab(newWin, 2);
|
||||
ss.forgetClosedTab(newWin, null);
|
||||
|
||||
closedTabs = SessionStore.getClosedTabData(newWin, false);
|
||||
closedTabs = SessionStore.getClosedTabData(newWin, false);
|
||||
|
||||
// Verify that non JSON serialized data is the same as JSON serialized data.
|
||||
is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
|
||||
"Non-serialized data is the same as serialized data")
|
||||
// Verify that non JSON serialized data is the same as JSON serialized data.
|
||||
is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
|
||||
"Non-serialized data is the same as serialized data")
|
||||
|
||||
is(closedTabs.length, remember_count,
|
||||
"The correct amout of tabs was removed");
|
||||
is(countByTitle(closedTabs, FORGET), 0,
|
||||
"All tabs specifically forgotten were indeed removed");
|
||||
is(countByTitle(closedTabs, REMEMBER), remember_count,
|
||||
"... and tabs not specifically forgetten weren't");
|
||||
is(closedTabs.length, remember_count,
|
||||
"The correct amout of tabs was removed");
|
||||
is(countByTitle(closedTabs, FORGET), 0,
|
||||
"All tabs specifically forgotten were indeed removed");
|
||||
is(countByTitle(closedTabs, REMEMBER), remember_count,
|
||||
"... and tabs not specifically forgetten weren't");
|
||||
|
||||
// Clean up.
|
||||
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
|
||||
BrowserTestUtils.closeWindow(newWin).then(finish);
|
||||
// Clean up.
|
||||
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
|
||||
BrowserTestUtils.closeWindow(newWin).then(finish);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ add_task(async function() {
|
|||
gPrefService.setIntPref("browser.sessionstore.max_tabs_undo",
|
||||
test_state.windows[0]._closedTabs.length);
|
||||
ss.setWindowState(newWin, JSON.stringify(test_state), true);
|
||||
await promiseWindowRestored(newWin);
|
||||
|
||||
let closedTabs = JSON.parse(ss.getClosedTabData(newWin));
|
||||
is(closedTabs.length, test_state.windows[0]._closedTabs.length,
|
||||
|
|
|
@ -22,24 +22,30 @@ function test() {
|
|||
newState.windows[0].extData[uniqueKey2] = uniqueValue2;
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), false);
|
||||
|
||||
is(newWin.gBrowser.tabs.length, 2,
|
||||
"original tab wasn't overwritten");
|
||||
is(ss.getWindowValue(newWin, uniqueKey1), uniqueValue1,
|
||||
"window value wasn't overwritten when the tabs weren't");
|
||||
is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue2,
|
||||
"new window value was correctly added");
|
||||
promiseWindowRestored(newWin).then(() => {
|
||||
is(newWin.gBrowser.tabs.length, 2,
|
||||
"original tab wasn't overwritten");
|
||||
is(ss.getWindowValue(newWin, uniqueKey1), uniqueValue1,
|
||||
"window value wasn't overwritten when the tabs weren't");
|
||||
is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue2,
|
||||
"new window value was correctly added");
|
||||
|
||||
newState.windows[0].extData[uniqueKey2] = uniqueValue1;
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
newState.windows[0].extData[uniqueKey2] = uniqueValue1;
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
|
||||
is(newWin.gBrowser.tabs.length, 1,
|
||||
"original tabs were overwritten");
|
||||
is(ss.getWindowValue(newWin, uniqueKey1), "",
|
||||
"window value was cleared");
|
||||
is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue1,
|
||||
"window value was correctly overwritten");
|
||||
promiseWindowRestored(newWin).then(() => {
|
||||
is(newWin.gBrowser.tabs.length, 1,
|
||||
"original tabs were overwritten");
|
||||
is(ss.getWindowValue(newWin, uniqueKey1), "",
|
||||
"window value was cleared");
|
||||
is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue1,
|
||||
"window value was correctly overwritten");
|
||||
|
||||
// clean up
|
||||
BrowserTestUtils.closeWindow(newWin).then(finish);
|
||||
// clean up
|
||||
BrowserTestUtils.closeWindow(newWin).then(finish);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -25,36 +25,43 @@ function test() {
|
|||
"window value was set before the window was overwritten");
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
|
||||
// use newWin.setTimeout(..., 0) to mirror sss_restoreWindowFeatures
|
||||
newWin.setTimeout(function() {
|
||||
is(ss.getWindowValue(newWin, uniqueKey), "",
|
||||
"window value was implicitly cleared");
|
||||
|
||||
is(newWin.windowState, newWin.STATE_MAXIMIZED,
|
||||
"the window was maximized");
|
||||
|
||||
is(JSON.parse(ss.getClosedTabData(newWin)).length, 1,
|
||||
"the closed tab was added before the window was overwritten");
|
||||
delete newState.windows[0]._closedTabs;
|
||||
delete newState.windows[0].sizemode;
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
|
||||
promiseWindowRestored(newWin).then(() => {
|
||||
// use newWin.setTimeout(..., 0) to mirror sss_restoreWindowFeatures
|
||||
newWin.setTimeout(function() {
|
||||
is(JSON.parse(ss.getClosedTabData(newWin)).length, 0,
|
||||
"closed tabs were implicitly cleared");
|
||||
is(ss.getWindowValue(newWin, uniqueKey), "",
|
||||
"window value was implicitly cleared");
|
||||
|
||||
is(newWin.windowState, newWin.STATE_MAXIMIZED,
|
||||
"the window remains maximized");
|
||||
newState.windows[0].sizemode = "normal";
|
||||
"the window was maximized");
|
||||
|
||||
is(JSON.parse(ss.getClosedTabData(newWin)).length, 1,
|
||||
"the closed tab was added before the window was overwritten");
|
||||
delete newState.windows[0]._closedTabs;
|
||||
delete newState.windows[0].sizemode;
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
|
||||
newWin.setTimeout(function() {
|
||||
isnot(newWin.windowState, newWin.STATE_MAXIMIZED,
|
||||
"the window was explicitly unmaximized");
|
||||
promiseWindowRestored(newWin).then(() => {
|
||||
newWin.setTimeout(function() {
|
||||
is(JSON.parse(ss.getClosedTabData(newWin)).length, 0,
|
||||
"closed tabs were implicitly cleared");
|
||||
|
||||
BrowserTestUtils.closeWindow(newWin).then(finish);
|
||||
}, 0);
|
||||
is(newWin.windowState, newWin.STATE_MAXIMIZED,
|
||||
"the window remains maximized");
|
||||
newState.windows[0].sizemode = "normal";
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
|
||||
promiseWindowRestored(newWin).then(() => {
|
||||
newWin.setTimeout(function() {
|
||||
isnot(newWin.windowState, newWin.STATE_MAXIMIZED,
|
||||
"the window was explicitly unmaximized");
|
||||
|
||||
BrowserTestUtils.closeWindow(newWin).then(finish);
|
||||
}, 0);
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ add_task(async function test_bug_490040() {
|
|||
let win = await BrowserTestUtils.openNewBrowserWindow();
|
||||
|
||||
ss.setWindowState(win, JSON.stringify(state.windowState), true);
|
||||
await promiseWindowRestored(win);
|
||||
if (state.windowState.windows[0].tabs.length) {
|
||||
await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
|
||||
}
|
||||
|
|
|
@ -85,35 +85,38 @@ function test() {
|
|||
test_state._closedWindows.length);
|
||||
ss.setWindowState(newWin, JSON.stringify(test_state), true);
|
||||
|
||||
let closedWindows = JSON.parse(ss.getClosedWindowData());
|
||||
is(closedWindows.length, test_state._closedWindows.length,
|
||||
"Closed window list has the expected length");
|
||||
is(countByTitle(closedWindows, FORGET),
|
||||
test_state._closedWindows.length - remember_count,
|
||||
"The correct amount of windows are to be forgotten");
|
||||
is(countByTitle(closedWindows, REMEMBER), remember_count,
|
||||
"Everything is set up.");
|
||||
promiseWindowRestored(newWin).then(() => {
|
||||
let closedWindows = JSON.parse(ss.getClosedWindowData());
|
||||
is(closedWindows.length, test_state._closedWindows.length,
|
||||
"Closed window list has the expected length");
|
||||
is(countByTitle(closedWindows, FORGET),
|
||||
test_state._closedWindows.length - remember_count,
|
||||
"The correct amount of windows are to be forgotten");
|
||||
is(countByTitle(closedWindows, REMEMBER), remember_count,
|
||||
"Everything is set up.");
|
||||
|
||||
// all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
|
||||
ok(testForError(() => ss.forgetClosedWindow(-1)),
|
||||
"Invalid window for forgetClosedWindow throws");
|
||||
ok(testForError(() => ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
|
||||
"Invalid window for forgetClosedWindow throws");
|
||||
// all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
|
||||
ok(testForError(() => ss.forgetClosedWindow(-1)),
|
||||
"Invalid window for forgetClosedWindow throws");
|
||||
ok(testForError(() => ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
|
||||
"Invalid window for forgetClosedWindow throws");
|
||||
|
||||
// Remove third window, then first window
|
||||
ss.forgetClosedWindow(2);
|
||||
ss.forgetClosedWindow(null);
|
||||
// Remove third window, then first window
|
||||
ss.forgetClosedWindow(2);
|
||||
ss.forgetClosedWindow(null);
|
||||
|
||||
closedWindows = JSON.parse(ss.getClosedWindowData());
|
||||
is(closedWindows.length, remember_count,
|
||||
"The correct amount of windows were removed");
|
||||
is(countByTitle(closedWindows, FORGET), 0,
|
||||
"All windows specifically forgotten were indeed removed");
|
||||
is(countByTitle(closedWindows, REMEMBER), remember_count,
|
||||
"... and windows not specifically forgetten weren't.");
|
||||
closedWindows = JSON.parse(ss.getClosedWindowData());
|
||||
is(closedWindows.length, remember_count,
|
||||
"The correct amount of windows were removed");
|
||||
is(countByTitle(closedWindows, FORGET), 0,
|
||||
"All windows specifically forgotten were indeed removed");
|
||||
is(countByTitle(closedWindows, REMEMBER), remember_count,
|
||||
"... and windows not specifically forgetten weren't.");
|
||||
|
||||
// clean up
|
||||
gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
|
||||
BrowserTestUtils.closeWindow(newWin).then(finish);
|
||||
// clean up
|
||||
gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
|
||||
BrowserTestUtils.closeWindow(newWin).then(finish);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -22,16 +22,18 @@ function test() {
|
|||
promiseWindowLoaded(win).then(() => {
|
||||
|
||||
is(win.gURLBar.readOnly, false,
|
||||
"URL bar should not be read-only before setting the state");
|
||||
"URL bar should not be read-only before setting the state");
|
||||
is(win.gURLBar.getAttribute("enablehistory"), "true",
|
||||
"URL bar autocomplete should be enabled before setting the state");
|
||||
"URL bar autocomplete should be enabled before setting the state");
|
||||
ss.setWindowState(win, state, true);
|
||||
is(win.gURLBar.readOnly, expected.readOnly,
|
||||
"URL bar read-only state should be restored correctly");
|
||||
is(win.gURLBar.getAttribute("enablehistory"), expected.enablehistory,
|
||||
"URL bar autocomplete state should be restored correctly");
|
||||
promiseWindowRestored(win).then(() => {
|
||||
is(win.gURLBar.readOnly, expected.readOnly,
|
||||
"URL bar read-only state should be restored correctly");
|
||||
is(win.gURLBar.getAttribute("enablehistory"), expected.enablehistory,
|
||||
"URL bar autocomplete state should be restored correctly");
|
||||
|
||||
BrowserTestUtils.closeWindow(win).then(callback);
|
||||
BrowserTestUtils.closeWindow(win).then(callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -28,8 +28,10 @@ function test() {
|
|||
if (/NS_ERROR_MALFORMED_URI/.test(e))
|
||||
gotError = true;
|
||||
}
|
||||
ok(!gotError, "Didn't get a malformed URI error.");
|
||||
BrowserTestUtils.closeWindow(theWin).then(finish);
|
||||
promiseWindowRestored(theWin).then(() => {
|
||||
ok(!gotError, "Didn't get a malformed URI error.");
|
||||
BrowserTestUtils.closeWindow(theWin).then(finish);
|
||||
});
|
||||
});
|
||||
}, {once: true});
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ add_task(async function() {
|
|||
|
||||
// Set the test state.
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
await promiseWindowRestored(window);
|
||||
|
||||
// Wait until the selected tab is restored and all others are pending.
|
||||
await Promise.all(Array.map(gBrowser.tabs, tab => {
|
||||
|
|
|
@ -42,6 +42,7 @@ add_task(async function test() {
|
|||
let backupState = SessionStore.getBrowserState();
|
||||
SessionStore.setBrowserState(JSON.stringify(TEST_STATE));
|
||||
let win = await promiseWindow;
|
||||
await promiseWindowRestored(win);
|
||||
|
||||
// The window has now been opened. Check the state that is returned,
|
||||
// this should come from the cache while the window isn't restored, yet.
|
||||
|
|
|
@ -37,12 +37,13 @@ var state = {windows: [{tabs: [{entries: [
|
|||
}
|
||||
]}]}]}
|
||||
|
||||
function test() {
|
||||
add_task(async function test() {
|
||||
registerCleanupFunction(function() {
|
||||
ss.setBrowserState(stateBackup);
|
||||
});
|
||||
|
||||
/* This test fails by hanging. */
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
await promiseWindowRestored(window);
|
||||
ok(true, "Didn't hang!");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -24,9 +24,12 @@ function test() {
|
|||
info(ex);
|
||||
}
|
||||
|
||||
ok(!gotError, "ss.setWindowState did not throw an error");
|
||||
promiseWindowRestored(window).then(() => {
|
||||
ok(!gotError, "ss.setWindowState did not throw an error");
|
||||
|
||||
// Make sure that we reset the state. Use a full state just in case things get crazy.
|
||||
let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }]}]};
|
||||
waitForBrowserState(blankState, finish);
|
||||
// Make sure that we reset the state. Use a full state just in case things get crazy.
|
||||
let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }]}]};
|
||||
waitForBrowserState(blankState, finish);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -101,19 +101,22 @@ add_task(async function() {
|
|||
// Now, test that we don't record history if the iframe is added dynamically
|
||||
add_task(async function() {
|
||||
// Start with an empty history
|
||||
let blankState = JSON.stringify({
|
||||
windows: [{
|
||||
tabs: [{ entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }],
|
||||
_closedTabs: []
|
||||
}],
|
||||
_closedWindows: []
|
||||
});
|
||||
ss.setBrowserState(blankState);
|
||||
let blankState = JSON.stringify({
|
||||
windows: [{
|
||||
tabs: [{ entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }],
|
||||
_closedTabs: []
|
||||
}],
|
||||
_closedWindows: []
|
||||
});
|
||||
ss.setBrowserState(blankState);
|
||||
await promiseWindowRestored(window);
|
||||
|
||||
let testURL = getRootDirectory(gTestPath) + "browser_frame_history_index_blank.html";
|
||||
let tab = BrowserTestUtils.addTab(gBrowser, testURL);
|
||||
gBrowser.selectedTab = tab;
|
||||
dump("Wait here\n");
|
||||
await waitForLoadsInBrowser(tab.linkedBrowser, 1);
|
||||
dump("Finished Wait here\n");
|
||||
|
||||
info("dynamic: Opening a page with an iframe containing three frames, 4 dynamic loads should take place");
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
|
|
|
@ -37,11 +37,13 @@ add_task(async function() {
|
|||
// Open a new window and restore it to an initial state.
|
||||
let win = await promiseNewWindowLoaded({private: false});
|
||||
SessionStore.setWindowState(win, JSON.stringify(initialState), true);
|
||||
await promiseWindowRestored(win);
|
||||
is(SessionStore.getClosedTabCount(win), 2, "2 closed tabs after restoring initial state");
|
||||
|
||||
// Restore the new state but do not overwrite existing tabs (this should
|
||||
// cause the closed tabs to be merged).
|
||||
SessionStore.setWindowState(win, JSON.stringify(restoreState), false);
|
||||
await promiseWindowRestored(win);
|
||||
|
||||
// Verify the windows closed tab data is correct.
|
||||
let iClosed = initialState.windows[0]._closedTabs;
|
||||
|
|
|
@ -135,6 +135,7 @@ async function runScenarios(scenarios) {
|
|||
scenario.selectedTab);
|
||||
|
||||
SessionStore.setWindowState(win, state, true);
|
||||
await promiseWindowRestored(win);
|
||||
|
||||
for (let i = 0; i < scenario.expectedRemoteness.length; ++i) {
|
||||
let expectedRemoteness = scenario.expectedRemoteness[i];
|
||||
|
|
|
@ -135,6 +135,7 @@ add_task(async function run_test() {
|
|||
|
||||
// Restore window with session cookies that have no originAttributes.
|
||||
ss.setWindowState(win, SESSION_DATA, true);
|
||||
await promiseWindowRestored(win);
|
||||
|
||||
let enumerator = Services.cookies.getCookiesFromHost(TEST_HOST, {});
|
||||
let cookie;
|
||||
|
@ -155,6 +156,7 @@ add_task(async function run_test() {
|
|||
|
||||
// Restore window with session cookies that have originAttributes within.
|
||||
ss.setWindowState(win, SESSION_DATA_OA, true);
|
||||
await promiseWindowRestored(win);
|
||||
|
||||
enumerator = Services.cookies.getCookiesFromHost(TEST_HOST, {});
|
||||
cookieCount = 0;
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
const TEST_URLS_MAP = {
|
||||
"about:about": "About About",
|
||||
"about:license": "Licenses",
|
||||
"about:profiles": "About Profiles",
|
||||
"about:mozilla": "The Book of Mozilla, 15:1"
|
||||
};
|
||||
const TEST_URLS = Object.keys(TEST_URLS_MAP);
|
||||
const TEST_LABELS = Object.values(TEST_URLS_MAP);
|
||||
|
||||
const Paths = SessionFile.Paths;
|
||||
const BROKEN_WM_Z_ORDER = AppConstants.platform != "macosx" && AppConstants.platform != "win";
|
||||
|
||||
let source;
|
||||
let state;
|
||||
|
||||
function promiseProvideWindow(url, features) {
|
||||
return new Promise(resolve => provideWindow(resolve, url, features));
|
||||
}
|
||||
|
||||
add_task(async function init() {
|
||||
// Make sure that we start with only primary window.
|
||||
await promiseAllButPrimaryWindowClosed();
|
||||
|
||||
let promises = [];
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
let url = Object.keys(TEST_URLS_MAP)[i];
|
||||
let window = await promiseProvideWindow();
|
||||
BrowserTestUtils.loadURI(window.gBrowser, url);
|
||||
if (i == 2) {
|
||||
window.minimize();
|
||||
}
|
||||
// We want to get the lastest state from each window.
|
||||
await BrowserTestUtils.waitForLocationChange(window.gBrowser, url);
|
||||
await BrowserTestUtils.browserStopped(window.gBrowser.selectedBrowser, url);
|
||||
promises.push(TabStateFlusher.flushWindow(window));
|
||||
}
|
||||
|
||||
// Wait until we get the lastest history from all windows.
|
||||
await Promise.all(promises);
|
||||
|
||||
// Force save state and read the written state.
|
||||
source = await promiseRecoveryFileContents();
|
||||
state = JSON.parse(source);
|
||||
|
||||
// Close all windows as we don't need them.
|
||||
await promiseAllButPrimaryWindowClosed();
|
||||
});
|
||||
|
||||
add_task(async function test_z_indices_are_saved_correctly() {
|
||||
is(state.windows.length, 4, "Correct number of windows saved");
|
||||
|
||||
// Check if we saved state in correct order of creation.
|
||||
for (let i = 0; i < TEST_URLS.length; ++i) {
|
||||
is(state.windows[i].tabs[0].entries[0].url, TEST_URLS[i],
|
||||
`Window #${i} is stored in correct creation order`);
|
||||
}
|
||||
|
||||
// Check if we saved a valid zIndex (no null, no undefined or no 0).
|
||||
for (let win of state.windows) {
|
||||
ok(win.zIndex, "A valid zIndex is stored");
|
||||
}
|
||||
|
||||
if (BROKEN_WM_Z_ORDER) {
|
||||
// Last window should have zIndex of 2.
|
||||
is(state.windows[3].zIndex, 2, "Currently using window has correct z-index");
|
||||
|
||||
// Other windows should have zIndex of 1.
|
||||
is(state.windows[0].zIndex, 1, "Window #1 has correct z-index");
|
||||
is(state.windows[1].zIndex, 1, "Window #2 has correct z-index");
|
||||
|
||||
// Minimized window should have zIndex of -1.
|
||||
is(state.windows[2].zIndex, -1, "Minimized window has correct z-index");
|
||||
} else {
|
||||
is(state.windows[2].zIndex, -1, "Minimzed window has correct z-index");
|
||||
ok(state.windows[0].zIndex != -1, "Window #1 shouldn't has z-index -1");
|
||||
ok(state.windows[1].zIndex > state.windows[0].zIndex, "Window #2 has correct z-index");
|
||||
ok(state.windows[3].zIndex > state.windows[1].zIndex, "Currently using window has correct z-index");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_windows_are_restored_in_reversed_z_index() {
|
||||
let windowsOpened = 1;
|
||||
let windows = [window];
|
||||
let windowsRestored = 0;
|
||||
let tabsRestoredLabels = [];
|
||||
|
||||
// A defer promise that will be resolved once
|
||||
// we restored all tabs.
|
||||
let defer = {};
|
||||
defer.promise = new Promise((resolve, reject) => {
|
||||
defer.resolve = resolve;
|
||||
defer.reject = reject;
|
||||
});
|
||||
|
||||
function allTabsRestored() {
|
||||
is(tabsRestoredLabels[0], TEST_LABELS[3], "First restored tab should be previous used tab");
|
||||
// We don't care about restoration order of windows in between the first and last window
|
||||
// when the OS has broken vm z-order.
|
||||
if (!BROKEN_WM_Z_ORDER) {
|
||||
is(tabsRestoredLabels[1], TEST_LABELS[1], "Second restored tab is correct");
|
||||
is(tabsRestoredLabels[2], TEST_LABELS[0], "Third restored tab is correct");
|
||||
}
|
||||
is(tabsRestoredLabels[3], TEST_LABELS[2], "Last restored tab should be in minimized window");
|
||||
|
||||
// Finish the test.
|
||||
defer.resolve();
|
||||
}
|
||||
|
||||
function onSSWindowRestored(aEvent) {
|
||||
if (++windowsRestored == 4) {
|
||||
// Remove the event listener from each window
|
||||
windows.forEach(function(win) {
|
||||
win.removeEventListener("SSWindowRestored", onSSWindowRestored, true);
|
||||
});
|
||||
executeSoon(allTabsRestored);
|
||||
}
|
||||
tabsRestoredLabels.push(aEvent.target.gBrowser.tabs[0].label);
|
||||
}
|
||||
|
||||
// Used to add our listener to further windows so we can catch SSWindowRestored
|
||||
// coming from them when creating a multi-window state.
|
||||
function windowObserver(aSubject, aTopic, aData) {
|
||||
if (aTopic == "domwindowopened") {
|
||||
let newWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
newWindow.addEventListener("load", function() {
|
||||
if (++windowsOpened == 3) {
|
||||
Services.ww.unregisterNotification(windowObserver);
|
||||
}
|
||||
|
||||
// Track this window so we can remove the progress listener later.
|
||||
windows.push(newWindow);
|
||||
// Add the progress listener.
|
||||
newWindow.addEventListener("SSWindowRestored", onSSWindowRestored, true);
|
||||
}, { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
Services.ww.registerNotification(windowObserver);
|
||||
registerCleanupFunction(function() {
|
||||
windows.forEach(function(win) {
|
||||
win.removeEventListener("SSWindowRestored", onSSWindowRestored, true);
|
||||
if (win !== window) {
|
||||
BrowserTestUtils.closeWindow(win);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("SSWindowRestored", onSSWindowRestored, true);
|
||||
|
||||
// Restore states.
|
||||
ss.setBrowserState(source);
|
||||
|
||||
await defer.promise;
|
||||
});
|
||||
|
|
@ -42,6 +42,7 @@ add_task(async function() {
|
|||
}
|
||||
|
||||
ss.setWindowState(win2, JSON.stringify(winState), true);
|
||||
await promiseWindowRestored(win2);
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
let browser = win2.gBrowser.tabs[i].linkedBrowser;
|
||||
|
@ -102,6 +103,7 @@ add_task(async function() {
|
|||
await TabStateFlusher.flush(win2.gBrowser.tabs[0].linkedBrowser);
|
||||
|
||||
ss.setWindowState(win2, JSON.stringify(winState), true);
|
||||
await promiseWindowRestored(win2);
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
let browser = win2.gBrowser.tabs[i].linkedBrowser;
|
||||
|
|
|
@ -186,6 +186,10 @@ function promiseTabState(tab, state) {
|
|||
return promise;
|
||||
}
|
||||
|
||||
function promiseWindowRestored(win) {
|
||||
return new Promise(resolve => win.addEventListener("SSWindowRestored", resolve, {once: true}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a content -> chrome message.
|
||||
*/
|
||||
|
|
|
@ -120,6 +120,7 @@ if (typeof Mozilla == "undefined") {
|
|||
* <li>privateWindow
|
||||
* <li>quit
|
||||
* <li>readerMode-urlBar
|
||||
* <li>screenshots
|
||||
* <li>search
|
||||
* <li>searchIcon
|
||||
* <li>searchPrefsLink
|
||||
|
|
|
@ -257,6 +257,12 @@ this.UITour = {
|
|||
return aDocument.getElementById("pageAction-urlbar-sendToDevice") ||
|
||||
aDocument.getElementById("pageAction-panel-sendToDevice");
|
||||
},
|
||||
}],
|
||||
["screenshots", {
|
||||
query: (aDocument) => {
|
||||
return aDocument.getElementById("pageAction-urlbar-screenshots") ||
|
||||
aDocument.getElementById("pageAction-panel-screenshots");
|
||||
},
|
||||
}]
|
||||
]),
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ function getExpectedTargets() {
|
|||
"privateWindow",
|
||||
...(hasQuit ? ["quit"] : []),
|
||||
"readerMode-urlBar",
|
||||
"screenshots",
|
||||
"trackingProtection",
|
||||
"urlbar",
|
||||
];
|
||||
|
@ -37,6 +38,7 @@ function getExpectedTargets() {
|
|||
add_task(setup_UITourTest);
|
||||
|
||||
add_UITour_task(async function test_availableTargets() {
|
||||
await ensureScreenshotsEnabled();
|
||||
let data = await getConfigurationPromise("availableTargets");
|
||||
let expecteds = getExpectedTargets();
|
||||
ok_targets(data, expecteds);
|
||||
|
@ -80,6 +82,7 @@ add_UITour_task(async function test_availableTargets_removeUrlbarPageActionsAll(
|
|||
ok_targets(data, expecteds);
|
||||
let expectedActions = [
|
||||
[ "pocket", "pageAction-panel-pocket" ],
|
||||
[ "screenshots", "pageAction-panel-screenshots" ],
|
||||
[ "pageAction-bookmark", "pageAction-panel-bookmark" ],
|
||||
[ "pageAction-copyURL", "pageAction-panel-copyURL" ],
|
||||
[ "pageAction-emailLink", "pageAction-panel-emailLink" ],
|
||||
|
@ -99,6 +102,7 @@ add_UITour_task(async function test_availableTargets_addUrlbarPageActionsAll() {
|
|||
ok_targets(data, expecteds);
|
||||
let expectedActions = [
|
||||
[ "pocket", "pocket-button-box" ],
|
||||
[ "screenshots", "pageAction-urlbar-screenshots" ],
|
||||
[ "pageAction-bookmark", "star-button-box" ],
|
||||
[ "pageAction-copyURL", "pageAction-urlbar-copyURL" ],
|
||||
[ "pageAction-emailLink", "pageAction-urlbar-emailLink" ],
|
||||
|
@ -150,3 +154,14 @@ var pageActionsHelper = {
|
|||
this._originalStates = null;
|
||||
}
|
||||
};
|
||||
|
||||
function ensureScreenshotsEnabled() {
|
||||
SpecialPowers.pushPrefEnv({ set: [
|
||||
[ "extensions.screenshots.system", false ],
|
||||
[ "extensions.screenshots.system-disabled", false ]
|
||||
]});
|
||||
return BrowserTestUtils.waitForCondition(() => {
|
||||
return PageActions.actionForID("screenshots") &&
|
||||
!CustomizableUI.getWidget("screenshots_mozilla_org-browser-action");
|
||||
}, "Should enable Screenshots");
|
||||
}
|
||||
|
|
|
@ -114,8 +114,8 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
|
|||
}
|
||||
newRows = prevState.rows.map(site => {
|
||||
if (site && site.url === action.data.url) {
|
||||
const {bookmarkGuid, bookmarkTitle, lastModified} = action.data;
|
||||
return Object.assign({}, site, {bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified});
|
||||
const {bookmarkGuid, bookmarkTitle, dateAdded} = action.data;
|
||||
return Object.assign({}, site, {bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded});
|
||||
}
|
||||
return site;
|
||||
});
|
||||
|
@ -203,12 +203,16 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
let order;
|
||||
let index;
|
||||
if (prevState.length > 0) {
|
||||
order = action.data.order || prevState[0].order - 1;
|
||||
order = action.data.order !== undefined ? action.data.order : prevState[0].order - 1;
|
||||
index = newState.findIndex(section => section.order >= order);
|
||||
if (index === -1) {
|
||||
index = newState.length;
|
||||
}
|
||||
} else {
|
||||
order = action.data.order || 1;
|
||||
order = action.data.order !== undefined ? action.data.order : 0;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
const section = Object.assign({title: "", rows: [], order, enabled: false}, action.data, {initialized});
|
||||
newState.splice(index, 0, section);
|
||||
}
|
||||
|
@ -231,8 +235,11 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
rows: section.rows.map(item => {
|
||||
// find the item within the rows that is attempted to be bookmarked
|
||||
if (item.url === action.data.url) {
|
||||
const {bookmarkGuid, bookmarkTitle, lastModified} = action.data;
|
||||
Object.assign(item, {bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified});
|
||||
const {bookmarkGuid, bookmarkTitle, dateAdded} = action.data;
|
||||
Object.assign(item, {bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded});
|
||||
if (!item.type || item.type === "history") {
|
||||
item.type = "bookmark";
|
||||
}
|
||||
}
|
||||
return item;
|
||||
})
|
||||
|
@ -249,6 +256,9 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
delete newSite.bookmarkGuid;
|
||||
delete newSite.bookmarkTitle;
|
||||
delete newSite.bookmarkDateCreated;
|
||||
if (!newSite.type || newSite.type === "bookmark") {
|
||||
newSite.type = "history";
|
||||
}
|
||||
return newSite;
|
||||
}
|
||||
return item;
|
||||
|
|
|
@ -459,9 +459,9 @@ function TopSites() {
|
|||
var _action$data2 = action.data;
|
||||
const bookmarkGuid = _action$data2.bookmarkGuid,
|
||||
bookmarkTitle = _action$data2.bookmarkTitle,
|
||||
lastModified = _action$data2.lastModified;
|
||||
dateAdded = _action$data2.dateAdded;
|
||||
|
||||
return Object.assign({}, site, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified });
|
||||
return Object.assign({}, site, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded });
|
||||
}
|
||||
return site;
|
||||
});
|
||||
|
@ -558,12 +558,16 @@ function Sections() {
|
|||
let order;
|
||||
let index;
|
||||
if (prevState.length > 0) {
|
||||
order = action.data.order || prevState[0].order - 1;
|
||||
order = action.data.order !== undefined ? action.data.order : prevState[0].order - 1;
|
||||
index = newState.findIndex(section => section.order >= order);
|
||||
if (index === -1) {
|
||||
index = newState.length;
|
||||
}
|
||||
} else {
|
||||
order = action.data.order || 1;
|
||||
order = action.data.order !== undefined ? action.data.order : 0;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
const section = Object.assign({ title: "", rows: [], order, enabled: false }, action.data, { initialized });
|
||||
newState.splice(index, 0, section);
|
||||
}
|
||||
|
@ -589,9 +593,12 @@ function Sections() {
|
|||
var _action$data3 = action.data;
|
||||
const bookmarkGuid = _action$data3.bookmarkGuid,
|
||||
bookmarkTitle = _action$data3.bookmarkTitle,
|
||||
lastModified = _action$data3.lastModified;
|
||||
dateAdded = _action$data3.dateAdded;
|
||||
|
||||
Object.assign(item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified });
|
||||
Object.assign(item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded });
|
||||
if (!item.type || item.type === "history") {
|
||||
item.type = "bookmark";
|
||||
}
|
||||
}
|
||||
return item;
|
||||
})
|
||||
|
@ -608,6 +615,9 @@ function Sections() {
|
|||
delete newSite.bookmarkGuid;
|
||||
delete newSite.bookmarkTitle;
|
||||
delete newSite.bookmarkDateCreated;
|
||||
if (!newSite.type || newSite.type === "bookmark") {
|
||||
newSite.type = "history";
|
||||
}
|
||||
return newSite;
|
||||
}
|
||||
return item;
|
||||
|
@ -2985,7 +2995,7 @@ module.exports = {
|
|||
},
|
||||
bookmark: {
|
||||
intlID: "type_label_bookmarked",
|
||||
icon: "bookmark"
|
||||
icon: "bookmark-added"
|
||||
},
|
||||
trending: {
|
||||
intlID: "type_label_recommended",
|
||||
|
|
|
@ -78,6 +78,8 @@ input {
|
|||
background-image: url("chrome://browser/skin/check.svg"); }
|
||||
.icon.icon-webextension {
|
||||
background-image: url("assets/glyph-webextension-16.svg"); }
|
||||
.icon.icon-highlights {
|
||||
background-image: url("assets/glyph-highlights-16.svg"); }
|
||||
|
||||
html,
|
||||
body,
|
||||
|
@ -656,8 +658,6 @@ main {
|
|||
background-image: url("chrome://browser/skin/forward.svg");
|
||||
fill: #FFF;
|
||||
-moz-context-properties: fill; }
|
||||
.search-wrapper input[aria-expanded="true"] {
|
||||
border-radius: 4px 4px 0 0; }
|
||||
.search-wrapper .search-label {
|
||||
background: url("assets/glyph-search-16.svg") no-repeat center center/20px;
|
||||
fill: rgba(12, 12, 13, 0.6);
|
||||
|
@ -687,7 +687,6 @@ main {
|
|||
transform: scaleX(-1); }
|
||||
.search-wrapper .contentSearchSuggestionTable {
|
||||
border: 0;
|
||||
box-shadow: 0 0 0 2px #0060DF;
|
||||
transform: translateY(2px); }
|
||||
|
||||
.context-menu {
|
||||
|
@ -802,8 +801,9 @@ main {
|
|||
margin: 0; }
|
||||
.prefs-pane .actions {
|
||||
background-color: #F9F9FA;
|
||||
border-left: 1px solid #D7D7DB;
|
||||
bottom: 0;
|
||||
offset-inline-end: -1px;
|
||||
offset-inline-end: 0;
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
width: 400px; }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M9.5 3s.428 2.43 1.249 3.251S14 7.5 14 7.5s-2.43.394-3.251 1.215S9.5 12 9.5 12s-.394-2.464-1.215-3.285S5 7.5 5 7.5s2.464-.428 3.285-1.249S9.5 3 9.5 3m0-2h-.014a2 2 0 0 0-1.96 1.68 7.536 7.536 0 0 1-.659 2.154 7.9 7.9 0 0 1-2.212.7 2 2 0 0 0 .029 3.945 7.733 7.733 0 0 1 2.183.658 7.74 7.74 0 0 1 .658 2.185A2 2 0 0 0 9.489 14H9.5a2 2 0 0 0 1.971-1.657 7.891 7.891 0 0 1 .7-2.209 7.566 7.566 0 0 1 2.154-.659 2 2 0 0 0 .027-3.944 7.694 7.694 0 0 1-2.181-.7 7.731 7.731 0 0 1-.7-2.181A2 2 0 0 0 9.5 1zM3 15.5a.5.5 0 0 1-.49-.421 3.047 3.047 0 0 0-.4-1.186 3.047 3.047 0 0 0-1.186-.4.5.5 0 0 1-.007-.986 3.147 3.147 0 0 0 1.192-.417 3.051 3.051 0 0 0 .4-1.171A.5.5 0 0 1 3 10.5a.5.5 0 0 1 .492.413 3.094 3.094 0 0 0 .417 1.179 3.142 3.142 0 0 0 1.178.416.5.5 0 0 1-.007.985 3.007 3.007 0 0 0-1.172.4 3.166 3.166 0 0 0-.416 1.192A.5.5 0 0 1 3 15.5zm-.5-11a.5.5 0 0 1-.49-.42 2.344 2.344 0 0 0-.265-.82 2.344 2.344 0 0 0-.82-.265.5.5 0 0 1-.007-.986 2.41 2.41 0 0 0 .827-.277A2.306 2.306 0 0 0 2.007.92.5.5 0 0 1 2.5.5a.5.5 0 0 1 .492.412 2.353 2.353 0 0 0 .278.818 2.372 2.372 0 0 0 .816.276.5.5 0 0 1-.007.985 2.306 2.306 0 0 0-.811.266 2.41 2.41 0 0 0-.277.827.5.5 0 0 1-.491.416z" fill="context-fill"/></svg>
|
После Ширина: | Высота: | Размер: 1.3 KiB |
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -8,7 +8,7 @@
|
|||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:unpack>false</em:unpack>
|
||||
<em:version>2017.09.01.1439-222d033f</em:version>
|
||||
<em:version>2017.09.08.0882-3dbf720c</em:version>
|
||||
<em:name>Activity Stream</em:name>
|
||||
<em:description>A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you're looking for in Firefox.</em:description>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
|
|
@ -22,6 +22,7 @@ const {SystemTickFeed} = Cu.import("resource://activity-stream/lib/SystemTickFee
|
|||
const {TelemetryFeed} = Cu.import("resource://activity-stream/lib/TelemetryFeed.jsm", {});
|
||||
const {TopSitesFeed} = Cu.import("resource://activity-stream/lib/TopSitesFeed.jsm", {});
|
||||
const {TopStoriesFeed} = Cu.import("resource://activity-stream/lib/TopStoriesFeed.jsm", {});
|
||||
const {HighlightsFeed} = Cu.import("resource://activity-stream/lib/HighlightsFeed.jsm", {});
|
||||
|
||||
const DEFAULT_SITES = new Map([
|
||||
// This first item is the global list fallback for any unexpected geos
|
||||
|
@ -149,6 +150,12 @@ const FEEDS_DATA = [
|
|||
title: "Manages sections",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "section.highlights",
|
||||
factory: () => new HighlightsFeed(),
|
||||
title: "Fetches content recommendations from places db",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "section.topstories",
|
||||
factory: () => new TopStoriesFeed(),
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
|
||||
const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {});
|
||||
const {SectionsManager} = Cu.import("resource://activity-stream/lib/SectionsManager.jsm", {});
|
||||
const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
|
||||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
|
||||
const HIGHLIGHTS_MAX_LENGTH = 9;
|
||||
const HIGHLIGHTS_UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
|
||||
const SECTION_ID = "highlights";
|
||||
|
||||
this.HighlightsFeed = class HighlightsFeed {
|
||||
constructor() {
|
||||
this.highlightsLastUpdated = 0;
|
||||
this.highlights = [];
|
||||
this.dedupe = new Dedupe(this._dedupeKey);
|
||||
}
|
||||
|
||||
_dedupeKey(site) {
|
||||
return site && site.url;
|
||||
}
|
||||
|
||||
init() {
|
||||
SectionsManager.onceInitialized(this.postInit.bind(this));
|
||||
}
|
||||
|
||||
postInit() {
|
||||
SectionsManager.enableSection(SECTION_ID);
|
||||
}
|
||||
|
||||
uninit() {
|
||||
SectionsManager.disableSection(SECTION_ID);
|
||||
}
|
||||
|
||||
async fetchHighlights(broadcast = false) {
|
||||
this.highlights = await NewTabUtils.activityStreamLinks.getHighlights();
|
||||
for (let highlight of this.highlights) {
|
||||
highlight.hostname = shortURL(Object.assign({}, highlight, {url: highlight.url}));
|
||||
highlight.image = highlight.preview_image_url;
|
||||
if (highlight.bookmarkGuid) {
|
||||
highlight.type = "bookmark";
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any Highlights that are in Top Sites already
|
||||
const deduped = this.dedupe.group(this.store.getState().TopSites.rows, this.highlights);
|
||||
this.highlights = deduped[1];
|
||||
|
||||
SectionsManager.updateSection(SECTION_ID, {rows: this.highlights}, this.highlightsLastUpdated === 0 || broadcast);
|
||||
this.highlightsLastUpdated = Date.now();
|
||||
}
|
||||
|
||||
onAction(action) {
|
||||
switch (action.type) {
|
||||
case at.INIT:
|
||||
this.init();
|
||||
break;
|
||||
case at.NEW_TAB_LOAD:
|
||||
if (this.highlights.length < HIGHLIGHTS_MAX_LENGTH) {
|
||||
// If we haven't filled the highlights grid yet, fetch again.
|
||||
this.fetchHighlights(true);
|
||||
} else if (Date.now() - this.highlightsLastUpdated >= HIGHLIGHTS_UPDATE_TIME) {
|
||||
// If the last time we refreshed the data is greater than 15 minutes, fetch again.
|
||||
this.fetchHighlights(false);
|
||||
}
|
||||
break;
|
||||
case at.MIGRATION_COMPLETED:
|
||||
case at.PLACES_HISTORY_CLEARED:
|
||||
case at.PLACES_LINK_DELETED:
|
||||
case at.PLACES_LINK_BLOCKED:
|
||||
this.fetchHighlights(true);
|
||||
break;
|
||||
case at.PLACES_BOOKMARK_ADDED:
|
||||
case at.PLACES_BOOKMARK_REMOVED:
|
||||
case at.TOP_SITES_UPDATED:
|
||||
this.fetchHighlights(false);
|
||||
break;
|
||||
case at.UNINIT:
|
||||
this.uninit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.HIGHLIGHTS_UPDATE_TIME = HIGHLIGHTS_UPDATE_TIME;
|
||||
this.EXPORTED_SYMBOLS = ["HighlightsFeed", "HIGHLIGHTS_UPDATE_TIME", "SECTION_ID"];
|
|
@ -88,19 +88,24 @@ class BookmarksObserver extends Observer {
|
|||
* @param {int} dateAdded
|
||||
* @param {str} guid The unique id of the bookmark
|
||||
*/
|
||||
async onItemAdded(...args) {
|
||||
onItemAdded(...args) {
|
||||
const type = args[3];
|
||||
const guid = args[7];
|
||||
if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// bookmark: {bookmarkGuid, bookmarkTitle, lastModified, url}
|
||||
const bookmark = await NewTabUtils.activityStreamProvider.getBookmark(guid);
|
||||
this.dispatch({type: at.PLACES_BOOKMARK_ADDED, data: bookmark});
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
const uri = args[4];
|
||||
const bookmarkTitle = args[5];
|
||||
const dateAdded = args[6];
|
||||
const bookmarkGuid = args[7];
|
||||
this.dispatch({
|
||||
type: at.PLACES_BOOKMARK_ADDED,
|
||||
data: {
|
||||
bookmarkGuid,
|
||||
bookmarkTitle,
|
||||
dateAdded,
|
||||
url: uri.spec
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,7 +34,26 @@ const BUILT_IN_SECTIONS = {
|
|||
emptyState: {
|
||||
message: {id: "topstories_empty_state", values: {provider: options.provider_name}},
|
||||
icon: "check"
|
||||
}
|
||||
},
|
||||
order: 0
|
||||
}),
|
||||
"feeds.section.highlights": options => ({
|
||||
id: "highlights",
|
||||
pref: {
|
||||
titleString: {id: "settings_pane_highlights_header"},
|
||||
descString: {id: "settings_pane_highlights_body2"}
|
||||
},
|
||||
shouldHidePref: false,
|
||||
eventSource: "HIGHLIGHTS",
|
||||
icon: "highlights",
|
||||
title: {id: "header_highlights"},
|
||||
maxRows: 3,
|
||||
availableContextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
|
||||
emptyState: {
|
||||
message: {id: "highlights_empty_state"},
|
||||
icon: "highlights"
|
||||
},
|
||||
order: 1
|
||||
})
|
||||
};
|
||||
|
||||
|
|
|
@ -159,10 +159,12 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
* Lazily initialize PingCentre to send pings
|
||||
*/
|
||||
get pingCentre() {
|
||||
const ACTIVITY_STREAM_ID = "activity-stream";
|
||||
Object.defineProperty(this, "pingCentre",
|
||||
{
|
||||
value: new PingCentre({
|
||||
topic: "activity-stream",
|
||||
topic: ACTIVITY_STREAM_ID,
|
||||
filter: ACTIVITY_STREAM_ID,
|
||||
overrideEndpointPref: ACTIVITY_STREAM_ENDPOINT_PREF
|
||||
})
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ function getPath(url) {
|
|||
this.TippyTopProvider = class TippyTopProvider {
|
||||
constructor() {
|
||||
this._sitesByDomain = new Map();
|
||||
this.initialized = false;
|
||||
}
|
||||
async init() {
|
||||
// Load the Tippy Top sites from the json manifest.
|
||||
|
@ -35,6 +36,7 @@ this.TippyTopProvider = class TippyTopProvider {
|
|||
this._sitesByDomain.set(getDomain(url), site);
|
||||
}
|
||||
}
|
||||
this.initialized = true;
|
||||
} catch (error) {
|
||||
Cu.reportError("Failed to load tippy top manifest.");
|
||||
}
|
||||
|
|
|
@ -81,6 +81,10 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
return pinned.slice(0, TOP_SITES_SHOWMORE_LENGTH);
|
||||
}
|
||||
async refresh(target = null) {
|
||||
if (!this._tippyTopProvider.initialized) {
|
||||
await this._tippyTopProvider.init();
|
||||
}
|
||||
|
||||
const links = await this.getLinksWithDefaults();
|
||||
|
||||
// First, cache existing screenshots in case we need to reuse them
|
||||
|
@ -165,7 +169,6 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
async onAction(action) {
|
||||
switch (action.type) {
|
||||
case at.INIT:
|
||||
await this._tippyTopProvider.init();
|
||||
this.refresh();
|
||||
break;
|
||||
case at.NEW_TAB_LOAD:
|
||||
|
|
|
@ -68,7 +68,7 @@ describe("Reducers", () => {
|
|||
url: "bar.com",
|
||||
bookmarkGuid: "bookmark123",
|
||||
bookmarkTitle: "Title for bar.com",
|
||||
lastModified: 1234567
|
||||
dateAdded: 1234567
|
||||
}
|
||||
};
|
||||
const nextState = TopSites(oldState, action);
|
||||
|
@ -77,7 +77,7 @@ describe("Reducers", () => {
|
|||
assert.equal(newRow.url, action.data.url);
|
||||
assert.equal(newRow.bookmarkGuid, action.data.bookmarkGuid);
|
||||
assert.equal(newRow.bookmarkTitle, action.data.bookmarkTitle);
|
||||
assert.equal(newRow.bookmarkDateCreated, action.data.lastModified);
|
||||
assert.equal(newRow.bookmarkDateCreated, action.data.dateAdded);
|
||||
|
||||
// old row is unchanged
|
||||
assert.equal(nextState.rows[0], oldState.rows[0]);
|
||||
|
@ -92,7 +92,7 @@ describe("Reducers", () => {
|
|||
url: "bar.com",
|
||||
bookmarkGuid: "bookmark123",
|
||||
bookmarkTitle: "Title for bar.com",
|
||||
lastModified: 123456
|
||||
dateAdded: 123456
|
||||
}]
|
||||
};
|
||||
const action = {type: at.PLACES_BOOKMARK_REMOVED, data: {url: "bar.com"}};
|
||||
|
@ -211,7 +211,8 @@ describe("Reducers", () => {
|
|||
title: `Foo Bar ${i}`,
|
||||
initialized: false,
|
||||
rows: [{url: "www.foo.bar"}, {url: "www.other.url"}],
|
||||
order: i
|
||||
order: i,
|
||||
type: "history"
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -254,6 +255,24 @@ describe("Reducers", () => {
|
|||
assert.equal(newState[0].id, newSection.id);
|
||||
assert.ok(newState[0].order < newState[1].order);
|
||||
});
|
||||
it("should insert sections with a 0 `order` at the top on SECTION_REGISTER", () => {
|
||||
const newSection = {id: "new_section", order: 0};
|
||||
const action = {type: at.SECTION_REGISTER, data: newSection};
|
||||
const newState = Sections(oldState, action);
|
||||
assert.equal(newState[0].id, newSection.id);
|
||||
});
|
||||
it("should insert sections with a 1 `order` in the right spot on SECTION_REGISTER", () => {
|
||||
const newSection = {id: "new_section", order: 1};
|
||||
const action = {type: at.SECTION_REGISTER, data: newSection};
|
||||
const newState = Sections(oldState, action);
|
||||
assert.equal(newState[1].id, newSection.id);
|
||||
});
|
||||
it("should insert sections with higher `order` than any existing at the bottom on SECTION_REGISTER", () => {
|
||||
const newSection = {id: "new_section", order: 10000};
|
||||
const action = {type: at.SECTION_REGISTER, data: newSection};
|
||||
const newState = Sections(oldState, action);
|
||||
assert.equal(newState[newState.length - 1].id, newSection.id);
|
||||
});
|
||||
it("should set newSection.rows === [] if no rows are provided on SECTION_REGISTER", () => {
|
||||
const action = {type: at.SECTION_REGISTER, data: {id: "foo_bar_5", title: "Foo Bar 5"}};
|
||||
const newState = Sections(oldState, action);
|
||||
|
@ -328,7 +347,7 @@ describe("Reducers", () => {
|
|||
url: "www.foo.bar",
|
||||
bookmarkGuid: "bookmark123",
|
||||
bookmarkTitle: "Title for bar.com",
|
||||
lastModified: 1234567
|
||||
dateAdded: 1234567
|
||||
}
|
||||
};
|
||||
const nextState = Sections(oldState, action);
|
||||
|
@ -338,9 +357,10 @@ describe("Reducers", () => {
|
|||
|
||||
// new row has bookmark data
|
||||
assert.equal(newRow.url, action.data.url);
|
||||
assert.equal(newRow.type, "bookmark");
|
||||
assert.equal(newRow.bookmarkGuid, action.data.bookmarkGuid);
|
||||
assert.equal(newRow.bookmarkTitle, action.data.bookmarkTitle);
|
||||
assert.equal(newRow.bookmarkDateCreated, action.data.lastModified);
|
||||
assert.equal(newRow.bookmarkDateCreated, action.data.dateAdded);
|
||||
|
||||
// old row is unchanged
|
||||
assert.equal(oldRow, oldState[0].rows[1]);
|
||||
|
@ -362,14 +382,16 @@ describe("Reducers", () => {
|
|||
item.rows[0].bookmarkGuid = "bookmark123";
|
||||
item.rows[0].bookmarkTitle = "Title for bar.com";
|
||||
item.rows[0].bookmarkDateCreated = 1234567;
|
||||
item.rows[0].type = "bookmark";
|
||||
});
|
||||
const nextState = Sections(oldState, action);
|
||||
// check a section to ensure the correct bookmark was removed
|
||||
const newRow = nextState[0].rows[0];
|
||||
const oldRow = nextState[0].rows[1];
|
||||
|
||||
// new row has bookmark data
|
||||
// new row isn't a bookmark
|
||||
assert.equal(newRow.url, action.data.url);
|
||||
assert.equal(newRow.type, "history");
|
||||
assert.isUndefined(newRow.bookmarkGuid);
|
||||
assert.isUndefined(newRow.bookmarkTitle);
|
||||
assert.isUndefined(newRow.bookmarkDateCreated);
|
||||
|
|
|
@ -23,7 +23,8 @@ describe("ActivityStream", () => {
|
|||
"lib/SystemTickFeed.jsm": {SystemTickFeed: Fake},
|
||||
"lib/TelemetryFeed.jsm": {TelemetryFeed: Fake},
|
||||
"lib/TopSitesFeed.jsm": {TopSitesFeed: Fake},
|
||||
"lib/TopStoriesFeed.jsm": {TopStoriesFeed: Fake}
|
||||
"lib/TopStoriesFeed.jsm": {TopStoriesFeed: Fake},
|
||||
"lib/HighlightsFeed.jsm": {HighlightsFeed: Fake}
|
||||
}));
|
||||
as = new ActivityStream();
|
||||
sandbox.stub(as.store, "init");
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
"use strict";
|
||||
const injector = require("inject!lib/HighlightsFeed.jsm");
|
||||
const {GlobalOverrider} = require("test/unit/utils");
|
||||
const {actionTypes: at} = require("common/Actions.jsm");
|
||||
const {Dedupe} = require("common/Dedupe.jsm");
|
||||
|
||||
const FAKE_LINKS = new Array(9).fill(null).map((v, i) => ({url: `http://www.site${i}.com`}));
|
||||
|
||||
describe("Top Sites Feed", () => {
|
||||
let HighlightsFeed;
|
||||
let HIGHLIGHTS_UPDATE_TIME;
|
||||
let SECTION_ID;
|
||||
let feed;
|
||||
let globals;
|
||||
let sandbox;
|
||||
let links;
|
||||
let clock;
|
||||
let fakeNewTabUtils;
|
||||
let sectionsManagerStub;
|
||||
let shortURLStub;
|
||||
|
||||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
sandbox = globals.sandbox;
|
||||
fakeNewTabUtils = {activityStreamLinks: {getHighlights: sandbox.spy(() => Promise.resolve(links))}};
|
||||
sectionsManagerStub = {
|
||||
onceInitialized: sinon.stub().callsFake(callback => callback()),
|
||||
enableSection: sinon.spy(),
|
||||
disableSection: sinon.spy(),
|
||||
updateSection: sinon.spy(),
|
||||
sections: new Map([["highlights", {}]])
|
||||
};
|
||||
shortURLStub = sinon.stub().callsFake(site => site.url);
|
||||
globals.set("NewTabUtils", fakeNewTabUtils);
|
||||
({HighlightsFeed, HIGHLIGHTS_UPDATE_TIME, SECTION_ID} = injector({
|
||||
"lib/ShortURL.jsm": {shortURL: shortURLStub},
|
||||
"lib/SectionsManager.jsm": {SectionsManager: sectionsManagerStub},
|
||||
"common/Dedupe.jsm": {Dedupe}
|
||||
}));
|
||||
feed = new HighlightsFeed();
|
||||
feed.store = {dispatch: sinon.spy(), getState() { return {TopSites: {rows: Array(12).fill(null).map((v, i) => ({url: `http://www.topsite${i}.com`}))}}; }};
|
||||
links = FAKE_LINKS;
|
||||
clock = sinon.useFakeTimers();
|
||||
});
|
||||
afterEach(() => {
|
||||
globals.restore();
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
describe("#init", () => {
|
||||
it("should create a HighlightsFeed", () => {
|
||||
assert.instanceOf(feed, HighlightsFeed);
|
||||
});
|
||||
it("should call SectionsManager.onceInitialized on INIT", () => {
|
||||
feed.onAction({type: at.INIT});
|
||||
assert.calledOnce(sectionsManagerStub.onceInitialized);
|
||||
});
|
||||
it("should enable its section", () => {
|
||||
feed.onAction({type: at.INIT});
|
||||
assert.calledOnce(sectionsManagerStub.enableSection);
|
||||
assert.calledWith(sectionsManagerStub.enableSection, SECTION_ID);
|
||||
});
|
||||
it("should *not* fetch highlights on init to avoid loading Places too early", () => {
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
|
||||
feed.onAction({type: at.INIT});
|
||||
|
||||
assert.notCalled(feed.fetchHighlights);
|
||||
});
|
||||
});
|
||||
describe("#fetchHighlights", () => {
|
||||
it("should add hostname and image to each link", async () => {
|
||||
links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
|
||||
await feed.fetchHighlights();
|
||||
assert.equal(feed.highlights[0].hostname, links[0].url);
|
||||
assert.equal(feed.highlights[0].image, links[0].preview_image_url);
|
||||
});
|
||||
it("should not include any links already in Top Sites", async () => {
|
||||
links = [
|
||||
{url: "https://mozilla.org"},
|
||||
{url: "http://www.topsite0.com"},
|
||||
{url: "http://www.topsite1.com"},
|
||||
{url: "http://www.topsite2.com"}
|
||||
];
|
||||
await feed.fetchHighlights();
|
||||
assert.equal(feed.highlights.length, 1);
|
||||
assert.deepEqual(feed.highlights[0], links[0]);
|
||||
});
|
||||
it("should set type to bookmark if there is a bookmarkGuid", async () => {
|
||||
links = [{url: "https://mozilla.org", type: "history", bookmarkGuid: "1234567890"}];
|
||||
await feed.fetchHighlights();
|
||||
assert.equal(feed.highlights[0].type, "bookmark");
|
||||
});
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
it("should disable its section", () => {
|
||||
feed.onAction({type: at.UNINIT});
|
||||
assert.calledOnce(sectionsManagerStub.disableSection);
|
||||
assert.calledWith(sectionsManagerStub.disableSection, SECTION_ID);
|
||||
});
|
||||
});
|
||||
describe("#onAction", () => {
|
||||
it("should fetch highlights on NEW_TAB_LOAD after update interval", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
assert.notCalled(feed.fetchHighlights);
|
||||
|
||||
clock.tick(HIGHLIGHTS_UPDATE_TIME);
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
});
|
||||
it("should fetch highlights on NEW_TAB_LOAD if grid is empty", async () => {
|
||||
links = [];
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
});
|
||||
it("should fetch highlights on NEW_TAB_LOAD if grid isn't full", async () => {
|
||||
links = new Array(8).fill(null).map((v, i) => ({url: `http://www.site${i}.com`}));
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.NEW_TAB_LOAD});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
});
|
||||
it("should fetch highlights on MIGRATION_COMPLETED", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.MIGRATION_COMPLETED});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWith(feed.fetchHighlights, true);
|
||||
});
|
||||
it("should fetch highlights on PLACES_HISTORY_CLEARED", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.PLACES_HISTORY_CLEARED});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWith(feed.fetchHighlights, true);
|
||||
});
|
||||
it("should fetch highlights on PLACES_LINK_DELETED", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.PLACES_LINK_DELETED});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWith(feed.fetchHighlights, true);
|
||||
});
|
||||
it("should fetch highlights on PLACES_LINK_BLOCKED", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.PLACES_LINK_BLOCKED});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWith(feed.fetchHighlights, true);
|
||||
});
|
||||
it("should fetch highlights on PLACES_BOOKMARK_ADDED", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.PLACES_BOOKMARK_ADDED});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWith(feed.fetchHighlights, false);
|
||||
});
|
||||
it("should fetch highlights on PLACES_BOOKMARK_REMOVED", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.PLACES_BOOKMARK_REMOVED});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWith(feed.fetchHighlights, false);
|
||||
});
|
||||
it("should fetch highlights on TOP_SITES_UPDATED", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
feed.onAction({type: at.TOP_SITES_UPDATED});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWith(feed.fetchHighlights, false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -3,7 +3,7 @@ const {HistoryObserver, BookmarksObserver} = PlacesFeed;
|
|||
const {GlobalOverrider} = require("test/unit/utils");
|
||||
const {actionTypes: at} = require("common/Actions.jsm");
|
||||
|
||||
const FAKE_BOOKMARK = {bookmarkGuid: "xi31", bookmarkTitle: "Foo", lastModified: 123214232, url: "foo.com"};
|
||||
const FAKE_BOOKMARK = {bookmarkGuid: "xi31", bookmarkTitle: "Foo", dateAdded: 123214232, url: "foo.com"};
|
||||
const TYPE_BOOKMARK = 0; // This is fake, for testing
|
||||
|
||||
const BLOCKED_EVENT = "newtab-linkBlocked"; // The event dispatched in NewTabUtils when a link is blocked;
|
||||
|
@ -210,28 +210,18 @@ describe("PlacesFeed", () => {
|
|||
});
|
||||
describe("#onItemAdded", () => {
|
||||
beforeEach(() => {
|
||||
// Make sure getBookmark returns our fake bookmark if it is called with the expected guid
|
||||
sandbox.stub(global.NewTabUtils.activityStreamProvider, "getBookmark")
|
||||
.withArgs(FAKE_BOOKMARK.guid).returns(Promise.resolve(FAKE_BOOKMARK));
|
||||
});
|
||||
it("should dispatch a PLACES_BOOKMARK_ADDED action with the bookmark data", async () => {
|
||||
// Yes, onItemAdded has at least 8 arguments. See function definition for docs.
|
||||
const args = [null, null, null, TYPE_BOOKMARK, null, null, null, FAKE_BOOKMARK.guid];
|
||||
const args = [null, null, null, TYPE_BOOKMARK,
|
||||
{spec: FAKE_BOOKMARK.url}, FAKE_BOOKMARK.bookmarkTitle,
|
||||
FAKE_BOOKMARK.dateAdded,
|
||||
FAKE_BOOKMARK.bookmarkGuid
|
||||
];
|
||||
await observer.onItemAdded(...args);
|
||||
|
||||
assert.calledWith(dispatch, {type: at.PLACES_BOOKMARK_ADDED, data: FAKE_BOOKMARK});
|
||||
});
|
||||
it("should catch errors gracefully", async () => {
|
||||
const e = new Error("test error");
|
||||
global.NewTabUtils.activityStreamProvider.getBookmark.restore();
|
||||
sandbox.stub(global.NewTabUtils.activityStreamProvider, "getBookmark")
|
||||
.returns(Promise.reject(e));
|
||||
|
||||
const args = [null, null, null, TYPE_BOOKMARK, null, null, null, FAKE_BOOKMARK.guid];
|
||||
await observer.onItemAdded(...args);
|
||||
|
||||
assert.calledWith(global.Components.utils.reportError, e);
|
||||
});
|
||||
it("should ignore events that are not of TYPE_BOOKMARK", async () => {
|
||||
const args = [null, null, null, "nottypebookmark"];
|
||||
await observer.onItemAdded(...args);
|
||||
|
|
|
@ -29,8 +29,9 @@ describe("SectionsManager", () => {
|
|||
SectionsManager.sections.clear();
|
||||
SectionsManager.initialized = false;
|
||||
SectionsManager.init();
|
||||
assert.equal(SectionsManager.sections.size, 1);
|
||||
assert.equal(SectionsManager.sections.size, 2);
|
||||
assert.ok(SectionsManager.sections.has("topstories"));
|
||||
assert.ok(SectionsManager.sections.has("highlights"));
|
||||
});
|
||||
it("should set .initialized to true", () => {
|
||||
SectionsManager.sections.clear();
|
||||
|
@ -215,11 +216,14 @@ describe("SectionsFeed", () => {
|
|||
it("should call onAddSection for any already added sections in SectionsManager", () => {
|
||||
SectionsManager.init();
|
||||
assert.ok(SectionsManager.sections.has("topstories"));
|
||||
assert.ok(SectionsManager.sections.has("highlights"));
|
||||
const topstories = SectionsManager.sections.get("topstories");
|
||||
const highlights = SectionsManager.sections.get("highlights");
|
||||
sinon.spy(feed, "onAddSection");
|
||||
feed.init();
|
||||
assert.calledOnce(feed.onAddSection);
|
||||
assert.calledTwice(feed.onAddSection);
|
||||
assert.calledWith(feed.onAddSection, SectionsManager.ADD_SECTION, "topstories", topstories);
|
||||
assert.calledWith(feed.onAddSection, SectionsManager.ADD_SECTION, "highlights", highlights);
|
||||
});
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
|
|
|
@ -15,7 +15,7 @@ const FAKE_SCREENSHOT = "data123";
|
|||
|
||||
function FakeTippyTopProvider() {}
|
||||
FakeTippyTopProvider.prototype = {
|
||||
async init() {},
|
||||
async init() { this.initialized = true; },
|
||||
processSite(site) { return site; }
|
||||
};
|
||||
|
||||
|
@ -247,6 +247,11 @@ describe("Top Sites Feed", () => {
|
|||
});
|
||||
});
|
||||
describe("#refresh", () => {
|
||||
it("should initialise _tippyTopProvider if it's not already initialised", async () => {
|
||||
feed._tippyTopProvider.initialized = false;
|
||||
await feed.refresh(action);
|
||||
assert.ok(feed._tippyTopProvider.initialized);
|
||||
});
|
||||
it("should dispatch an action with the links returned", async () => {
|
||||
sandbox.stub(feed, "getScreenshot");
|
||||
await feed.refresh(action);
|
||||
|
|
|
@ -47,6 +47,7 @@ const CONTENT = {
|
|||
label: GetStringFromName(changeAutofillOptsKey),
|
||||
accessKey: "C",
|
||||
callbackState: "open-pref",
|
||||
disableHighlight: true,
|
||||
},
|
||||
options: {
|
||||
persistWhileVisible: true,
|
||||
|
@ -141,9 +142,9 @@ let FormAutofillDoorhanger = {
|
|||
return [null, null];
|
||||
}
|
||||
|
||||
let {label, accessKey, callbackState} = mainActionParams;
|
||||
let {label, accessKey, disableHighlight, callbackState} = mainActionParams;
|
||||
let callback = resolve.bind(null, callbackState);
|
||||
let mainAction = {label, accessKey, callback};
|
||||
let mainAction = {label, accessKey, callback, disableHighlight};
|
||||
|
||||
if (!secondaryActionParams) {
|
||||
return [mainAction, null];
|
||||
|
|
|
@ -24,11 +24,11 @@
|
|||
}
|
||||
|
||||
#onboarding-overlay-button {
|
||||
padding: 0;
|
||||
padding: 10px 0 0 0;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 34px;
|
||||
offset-inline-start: 34px;
|
||||
top: 4px;
|
||||
offset-inline-start: 12px;
|
||||
border: none;
|
||||
/* Set to none so no grey contrast background in the high-contrast mode */
|
||||
background: none;
|
||||
|
|
|
@ -22,7 +22,7 @@ const BRAND_SHORT_NAME = Services.strings
|
|||
const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
|
||||
const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
|
||||
const ONBOARDING_MIN_WIDTH_PX = 960;
|
||||
const SPEECH_BUBBLE_MIN_WIDTH_PX = 1150;
|
||||
const SPEECH_BUBBLE_MIN_WIDTH_PX = 1130;
|
||||
|
||||
/**
|
||||
* Add any number of tours, key is the tourId, value should follow the format below
|
||||
|
|
|
@ -11,6 +11,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
|||
"resource://gre/modules/AppConstants.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ClientID",
|
||||
"resource://gre/modules/ClientID.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
|
||||
"resource://gre/modules/TelemetryEnvironment.jsm");
|
||||
|
||||
const PREF_BRANCH = "browser.ping-centre.";
|
||||
|
||||
|
@ -36,6 +38,7 @@ class PingCentre {
|
|||
}
|
||||
|
||||
this._topic = options.topic;
|
||||
this._filter = options.filter;
|
||||
this._prefs = Services.prefs.getBranch("");
|
||||
|
||||
this._setPingEndpoint(options.topic, options.overrideEndpointPref);
|
||||
|
@ -89,7 +92,23 @@ class PingCentre {
|
|||
this._fhrEnabled = this._prefs.getBoolPref(prefKey);
|
||||
}
|
||||
|
||||
_createExperimentsString(activeExperiments) {
|
||||
let experimentsString = "";
|
||||
for (let experimentID in activeExperiments) {
|
||||
if (!activeExperiments[experimentID] ||
|
||||
!activeExperiments[experimentID].branch ||
|
||||
(this._filter && !experimentID.includes(this._filter))) {
|
||||
continue;
|
||||
}
|
||||
let expString = `${experimentID}:${activeExperiments[experimentID].branch}`;
|
||||
experimentsString = experimentsString.concat(`${expString};`);
|
||||
}
|
||||
return experimentsString;
|
||||
}
|
||||
|
||||
async sendPing(data) {
|
||||
let experiments = TelemetryEnvironment.getActiveExperiments();
|
||||
let experimentsString = this._createExperimentsString(experiments);
|
||||
if (!this.enabled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -98,6 +117,7 @@ class PingCentre {
|
|||
const payload = Object.assign({
|
||||
topic: this._topic,
|
||||
client_id: clientID,
|
||||
shield_id: experimentsString,
|
||||
release_channel: AppConstants.MOZ_UPDATE_CHANNEL
|
||||
}, data);
|
||||
|
||||
|
|
|
@ -61,7 +61,6 @@ addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
|
|||
addons_install_button=install
|
||||
addons_uninstall_button=uninstall
|
||||
addons_adb_label=ADB Helper Add-on
|
||||
addons_adapters_label=Tools Adapters Add-on
|
||||
addons_adb_warning=USB devices won’t be detected without this add-on
|
||||
addons_status_unknown=?
|
||||
addons_status_installed=Installed
|
||||
|
|
|
@ -17,9 +17,6 @@ var {initCssProperties} = require("devtools/shared/fronts/css-properties");
|
|||
loader.lazyGetter(this, "StyleSheetsFront",
|
||||
() => require("devtools/shared/fronts/stylesheets").StyleSheetsFront);
|
||||
|
||||
loader.lazyGetter(this, "StyleEditorFront",
|
||||
() => require("devtools/shared/fronts/styleeditor").StyleEditorFront);
|
||||
|
||||
var StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
|
@ -54,12 +51,7 @@ StyleEditorPanel.prototype = {
|
|||
|
||||
this.target.on("close", this.destroy);
|
||||
|
||||
if (this.target.form.styleSheetsActor) {
|
||||
this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
|
||||
} else {
|
||||
/* We're talking to a pre-Firefox 29 server-side */
|
||||
this._debuggee = StyleEditorFront(this.target.client, this.target.form);
|
||||
}
|
||||
this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
|
||||
|
||||
// Initialize the CSS properties database.
|
||||
const {cssProperties} = yield initCssProperties(this._toolbox);
|
||||
|
|
|
@ -33,7 +33,6 @@ function CloseUI() {
|
|||
|
||||
function BuildUI(addons) {
|
||||
BuildItem(addons.adb, "adb");
|
||||
BuildItem(addons.adapters, "adapters");
|
||||
for (let addon of addons.simulators) {
|
||||
BuildItem(addon, "simulator");
|
||||
}
|
||||
|
@ -82,16 +81,6 @@ function BuildItem(addon, type) {
|
|||
li.setAttribute("addon", type);
|
||||
name.textContent = Strings.GetStringFromName("addons_adb_label");
|
||||
break;
|
||||
case "adapters":
|
||||
li.setAttribute("addon", type);
|
||||
try {
|
||||
name.textContent = Strings.GetStringFromName("addons_adapters_label");
|
||||
} catch (e) {
|
||||
// This code (bug 1081093) will be backported to Aurora, which doesn't
|
||||
// contain this string.
|
||||
name.textContent = "Tools Adapters Add-on";
|
||||
}
|
||||
break;
|
||||
case "simulator":
|
||||
li.setAttribute("addon", "simulator-" + addon.version);
|
||||
let stability = Strings.GetStringFromName("addons_" + addon.stability);
|
||||
|
|
|
@ -87,22 +87,16 @@ var UI = {
|
|||
this.reportError("error_appProjectsLoadFailed");
|
||||
});
|
||||
|
||||
// Auto install the ADB Addon Helper and Tools Adapters. Only once.
|
||||
// Auto install the ADB Addon Helper. Only once.
|
||||
// If the user decides to uninstall any of this addon, we won't install it again.
|
||||
let autoinstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
|
||||
let autoinstallFxdtAdapters = Services.prefs.getBoolPref("devtools.webide.autoinstallFxdtAdapters");
|
||||
if (autoinstallADBHelper) {
|
||||
GetAvailableAddons().then(addons => {
|
||||
addons.adb.install();
|
||||
}, console.error);
|
||||
}
|
||||
if (autoinstallFxdtAdapters) {
|
||||
GetAvailableAddons().then(addons => {
|
||||
addons.adapters.install();
|
||||
}, console.error);
|
||||
}
|
||||
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", false);
|
||||
|
||||
this.setupDeck();
|
||||
|
||||
|
|
|
@ -13,10 +13,8 @@ const ADDONS_URL = "devtools.webide.addonsURL";
|
|||
|
||||
var SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
|
||||
var ADB_LINK = Services.prefs.getCharPref("devtools.webide.adbAddonURL");
|
||||
var ADAPTERS_LINK = Services.prefs.getCharPref("devtools.webide.adaptersAddonURL");
|
||||
var SIMULATOR_ADDON_ID = Services.prefs.getCharPref("devtools.webide.simulatorAddonID");
|
||||
var ADB_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adbAddonID");
|
||||
var ADAPTERS_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adaptersAddonID");
|
||||
|
||||
var platform = Services.appShell.hiddenDOMWindow.navigator.platform;
|
||||
var OS = "";
|
||||
|
@ -38,7 +36,7 @@ addonsListener.onDisabled =
|
|||
addonsListener.onInstalled =
|
||||
addonsListener.onUninstalled = (updatedAddon) => {
|
||||
GetAvailableAddons().then(addons => {
|
||||
for (let a of [...addons.simulators, addons.adb, addons.adapters]) {
|
||||
for (let a of [...addons.simulators, addons.adb]) {
|
||||
if (a.addonID == updatedAddon.id) {
|
||||
a.updateInstallStatus();
|
||||
}
|
||||
|
@ -62,7 +60,6 @@ var GetAvailableAddons = exports.GetAvailableAddons = function () {
|
|||
}
|
||||
}
|
||||
addons.adb = new ADBAddon();
|
||||
addons.adapters = new AdaptersAddon();
|
||||
resolve(addons);
|
||||
}, e => {
|
||||
GetAvailableAddons_promise = null;
|
||||
|
@ -186,11 +183,3 @@ function ADBAddon() {
|
|||
this.updateInstallStatus();
|
||||
}
|
||||
ADBAddon.prototype = Object.create(Addon.prototype);
|
||||
|
||||
function AdaptersAddon() {
|
||||
EventEmitter.decorate(this);
|
||||
this.xpiLink = ADAPTERS_LINK.replace(/#OS#/g, OS);
|
||||
this.addonID = ADAPTERS_ADDON_ID;
|
||||
this.updateInstallStatus();
|
||||
}
|
||||
AdaptersAddon.prototype = Object.create(Addon.prototype);
|
||||
|
|
|
@ -166,7 +166,7 @@ ProjectList.prototype = {
|
|||
try {
|
||||
url = new URL(tab.url);
|
||||
} catch (e) {
|
||||
// Don't try to handle invalid URLs, especially from Valence.
|
||||
// Don't try to handle invalid URLs
|
||||
continue;
|
||||
}
|
||||
// Wanted to use nsIFaviconService here, but it only works for visited
|
||||
|
|
Двоичные данные
devtools/client/webide/test/addons/fxdt-adapters-linux32.xpi
Двоичные данные
devtools/client/webide/test/addons/fxdt-adapters-linux32.xpi
Двоичный файл не отображается.
Двоичные данные
devtools/client/webide/test/addons/fxdt-adapters-linux64.xpi
Двоичные данные
devtools/client/webide/test/addons/fxdt-adapters-linux64.xpi
Двоичный файл не отображается.
Двоичные данные
devtools/client/webide/test/addons/fxdt-adapters-mac64.xpi
Двоичные данные
devtools/client/webide/test/addons/fxdt-adapters-mac64.xpi
Двоичный файл не отображается.
Двоичные данные
devtools/client/webide/test/addons/fxdt-adapters-win32.xpi
Двоичные данные
devtools/client/webide/test/addons/fxdt-adapters-win32.xpi
Двоичный файл не отображается.
|
@ -26,10 +26,6 @@ support-files =
|
|||
addons/adbhelper-linux64.xpi
|
||||
addons/adbhelper-win32.xpi
|
||||
addons/adbhelper-mac64.xpi
|
||||
addons/fxdt-adapters-linux32.xpi
|
||||
addons/fxdt-adapters-linux64.xpi
|
||||
addons/fxdt-adapters-win32.xpi
|
||||
addons/fxdt-adapters-mac64.xpi
|
||||
build_app1/package.json
|
||||
build_app2/manifest.webapp
|
||||
build_app2/package.json
|
||||
|
|
|
@ -29,7 +29,6 @@ Services.prefs.setBoolPref("devtools.webide.enableLocalRuntime", true);
|
|||
Services.prefs.setCharPref("devtools.webide.addonsURL", TEST_BASE + "addons/simulators.json");
|
||||
Services.prefs.setCharPref("devtools.webide.simulatorAddonsURL", TEST_BASE + "addons/fxos_#SLASHED_VERSION#_simulator-#OS#.xpi");
|
||||
Services.prefs.setCharPref("devtools.webide.adbAddonURL", TEST_BASE + "addons/adbhelper-#OS#.xpi");
|
||||
Services.prefs.setCharPref("devtools.webide.adaptersAddonURL", TEST_BASE + "addons/fxdt-adapters-#OS#.xpi");
|
||||
Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
|
||||
Services.prefs.setCharPref("devtools.devices.url", TEST_BASE + "browser_devices.json");
|
||||
|
||||
|
@ -40,7 +39,6 @@ registerCleanupFunction(() => {
|
|||
Services.prefs.clearUserPref("devtools.webide.enabled");
|
||||
Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
|
||||
Services.prefs.clearUserPref("devtools.webide.autoinstallADBHelper");
|
||||
Services.prefs.clearUserPref("devtools.webide.autoinstallFxdtAdapters");
|
||||
Services.prefs.clearUserPref("devtools.webide.busyTimeout");
|
||||
Services.prefs.clearUserPref("devtools.webide.lastSelectedProject");
|
||||
Services.prefs.clearUserPref("devtools.webide.lastConnectedRuntime");
|
||||
|
@ -50,7 +48,6 @@ var openWebIDE = Task.async(function* (autoInstallAddons) {
|
|||
info("opening WebIDE");
|
||||
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", !!autoInstallAddons);
|
||||
Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", !!autoInstallAddons);
|
||||
|
||||
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
|
||||
let win = ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
|
||||
pref("devtools.webide.autoinstallADBHelper", true);
|
||||
pref("devtools.webide.autoinstallFxdtAdapters", true);
|
||||
pref("devtools.webide.autoConnectRuntime", true);
|
||||
pref("devtools.webide.restoreLastProject", true);
|
||||
pref("devtools.webide.enableLocalRuntime", false);
|
||||
|
@ -14,8 +13,6 @@ pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozil
|
|||
pref("devtools.webide.simulatorAddonRegExp", "fxos_(.*)_simulator@mozilla\\.org$");
|
||||
pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
|
||||
pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
|
||||
pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/valence/#OS#/valence-#OS#-latest.xpi");
|
||||
pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
|
||||
pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
|
||||
pref("devtools.webide.lastConnectedRuntime", "");
|
||||
pref("devtools.webide.lastSelectedProject", "");
|
||||
|
|
|
@ -54,7 +54,6 @@ DevToolsModules(
|
|||
'source.js',
|
||||
'storage.js',
|
||||
'string.js',
|
||||
'styleeditor.js',
|
||||
'styles.js',
|
||||
'stylesheets.js',
|
||||
'tab.js',
|
||||
|
@ -101,7 +100,7 @@ with Files('source.js'):
|
|||
with Files('storage.js'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Storage Inspector')
|
||||
|
||||
with Files('styleeditor.js'):
|
||||
with Files('stylesheets.js'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Style Editor')
|
||||
|
||||
with Files('webaudio.js'):
|
||||
|
|
|
@ -1,497 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci} = require("chrome");
|
||||
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const promise = require("promise");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const {fetch} = require("devtools/shared/DevToolsUtils");
|
||||
const {oldStyleSheetSpec, styleEditorSpec} = require("devtools/shared/specs/styleeditor");
|
||||
|
||||
loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic"));
|
||||
|
||||
var TRANSITION_CLASS = "moz-styleeditor-transitioning";
|
||||
var TRANSITION_DURATION_MS = 500;
|
||||
var TRANSITION_RULE = ":root.moz-styleeditor-transitioning, " +
|
||||
":root.moz-styleeditor-transitioning * {\n" +
|
||||
"transition-duration: " + TRANSITION_DURATION_MS +
|
||||
"ms !important;\n" +
|
||||
"transition-delay: 0ms !important;\n" +
|
||||
"transition-timing-function: ease-out !important;\n" +
|
||||
"transition-property: all !important;\n" +
|
||||
"}";
|
||||
|
||||
var OldStyleSheetActor = protocol.ActorClassWithSpec(oldStyleSheetSpec, {
|
||||
toString: function () {
|
||||
return "[OldStyleSheetActor " + this.actorID + "]";
|
||||
},
|
||||
|
||||
/**
|
||||
* Window of target
|
||||
*/
|
||||
get window() {
|
||||
return this._window || this.parentActor.window;
|
||||
},
|
||||
|
||||
/**
|
||||
* Document of target.
|
||||
*/
|
||||
get document() {
|
||||
return this.window.document;
|
||||
},
|
||||
|
||||
/**
|
||||
* URL of underlying stylesheet.
|
||||
*/
|
||||
get href() {
|
||||
return this.rawSheet.href;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the index (order) of stylesheet in the document.
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
get styleSheetIndex() {
|
||||
if (this._styleSheetIndex == -1) {
|
||||
for (let i = 0; i < this.document.styleSheets.length; i++) {
|
||||
if (this.document.styleSheets[i] == this.rawSheet) {
|
||||
this._styleSheetIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._styleSheetIndex;
|
||||
},
|
||||
|
||||
initialize: function (styleSheet, parentActor, window) {
|
||||
protocol.Actor.prototype.initialize.call(this, null);
|
||||
|
||||
this.rawSheet = styleSheet;
|
||||
this.parentActor = parentActor;
|
||||
this.conn = this.parentActor.conn;
|
||||
|
||||
this._window = window;
|
||||
|
||||
// text and index are unknown until source load
|
||||
this.text = null;
|
||||
this._styleSheetIndex = -1;
|
||||
|
||||
this._transitionRefCount = 0;
|
||||
|
||||
// if this sheet has an @import, then it's rules are loaded async
|
||||
let ownerNode = this.rawSheet.ownerNode;
|
||||
if (ownerNode) {
|
||||
let onSheetLoaded = (event) => {
|
||||
ownerNode.removeEventListener("load", onSheetLoaded);
|
||||
this._notifyPropertyChanged("ruleCount");
|
||||
};
|
||||
|
||||
ownerNode.addEventListener("load", onSheetLoaded);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current state of the actor
|
||||
*
|
||||
* @return {object}
|
||||
* With properties of the underlying stylesheet, plus 'text',
|
||||
* 'styleSheetIndex' and 'parentActor' if it's @imported
|
||||
*/
|
||||
form: function (detail) {
|
||||
if (detail === "actorid") {
|
||||
return this.actorID;
|
||||
}
|
||||
|
||||
let docHref;
|
||||
if (this.rawSheet.ownerNode) {
|
||||
if (this.rawSheet.ownerNode instanceof Ci.nsIDOMHTMLDocument) {
|
||||
docHref = this.rawSheet.ownerNode.location.href;
|
||||
}
|
||||
if (this.rawSheet.ownerNode.ownerDocument) {
|
||||
docHref = this.rawSheet.ownerNode.ownerDocument.location.href;
|
||||
}
|
||||
}
|
||||
|
||||
let form = {
|
||||
actor: this.actorID, // actorID is set when this actor is added to a pool
|
||||
href: this.href,
|
||||
nodeHref: docHref,
|
||||
disabled: this.rawSheet.disabled,
|
||||
title: this.rawSheet.title,
|
||||
system: !CssLogic.isContentStylesheet(this.rawSheet),
|
||||
styleSheetIndex: this.styleSheetIndex
|
||||
};
|
||||
|
||||
try {
|
||||
form.ruleCount = this.rawSheet.cssRules.length;
|
||||
} catch (e) {
|
||||
// stylesheet had an @import rule that wasn't loaded yet
|
||||
}
|
||||
return form;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the disabled property of the style sheet
|
||||
*
|
||||
* @return {object}
|
||||
* 'disabled' - the disabled state after toggling.
|
||||
*/
|
||||
toggleDisabled: function () {
|
||||
this.rawSheet.disabled = !this.rawSheet.disabled;
|
||||
this._notifyPropertyChanged("disabled");
|
||||
|
||||
return this.rawSheet.disabled;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send an event notifying that a property of the stylesheet
|
||||
* has changed.
|
||||
*
|
||||
* @param {string} property
|
||||
* Name of the changed property
|
||||
*/
|
||||
_notifyPropertyChanged: function (property) {
|
||||
this.emit("property-change", property, this.form()[property]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the source of the style sheet from its URL. Send a "sourceLoad"
|
||||
* event when it's been fetched.
|
||||
*/
|
||||
fetchSource: function () {
|
||||
this._getText().then((content) => {
|
||||
this.emit("source-load", this.text);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the text for this stylesheet from the cache or network. Return
|
||||
* cached text if it's already been fetched.
|
||||
*
|
||||
* @return {Promise}
|
||||
* Promise that resolves with a string text of the stylesheet.
|
||||
*/
|
||||
_getText: function () {
|
||||
if (this.text) {
|
||||
return promise.resolve(this.text);
|
||||
}
|
||||
|
||||
if (!this.href) {
|
||||
// this is an inline <style> sheet
|
||||
let content = this.rawSheet.ownerNode.textContent;
|
||||
this.text = content;
|
||||
return promise.resolve(content);
|
||||
}
|
||||
|
||||
let options = {
|
||||
policy: Ci.nsIContentPolicy.TYPE_INTERNAL_STYLESHEET,
|
||||
window: this.window,
|
||||
charset: this._getCSSCharset()
|
||||
};
|
||||
|
||||
return fetch(this.href, options).then(({ content }) => {
|
||||
this.text = content;
|
||||
return content;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the charset of the stylesheet according to the character set rules
|
||||
* defined in <http://www.w3.org/TR/CSS2/syndata.html#charset>.
|
||||
* Note that some of the algorithm is implemented in DevToolsUtils.fetch.
|
||||
*/
|
||||
_getCSSCharset: function () {
|
||||
let sheet = this.rawSheet;
|
||||
if (sheet) {
|
||||
// Do we have a @charset rule in the stylesheet?
|
||||
// step 2 of syndata.html (without the BOM check).
|
||||
if (sheet.cssRules) {
|
||||
let rules = sheet.cssRules;
|
||||
if (rules.length
|
||||
&& rules.item(0).type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||
return rules.item(0).encoding;
|
||||
}
|
||||
}
|
||||
|
||||
// step 3: charset attribute of <link> or <style> element, if it exists
|
||||
if (sheet.ownerNode && sheet.ownerNode.getAttribute) {
|
||||
let linkCharset = sheet.ownerNode.getAttribute("charset");
|
||||
if (linkCharset != null) {
|
||||
return linkCharset;
|
||||
}
|
||||
}
|
||||
|
||||
// step 4 (1 of 2): charset of referring stylesheet.
|
||||
let parentSheet = sheet.parentStyleSheet;
|
||||
if (parentSheet && parentSheet.cssRules &&
|
||||
parentSheet.cssRules[0].type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||
return parentSheet.cssRules[0].encoding;
|
||||
}
|
||||
|
||||
// step 4 (2 of 2): charset of referring document.
|
||||
if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) {
|
||||
return sheet.ownerNode.ownerDocument.characterSet;
|
||||
}
|
||||
}
|
||||
|
||||
// step 5: default to utf-8.
|
||||
return "UTF-8";
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the style sheet in place with new text.
|
||||
*
|
||||
* @param {object} request
|
||||
* 'text' - new text
|
||||
* 'transition' - whether to do CSS transition for change.
|
||||
*/
|
||||
update: function (text, transition) {
|
||||
DOMUtils.parseStyleSheet(this.rawSheet, text);
|
||||
|
||||
this.text = text;
|
||||
|
||||
this._notifyPropertyChanged("ruleCount");
|
||||
|
||||
if (transition) {
|
||||
this._insertTransistionRule();
|
||||
} else {
|
||||
this._notifyStyleApplied();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert a catch-all transition rule into the document. Set a timeout
|
||||
* to remove the rule after a certain time.
|
||||
*/
|
||||
_insertTransistionRule: function () {
|
||||
// Insert the global transition rule
|
||||
// Use a ref count to make sure we do not add it multiple times.. and remove
|
||||
// it only when all pending StyleEditor-generated transitions ended.
|
||||
if (this._transitionRefCount == 0) {
|
||||
this.rawSheet.insertRule(TRANSITION_RULE, this.rawSheet.cssRules.length);
|
||||
this.document.documentElement.classList.add(TRANSITION_CLASS);
|
||||
}
|
||||
|
||||
this._transitionRefCount++;
|
||||
|
||||
// Set up clean up and commit after transition duration (+10% buffer)
|
||||
// @see _onTransitionEnd
|
||||
this.window.setTimeout(this._onTransitionEnd.bind(this),
|
||||
Math.floor(TRANSITION_DURATION_MS * 1.1));
|
||||
},
|
||||
|
||||
/**
|
||||
* This cleans up class and rule added for transition effect and then
|
||||
* notifies that the style has been applied.
|
||||
*/
|
||||
_onTransitionEnd: function () {
|
||||
if (--this._transitionRefCount == 0) {
|
||||
this.document.documentElement.classList.remove(TRANSITION_CLASS);
|
||||
this.rawSheet.deleteRule(this.rawSheet.cssRules.length - 1);
|
||||
}
|
||||
|
||||
this.emit("style-applied");
|
||||
}
|
||||
});
|
||||
|
||||
exports.OldStyleSheetActor = OldStyleSheetActor;
|
||||
|
||||
/**
|
||||
* Creates a StyleEditorActor. StyleEditorActor provides remote access to the
|
||||
* stylesheets of a document.
|
||||
*/
|
||||
var StyleEditorActor = protocol.ActorClassWithSpec(styleEditorSpec, {
|
||||
/**
|
||||
* The window we work with, taken from the parent actor.
|
||||
*/
|
||||
get window() {
|
||||
return this.parentActor.window;
|
||||
},
|
||||
|
||||
/**
|
||||
* The current content document of the window we work with.
|
||||
*/
|
||||
get document() {
|
||||
return this.window.document;
|
||||
},
|
||||
|
||||
form: function () {
|
||||
return { actor: this.actorID };
|
||||
},
|
||||
|
||||
initialize: function (conn, tabActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, null);
|
||||
|
||||
this.parentActor = tabActor;
|
||||
|
||||
// keep a map of sheets-to-actors so we don't create two actors for one sheet
|
||||
this._sheets = new Map();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the current StyleEditorActor instance.
|
||||
*/
|
||||
destroy: function () {
|
||||
this._sheets.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by client when target navigates to a new document.
|
||||
* Adds load listeners to document.
|
||||
*/
|
||||
newDocument: function () {
|
||||
// delete previous document's actors
|
||||
this._clearStyleSheetActors();
|
||||
|
||||
// Note: listening for load won't be necessary once
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed
|
||||
if (this.document.readyState == "complete") {
|
||||
this._onDocumentLoaded();
|
||||
} else {
|
||||
this.window.addEventListener("load", this._onDocumentLoaded);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for document loaded event. Add actor for each stylesheet
|
||||
* and send an event notifying of the load
|
||||
*/
|
||||
_onDocumentLoaded: function (event) {
|
||||
if (event) {
|
||||
this.window.removeEventListener("load", this._onDocumentLoaded);
|
||||
}
|
||||
|
||||
let documents = [this.document];
|
||||
let forms = [];
|
||||
for (let doc of documents) {
|
||||
let sheetForms = this._addStyleSheets(doc.styleSheets);
|
||||
forms = forms.concat(sheetForms);
|
||||
// Recursively handle style sheets of the documents in iframes.
|
||||
for (let iframe of doc.getElementsByTagName("iframe")) {
|
||||
documents.push(iframe.contentDocument);
|
||||
}
|
||||
}
|
||||
|
||||
this.emit("document-load", forms);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add all the stylesheets to the map and create an actor for each one
|
||||
* if not already created. Send event that there are new stylesheets.
|
||||
*
|
||||
* @param {[DOMStyleSheet]} styleSheets
|
||||
* Stylesheets to add
|
||||
* @return {[object]}
|
||||
* Array of actors for each StyleSheetActor created
|
||||
*/
|
||||
_addStyleSheets: function (styleSheets) {
|
||||
let sheets = [];
|
||||
for (let i = 0; i < styleSheets.length; i++) {
|
||||
let styleSheet = styleSheets[i];
|
||||
sheets.push(styleSheet);
|
||||
|
||||
// Get all sheets, including imported ones
|
||||
let imports = this._getImported(styleSheet);
|
||||
sheets = sheets.concat(imports);
|
||||
}
|
||||
let actors = sheets.map(this._createStyleSheetActor.bind(this));
|
||||
|
||||
return actors;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new actor for a style sheet, if it hasn't already been created.
|
||||
*
|
||||
* @param {DOMStyleSheet} styleSheet
|
||||
* The style sheet to create an actor for.
|
||||
* @return {StyleSheetActor}
|
||||
* The actor for this style sheet
|
||||
*/
|
||||
_createStyleSheetActor: function (styleSheet) {
|
||||
if (this._sheets.has(styleSheet)) {
|
||||
return this._sheets.get(styleSheet);
|
||||
}
|
||||
let actor = new OldStyleSheetActor(styleSheet, this);
|
||||
|
||||
this.manage(actor);
|
||||
this._sheets.set(styleSheet, actor);
|
||||
|
||||
return actor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all the stylesheets @imported from a stylesheet.
|
||||
*
|
||||
* @param {DOMStyleSheet} styleSheet
|
||||
* Style sheet to search
|
||||
* @return {array}
|
||||
* All the imported stylesheets
|
||||
*/
|
||||
_getImported: function (styleSheet) {
|
||||
let imported = [];
|
||||
|
||||
for (let i = 0; i < styleSheet.cssRules.length; i++) {
|
||||
let rule = styleSheet.cssRules[i];
|
||||
if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
|
||||
// Associated styleSheet may be null if it has already been seen due to
|
||||
// duplicate @imports for the same URL.
|
||||
if (!rule.styleSheet) {
|
||||
continue;
|
||||
}
|
||||
imported.push(rule.styleSheet);
|
||||
|
||||
// recurse imports in this stylesheet as well
|
||||
imported = imported.concat(this._getImported(rule.styleSheet));
|
||||
} else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||
// @import rules must precede all others except @charset
|
||||
break;
|
||||
}
|
||||
}
|
||||
return imported;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all the current stylesheet actors in map.
|
||||
*/
|
||||
_clearStyleSheetActors: function () {
|
||||
for (let actor in this._sheets) {
|
||||
this.unmanage(this._sheets[actor]);
|
||||
}
|
||||
this._sheets.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new style sheet in the document with the given text.
|
||||
* Return an actor for it.
|
||||
*
|
||||
* @param {object} request
|
||||
* Debugging protocol request object, with 'text property'
|
||||
* @return {object}
|
||||
* Object with 'styelSheet' property for form on new actor.
|
||||
*/
|
||||
newStyleSheet: function (text) {
|
||||
let parent = this.document.documentElement;
|
||||
let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style");
|
||||
style.setAttribute("type", "text/css");
|
||||
|
||||
if (text) {
|
||||
style.appendChild(this.document.createTextNode(text));
|
||||
}
|
||||
parent.appendChild(style);
|
||||
|
||||
let actor = this._createStyleSheetActor(style.sheet);
|
||||
return actor;
|
||||
}
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
});
|
||||
|
||||
exports.StyleEditorActor = StyleEditorActor;
|
||||
|
|
@ -3419,7 +3419,6 @@ exports.CSS_PROPERTIES = {
|
|||
"geometricprecision",
|
||||
"grab",
|
||||
"grabbing",
|
||||
"grayscale",
|
||||
"grid",
|
||||
"groove",
|
||||
"groupbox",
|
||||
|
@ -6699,16 +6698,13 @@ exports.CSS_PROPERTIES = {
|
|||
"grid-template-columns",
|
||||
"grid-auto-flow",
|
||||
"grid-auto-rows",
|
||||
"grid-auto-columns",
|
||||
"grid-row-gap",
|
||||
"grid-column-gap"
|
||||
"grid-auto-columns"
|
||||
],
|
||||
"supports": [
|
||||
6,
|
||||
8
|
||||
],
|
||||
"values": [
|
||||
"calc",
|
||||
"column",
|
||||
"dense",
|
||||
"inherit",
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { SimpleStringFront } = require("devtools/shared/fronts/string");
|
||||
const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
|
||||
const {
|
||||
oldStyleSheetSpec,
|
||||
styleEditorSpec
|
||||
} = require("devtools/shared/specs/styleeditor");
|
||||
const promise = require("promise");
|
||||
const defer = require("devtools/shared/defer");
|
||||
|
||||
/**
|
||||
* StyleSheetFront is the client-side counterpart to a StyleSheetActor.
|
||||
*/
|
||||
const OldStyleSheetFront = FrontClassWithSpec(oldStyleSheetSpec, {
|
||||
initialize: function (conn, form, ctx, detail) {
|
||||
Front.prototype.initialize.call(this, conn, form, ctx, detail);
|
||||
|
||||
this._onPropertyChange = this._onPropertyChange.bind(this);
|
||||
this.on("property-change", this._onPropertyChange);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.off("property-change", this._onPropertyChange);
|
||||
|
||||
Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
_onPropertyChange: function (property, value) {
|
||||
this._form[property] = value;
|
||||
},
|
||||
|
||||
form: function (form, detail) {
|
||||
if (detail === "actorid") {
|
||||
this.actorID = form;
|
||||
return;
|
||||
}
|
||||
this.actorID = form.actor;
|
||||
this._form = form;
|
||||
},
|
||||
|
||||
getText: function () {
|
||||
let deferred = defer();
|
||||
|
||||
this.once("source-load", (source) => {
|
||||
let longStr = new SimpleStringFront(source);
|
||||
deferred.resolve(longStr);
|
||||
});
|
||||
this.fetchSource();
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
getOriginalSources: function () {
|
||||
return promise.resolve([]);
|
||||
},
|
||||
|
||||
get href() {
|
||||
return this._form.href;
|
||||
},
|
||||
get nodeHref() {
|
||||
return this._form.nodeHref;
|
||||
},
|
||||
get disabled() {
|
||||
return !!this._form.disabled;
|
||||
},
|
||||
get title() {
|
||||
return this._form.title;
|
||||
},
|
||||
get isSystem() {
|
||||
return this._form.system;
|
||||
},
|
||||
get styleSheetIndex() {
|
||||
return this._form.styleSheetIndex;
|
||||
},
|
||||
get ruleCount() {
|
||||
return this._form.ruleCount;
|
||||
}
|
||||
});
|
||||
|
||||
exports.OldStyleSheetFront = OldStyleSheetFront;
|
||||
|
||||
/**
|
||||
* The corresponding Front object for the StyleEditorActor.
|
||||
*/
|
||||
const StyleEditorFront = FrontClassWithSpec(styleEditorSpec, {
|
||||
initialize: function (client, tabForm) {
|
||||
Front.prototype.initialize.call(this, client);
|
||||
this.actorID = tabForm.styleEditorActor;
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
getStyleSheets: function () {
|
||||
let deferred = defer();
|
||||
|
||||
this.once("document-load", (styleSheets) => {
|
||||
deferred.resolve(styleSheets);
|
||||
});
|
||||
this.newDocument();
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
addStyleSheet: function (text) {
|
||||
return this.newStyleSheet(text);
|
||||
}
|
||||
});
|
||||
|
||||
exports.StyleEditorFront = StyleEditorFront;
|
|
@ -37,7 +37,6 @@ DevToolsModules(
|
|||
'source.js',
|
||||
'storage.js',
|
||||
'string.js',
|
||||
'styleeditor.js',
|
||||
'styles.js',
|
||||
'stylesheets.js',
|
||||
'timeline.js',
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
|
||||
|
||||
const oldStyleSheetSpec = generateActorSpec({
|
||||
typeName: "old-stylesheet",
|
||||
|
||||
events: {
|
||||
"property-change": {
|
||||
type: "propertyChange",
|
||||
property: Arg(0, "string"),
|
||||
value: Arg(1, "json")
|
||||
},
|
||||
"source-load": {
|
||||
type: "sourceLoad",
|
||||
source: Arg(0, "string")
|
||||
},
|
||||
"style-applied": {
|
||||
type: "styleApplied"
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleDisabled: {
|
||||
response: { disabled: RetVal("boolean")}
|
||||
},
|
||||
fetchSource: {},
|
||||
update: {
|
||||
request: {
|
||||
text: Arg(0, "string"),
|
||||
transition: Arg(1, "boolean")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.oldStyleSheetSpec = oldStyleSheetSpec;
|
||||
|
||||
const styleEditorSpec = generateActorSpec({
|
||||
typeName: "styleeditor",
|
||||
|
||||
events: {
|
||||
"document-load": {
|
||||
type: "documentLoad",
|
||||
styleSheets: Arg(0, "array:old-stylesheet")
|
||||
}
|
||||
},
|
||||
|
||||
method: {
|
||||
newDocument: {},
|
||||
newStyleSheet: {
|
||||
request: { text: Arg(0, "string") },
|
||||
response: { styleSheet: RetVal("old-stylesheet") }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.styleEditorSpec = styleEditorSpec;
|
|
@ -13678,7 +13678,8 @@ nsDocShell::EnsureFind()
|
|||
// default to our window
|
||||
nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_QueryInterface(scriptGO);
|
||||
nsCOMPtr<nsPIDOMWindowOuter> windowToSearch;
|
||||
nsFocusManager::GetFocusedDescendant(ourWindow, true,
|
||||
nsFocusManager::GetFocusedDescendant(ourWindow,
|
||||
nsFocusManager::eIncludeAllDescendants,
|
||||
getter_AddRefs(windowToSearch));
|
||||
|
||||
nsCOMPtr<nsIWebBrowserFindInFrames> findInFrames = do_QueryInterface(mFind);
|
||||
|
@ -13993,7 +13994,8 @@ nsDocShell::GetControllerForCommand(const char* aCommand,
|
|||
nsCOMPtr<nsPIWindowRoot> root = mScriptGlobal->GetTopWindowRoot();
|
||||
NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
|
||||
|
||||
return root->GetControllerForCommand(aCommand, aResult);
|
||||
return root->GetControllerForCommand(aCommand, false /* for any window */,
|
||||
aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -2158,7 +2158,9 @@ Element::ShouldBlur(nsIContent *aContent)
|
|||
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedFrame;
|
||||
nsIContent* contentToBlur =
|
||||
nsFocusManager::GetFocusedDescendant(window, false, getter_AddRefs(focusedFrame));
|
||||
nsFocusManager::GetFocusedDescendant(window,
|
||||
nsFocusManager::eOnlyCurrentWindow,
|
||||
getter_AddRefs(focusedFrame));
|
||||
if (contentToBlur == aContent)
|
||||
return true;
|
||||
|
||||
|
|
|
@ -3832,7 +3832,9 @@ Selection::NotifySelectionListeners()
|
|||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
|
||||
nsIContent* focusedContent =
|
||||
fm->GetFocusedDescendant(window, false, getter_AddRefs(focusedWindow));
|
||||
nsFocusManager::GetFocusedDescendant(window,
|
||||
nsFocusManager::eOnlyCurrentWindow,
|
||||
getter_AddRefs(focusedWindow));
|
||||
nsCOMPtr<Element> focusedElement = do_QueryInterface(focusedContent);
|
||||
// When all selected ranges are in an editing host, it should take focus.
|
||||
// But otherwise, we shouldn't move focus since Chromium doesn't move
|
||||
|
|
|
@ -3604,7 +3604,8 @@ nsIDocument::GetActiveElement()
|
|||
if (nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow()) {
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
|
||||
nsIContent* focusedContent =
|
||||
nsFocusManager::GetFocusedDescendant(window, false,
|
||||
nsFocusManager::GetFocusedDescendant(window,
|
||||
nsFocusManager::eOnlyCurrentWindow,
|
||||
getter_AddRefs(focusedWindow));
|
||||
// be safe and make sure the element is from this document
|
||||
if (focusedContent && focusedContent->OwnerDoc() == this) {
|
||||
|
|
|
@ -299,7 +299,8 @@ GetCurrentWindow(nsIContent* aContent)
|
|||
|
||||
// static
|
||||
nsIContent*
|
||||
nsFocusManager::GetFocusedDescendant(nsPIDOMWindowOuter* aWindow, bool aDeep,
|
||||
nsFocusManager::GetFocusedDescendant(nsPIDOMWindowOuter* aWindow,
|
||||
SearchRange aSearchRange,
|
||||
nsPIDOMWindowOuter** aFocusedWindow)
|
||||
{
|
||||
NS_ENSURE_TRUE(aWindow, nullptr);
|
||||
|
@ -308,13 +309,34 @@ nsFocusManager::GetFocusedDescendant(nsPIDOMWindowOuter* aWindow, bool aDeep,
|
|||
|
||||
nsIContent* currentContent = nullptr;
|
||||
nsPIDOMWindowOuter* window = aWindow;
|
||||
while (window) {
|
||||
for (;;) {
|
||||
*aFocusedWindow = window;
|
||||
currentContent = window->GetFocusedNode();
|
||||
if (!currentContent || !aDeep)
|
||||
if (!currentContent || aSearchRange == eOnlyCurrentWindow) {
|
||||
break;
|
||||
}
|
||||
|
||||
window = GetContentWindow(currentContent);
|
||||
if (!window) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (aSearchRange == eIncludeAllDescendants) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aSearchRange == eIncludeVisibleDescendants);
|
||||
|
||||
// If the child window doesn't have PresShell, it means the window is
|
||||
// invisible.
|
||||
nsIDocShell* docShell = window->GetDocShell();
|
||||
if (!docShell) {
|
||||
break;
|
||||
}
|
||||
nsIPresShell* presShell = docShell->GetPresShell();
|
||||
if (!presShell) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IF_ADDREF(*aFocusedWindow);
|
||||
|
@ -628,7 +650,10 @@ nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
|
|||
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
|
||||
nsCOMPtr<nsIContent> focusedContent =
|
||||
GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow));
|
||||
GetFocusedDescendant(window,
|
||||
aDeep ? nsFocusManager::eIncludeAllDescendants :
|
||||
nsFocusManager::eOnlyCurrentWindow,
|
||||
getter_AddRefs(focusedWindow));
|
||||
if (focusedContent)
|
||||
CallQueryInterface(focusedContent, aElement);
|
||||
|
||||
|
@ -738,7 +763,8 @@ nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow)
|
|||
// retrieve the last focused element within the window that was raised
|
||||
nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
|
||||
nsCOMPtr<nsIContent> currentFocus =
|
||||
GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
|
||||
GetFocusedDescendant(window, eIncludeAllDescendants,
|
||||
getter_AddRefs(currentWindow));
|
||||
|
||||
NS_ASSERTION(currentWindow, "window raised with no window current");
|
||||
if (!currentWindow)
|
||||
|
@ -910,7 +936,8 @@ nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus)
|
|||
if (aNeedsFocus) {
|
||||
nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
|
||||
nsCOMPtr<nsIContent> currentFocus =
|
||||
GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
|
||||
GetFocusedDescendant(window, eIncludeAllDescendants,
|
||||
getter_AddRefs(currentWindow));
|
||||
if (currentWindow)
|
||||
Focus(currentWindow, currentFocus, 0, true, false, false, true);
|
||||
}
|
||||
|
@ -1213,7 +1240,8 @@ nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
|
|||
nsCOMPtr<nsPIDOMWindowOuter> newWindow;
|
||||
nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(contentToFocus);
|
||||
if (subWindow) {
|
||||
contentToFocus = GetFocusedDescendant(subWindow, true, getter_AddRefs(newWindow));
|
||||
contentToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants,
|
||||
getter_AddRefs(newWindow));
|
||||
// since a window is being refocused, clear aFocusChanged so that the
|
||||
// caret position isn't updated.
|
||||
aFocusChanged = false;
|
||||
|
@ -2290,7 +2318,8 @@ nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow)
|
|||
// But on other platforms, we can just focus the toplevel widget to raise
|
||||
// the window.
|
||||
nsCOMPtr<nsPIDOMWindowOuter> childWindow;
|
||||
GetFocusedDescendant(aWindow, true, getter_AddRefs(childWindow));
|
||||
GetFocusedDescendant(aWindow, eIncludeAllDescendants,
|
||||
getter_AddRefs(childWindow));
|
||||
if (!childWindow)
|
||||
childWindow = aWindow;
|
||||
|
||||
|
@ -2654,7 +2683,8 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
|
|||
// When moving between documents, make sure to get the right
|
||||
// starting content in a descendant.
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
|
||||
startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow));
|
||||
startContent = GetFocusedDescendant(aWindow, eIncludeAllDescendants,
|
||||
getter_AddRefs(focusedWindow));
|
||||
}
|
||||
else if (aType != MOVEFOCUS_LASTDOC) {
|
||||
// Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
|
||||
|
|
|
@ -134,7 +134,17 @@ public:
|
|||
*
|
||||
* aWindow and aFocusedWindow must both be non-null.
|
||||
*/
|
||||
static nsIContent* GetFocusedDescendant(nsPIDOMWindowOuter* aWindow, bool aDeep,
|
||||
enum SearchRange
|
||||
{
|
||||
// Return focused content in aWindow. So, aFocusedWindow is always aWindow.
|
||||
eOnlyCurrentWindow,
|
||||
// Return focused content in aWindow or one of all sub windows.
|
||||
eIncludeAllDescendants,
|
||||
// Return focused content in aWindow or one of visible sub windows.
|
||||
eIncludeVisibleDescendants,
|
||||
};
|
||||
static nsIContent* GetFocusedDescendant(nsPIDOMWindowOuter* aWindow,
|
||||
SearchRange aSearchRange,
|
||||
nsPIDOMWindowOuter** aFocusedWindow);
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,9 +36,20 @@ public:
|
|||
virtual nsIDOMNode* GetPopupNode() = 0;
|
||||
virtual void SetPopupNode(nsIDOMNode* aNode) = 0;
|
||||
|
||||
/**
|
||||
* @param aForVisibleWindow true if caller needs controller which is
|
||||
* associated with visible window.
|
||||
*/
|
||||
virtual nsresult GetControllerForCommand(const char *aCommand,
|
||||
bool aForVisibleWindow,
|
||||
nsIController** aResult) = 0;
|
||||
virtual nsresult GetControllers(nsIControllers** aResult) = 0;
|
||||
|
||||
/**
|
||||
* @param aForVisibleWindow true if caller needs controllers which are
|
||||
* associated with visible window.
|
||||
*/
|
||||
virtual nsresult GetControllers(bool aForVisibleWindow,
|
||||
nsIControllers** aResult) = 0;
|
||||
|
||||
virtual void GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
|
||||
nsTArray<nsCString>& aDisabledCommands) = 0;
|
||||
|
|
|
@ -207,7 +207,8 @@ nsWindowRoot::GetWindow()
|
|||
}
|
||||
|
||||
nsresult
|
||||
nsWindowRoot::GetControllers(nsIControllers** aResult)
|
||||
nsWindowRoot::GetControllers(bool aForVisibleWindow,
|
||||
nsIControllers** aResult)
|
||||
{
|
||||
*aResult = nullptr;
|
||||
|
||||
|
@ -215,9 +216,13 @@ nsWindowRoot::GetControllers(nsIControllers** aResult)
|
|||
// describes controllers, so this code would have no special
|
||||
// knowledge of what object might have controllers.
|
||||
|
||||
nsFocusManager::SearchRange searchRange =
|
||||
aForVisibleWindow ? nsFocusManager::eIncludeVisibleDescendants :
|
||||
nsFocusManager::eIncludeAllDescendants;
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
|
||||
nsIContent* focusedContent =
|
||||
nsFocusManager::GetFocusedDescendant(mWindow, true, getter_AddRefs(focusedWindow));
|
||||
nsFocusManager::GetFocusedDescendant(mWindow, searchRange,
|
||||
getter_AddRefs(focusedWindow));
|
||||
if (focusedContent) {
|
||||
#ifdef MOZ_XUL
|
||||
RefPtr<nsXULElement> xulElement = nsXULElement::FromContent(focusedContent);
|
||||
|
@ -250,7 +255,8 @@ nsWindowRoot::GetControllers(nsIControllers** aResult)
|
|||
}
|
||||
|
||||
nsresult
|
||||
nsWindowRoot::GetControllerForCommand(const char * aCommand,
|
||||
nsWindowRoot::GetControllerForCommand(const char* aCommand,
|
||||
bool aForVisibleWindow,
|
||||
nsIController** _retval)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(_retval);
|
||||
|
@ -258,7 +264,7 @@ nsWindowRoot::GetControllerForCommand(const char * aCommand,
|
|||
|
||||
{
|
||||
nsCOMPtr<nsIControllers> controllers;
|
||||
GetControllers(getter_AddRefs(controllers));
|
||||
GetControllers(aForVisibleWindow, getter_AddRefs(controllers));
|
||||
if (controllers) {
|
||||
nsCOMPtr<nsIController> controller;
|
||||
controllers->GetControllerForCommand(aCommand, getter_AddRefs(controller));
|
||||
|
@ -269,8 +275,12 @@ nsWindowRoot::GetControllerForCommand(const char * aCommand,
|
|||
}
|
||||
}
|
||||
|
||||
nsFocusManager::SearchRange searchRange =
|
||||
aForVisibleWindow ? nsFocusManager::eIncludeVisibleDescendants :
|
||||
nsFocusManager::eIncludeAllDescendants;
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
|
||||
nsFocusManager::GetFocusedDescendant(mWindow, true, getter_AddRefs(focusedWindow));
|
||||
nsFocusManager::GetFocusedDescendant(mWindow, searchRange,
|
||||
getter_AddRefs(focusedWindow));
|
||||
while (focusedWindow) {
|
||||
nsCOMPtr<nsIControllers> controllers;
|
||||
focusedWindow->GetControllers(getter_AddRefs(controllers));
|
||||
|
@ -340,14 +350,16 @@ nsWindowRoot::GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
|
|||
nsTHashtable<nsCharPtrHashKey> commandsHandled;
|
||||
|
||||
nsCOMPtr<nsIControllers> controllers;
|
||||
GetControllers(getter_AddRefs(controllers));
|
||||
GetControllers(false, getter_AddRefs(controllers));
|
||||
if (controllers) {
|
||||
GetEnabledDisabledCommandsForControllers(controllers, commandsHandled,
|
||||
aEnabledCommands, aDisabledCommands);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
|
||||
nsFocusManager::GetFocusedDescendant(mWindow, true, getter_AddRefs(focusedWindow));
|
||||
nsFocusManager::GetFocusedDescendant(mWindow,
|
||||
nsFocusManager::eIncludeAllDescendants,
|
||||
getter_AddRefs(focusedWindow));
|
||||
while (focusedWindow) {
|
||||
focusedWindow->GetControllers(getter_AddRefs(controllers));
|
||||
if (controllers) {
|
||||
|
|
|
@ -43,8 +43,10 @@ public:
|
|||
|
||||
virtual nsPIDOMWindowOuter* GetWindow() override;
|
||||
|
||||
virtual nsresult GetControllers(nsIControllers** aResult) override;
|
||||
virtual nsresult GetControllers(bool aForVisibleWindow,
|
||||
nsIControllers** aResult) override;
|
||||
virtual nsresult GetControllerForCommand(const char * aCommand,
|
||||
bool aForVisibleWindow,
|
||||
nsIController** _retval) override;
|
||||
|
||||
virtual void GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
|
||||
|
|
|
@ -2084,7 +2084,17 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
|
|||
rv = state->GetIsNull(6, &nullPadding);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
bool shouldUpdateTo26 = false;
|
||||
if (nullPadding && aSavedResponseOut->mValue.type() == ResponseType::Opaque) {
|
||||
// XXXtt: This should be removed in the future (e.g. Nightly 58) by
|
||||
// bug 1398167.
|
||||
shouldUpdateTo26 = true;
|
||||
aSavedResponseOut->mValue.paddingSize() = 0;
|
||||
} else if (nullPadding) {
|
||||
#else
|
||||
if (nullPadding) {
|
||||
#endif // NIGHTLY_BUILD
|
||||
MOZ_DIAGNOSTIC_ASSERT(aSavedResponseOut->mValue.type() !=
|
||||
ResponseType::Opaque);
|
||||
aSavedResponseOut->mValue.paddingSize() =
|
||||
|
@ -2103,6 +2113,20 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
|
|||
rv = state->GetBlobAsUTF8String(7, aSavedResponseOut->mValue.channelInfo().securityInfo());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
if (shouldUpdateTo26) {
|
||||
// XXXtt: This is a quick fix for not updating properly in Nightly 57.
|
||||
// Note: This should be removed in the future (e.g. Nightly 58) by
|
||||
// bug 1398167.
|
||||
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"UPDATE entries SET response_padding_size = 0 "
|
||||
"WHERE response_type = 4 " // opaque response
|
||||
"AND response_padding_size IS NULL"
|
||||
));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
}
|
||||
#endif // NIGHTLY_BUILD
|
||||
|
||||
rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT "
|
||||
"name, "
|
||||
|
@ -3161,8 +3185,8 @@ nsresult MigrateFrom25To26(mozIStorageConnection* aConn, bool& aRewriteSchema)
|
|||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"UPDATE entries SET response_padding_size = 0"
|
||||
"WHERE response_type = 4" // opaque response
|
||||
"UPDATE entries SET response_padding_size = 0 "
|
||||
"WHERE response_type = 4" // opaque response
|
||||
));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
|
|
Двоичный файл не отображается.
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* The schema_25_profile.zip are made from local Nightly by following step
|
||||
* 1. Go to any website
|
||||
* 2. Open web console and type
|
||||
* caches.open("test")
|
||||
* .then(c => fetch("https://www.mozilla.org", {mode:"no-cors"})
|
||||
* .then(r => c.put("https://www.mozilla.org", r)));
|
||||
* 3. Go to profile directory and rename the website folder to "chrome"
|
||||
*/
|
||||
|
||||
async function run_test() {
|
||||
do_test_pending();
|
||||
create_test_profile('schema_25_profile.zip');
|
||||
|
||||
let cache = await caches.open("test");
|
||||
let response = await cache.match("https://www.mozilla.org");
|
||||
ok(!!response, "Upgrade from 25 to 26 do succeed");
|
||||
ok(response.type === 'opaque', "The response type does be opaque");
|
||||
|
||||
do_test_finished();
|
||||
}
|
|
@ -6,9 +6,11 @@
|
|||
head = head.js
|
||||
support-files =
|
||||
schema_15_profile.zip
|
||||
schema_25_profile.zip
|
||||
|
||||
# dummy test entry to generate profile zip files
|
||||
[make_profile.js]
|
||||
skip-if = true
|
||||
|
||||
[test_migration.js]
|
||||
[test_schema_26_upgrade.js]
|
||||
|
|
|
@ -99,7 +99,6 @@ skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests
|
|||
skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
|
||||
[test_fuzzing_bugs.html]
|
||||
[test_video_fastpath_mp4.html]
|
||||
fail-if = (os == 'win' && os_version == '6.1' && !e10s)
|
||||
[test_video_fastpath_theora.html]
|
||||
[test_video_fastpath_vp8.html]
|
||||
[test_video_fastpath_vp9.html]
|
||||
|
|
|
@ -256,5 +256,6 @@ nsCommandManager::GetControllerForCommand(const char* aCommand,
|
|||
NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
|
||||
|
||||
// no target window; send command to focus controller
|
||||
return root->GetControllerForCommand(aCommand, aResult);
|
||||
return root->GetControllerForCommand(aCommand, false /* for any window */,
|
||||
aResult);
|
||||
}
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче