Merge fx-team to central, a=merge CLOSED TREE
@ -454,7 +454,6 @@ var gExtensionCount = 0;
|
||||
/* eslint-disable mozilla/balanced-listeners */
|
||||
extensions.on("startup", (type, extension) => {
|
||||
gContextMenuMap.set(extension, new Map());
|
||||
gRootItems.delete(extension);
|
||||
if (++gExtensionCount == 1) {
|
||||
Services.obs.addObserver(contextMenuObserver,
|
||||
"on-build-contextmenu",
|
||||
@ -464,6 +463,7 @@ extensions.on("startup", (type, extension) => {
|
||||
|
||||
extensions.on("shutdown", (type, extension) => {
|
||||
gContextMenuMap.delete(extension);
|
||||
gRootItems.delete(extension);
|
||||
if (--gExtensionCount == 0) {
|
||||
Services.obs.removeObserver(contextMenuObserver,
|
||||
"on-build-contextmenu");
|
||||
|
@ -19,8 +19,12 @@
|
||||
|
||||
"clickBrowserAction": true,
|
||||
"clickPageAction": true,
|
||||
"CustomizableUI": true,
|
||||
"closeContextMenu": true,
|
||||
"closeExtensionContextMenu": true,
|
||||
"focusWindow": true,
|
||||
"makeWidgetId": true,
|
||||
"openContextMenu": true,
|
||||
"openExtensionContextMenu": true,
|
||||
"CustomizableUI": true,
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,10 @@ support-files =
|
||||
[browser_ext_commands_onCommand.js]
|
||||
[browser_ext_contentscript_connect.js]
|
||||
[browser_ext_contextMenus.js]
|
||||
[browser_ext_contextMenus_checkboxes.js]
|
||||
[browser_ext_contextMenus_icons.js]
|
||||
[browser_ext_contextMenus_radioGroups.js]
|
||||
[browser_ext_contextMenus_uninstall.js]
|
||||
[browser_ext_currentWindow.js]
|
||||
[browser_ext_getViews.js]
|
||||
[browser_ext_history.js]
|
||||
|
@ -27,36 +27,15 @@ add_task(function* () {
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish();
|
||||
|
||||
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#img1", {
|
||||
type: "contextmenu",
|
||||
button: 2,
|
||||
}, gBrowser.selectedBrowser);
|
||||
yield popupShownPromise;
|
||||
|
||||
let contentAreaContextMenu = yield openContextMenu("#img1");
|
||||
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
|
||||
is(item.length, 1, "contextMenu item for image was found");
|
||||
yield closeContextMenu();
|
||||
|
||||
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
|
||||
EventUtils.synthesizeMouseAtCenter(item[0], {});
|
||||
yield popupHiddenPromise;
|
||||
|
||||
contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
|
||||
type: "contextmenu",
|
||||
button: 2,
|
||||
}, gBrowser.selectedBrowser);
|
||||
yield popupShownPromise;
|
||||
|
||||
contentAreaContextMenu = yield openContextMenu("body");
|
||||
item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
|
||||
is(item.length, 0, "no contextMenu item for image was found");
|
||||
|
||||
// click something to close the context menu
|
||||
popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
|
||||
EventUtils.synthesizeMouseAtCenter(document.getElementById("context-selectall"), {});
|
||||
yield popupHiddenPromise;
|
||||
yield closeContextMenu();
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
@ -141,48 +120,6 @@ add_task(function* () {
|
||||
});
|
||||
browser.contextMenus.remove(parentToDel);
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "radio-group-1",
|
||||
type: "radio",
|
||||
checked: true,
|
||||
onclick: genericOnClick,
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "Checkbox",
|
||||
type: "checkbox",
|
||||
onclick: genericOnClick,
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "radio-group-2",
|
||||
type: "radio",
|
||||
onclick: genericOnClick,
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "radio-group-2",
|
||||
type: "radio",
|
||||
onclick: genericOnClick,
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
type: "separator",
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "Checkbox",
|
||||
type: "checkbox",
|
||||
checked: true,
|
||||
onclick: genericOnClick,
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "Checkbox",
|
||||
type: "checkbox",
|
||||
onclick: genericOnClick,
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "Without onclick property",
|
||||
id: "ext-without-onclick",
|
||||
@ -190,159 +127,58 @@ add_task(function* () {
|
||||
|
||||
browser.contextMenus.update(parent, {parentId: child2}).then(
|
||||
() => {
|
||||
browser.test.notifyFail();
|
||||
browser.test.notifyFail("contextmenus");
|
||||
},
|
||||
() => {
|
||||
browser.test.notifyPass();
|
||||
browser.test.notifyPass("contextmenus");
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish();
|
||||
yield extension.awaitFinish("contextmenus");
|
||||
|
||||
let contentAreaContextMenu;
|
||||
let expectedClickInfo = {
|
||||
menuItemId: "ext-image",
|
||||
mediaType: "image",
|
||||
srcUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/ctxmenu-image.png",
|
||||
pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
|
||||
};
|
||||
|
||||
function getTop() {
|
||||
contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
|
||||
is(items.length, 1, "top level item was found (context=selection)");
|
||||
let topItem = items[0];
|
||||
return topItem.childNodes[0];
|
||||
}
|
||||
|
||||
function* openExtensionMenu() {
|
||||
contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#img1", {
|
||||
type: "contextmenu",
|
||||
button: 2,
|
||||
}, gBrowser.selectedBrowser);
|
||||
yield popupShownPromise;
|
||||
|
||||
popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(getTop(), {});
|
||||
yield popupShownPromise;
|
||||
}
|
||||
|
||||
function* closeContextMenu(itemToSelect, expectedClickInfo, hasOnclickProperty = true) {
|
||||
function checkClickInfo(info, tab) {
|
||||
for (let i of Object.keys(expectedClickInfo)) {
|
||||
is(info[i], expectedClickInfo[i],
|
||||
"click info " + i + " expected to be: " + expectedClickInfo[i] + " but was: " + info[i]);
|
||||
}
|
||||
is(expectedClickInfo.pageSrc, tab.url);
|
||||
function checkClickInfo(result) {
|
||||
for (let i of Object.keys(expectedClickInfo)) {
|
||||
is(result.info[i], expectedClickInfo[i],
|
||||
"click info " + i + " expected to be: " + expectedClickInfo[i] + " but was: " + info[i]);
|
||||
}
|
||||
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
|
||||
EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
|
||||
|
||||
if (hasOnclickProperty) {
|
||||
let {info, tab} = yield extension.awaitMessage("onclick");
|
||||
if (expectedClickInfo) {
|
||||
checkClickInfo(info, tab);
|
||||
}
|
||||
}
|
||||
|
||||
let {info, tab} = yield extension.awaitMessage("browser.contextMenus.onClicked");
|
||||
if (expectedClickInfo) {
|
||||
checkClickInfo(info, tab);
|
||||
}
|
||||
|
||||
yield popupHiddenPromise;
|
||||
is(expectedClickInfo.pageSrc, result.tab.url);
|
||||
}
|
||||
|
||||
function confirmRadioGroupStates(expectedStates) {
|
||||
let top = getTop();
|
||||
|
||||
let radioItems = top.getElementsByAttribute("type", "radio");
|
||||
let radioGroup1 = top.getElementsByAttribute("label", "radio-group-1");
|
||||
let radioGroup2 = top.getElementsByAttribute("label", "radio-group-2");
|
||||
|
||||
is(radioItems.length, 3, "there should be 3 radio items in the context menu");
|
||||
is(radioGroup1.length, 1, "the first radio group should only have 1 radio item");
|
||||
is(radioGroup2.length, 2, "the second radio group should only have 2 radio items");
|
||||
|
||||
is(radioGroup1[0].hasAttribute("checked"), expectedStates[0], `radio item 1 has state (checked=${expectedStates[0]})`);
|
||||
is(radioGroup2[0].hasAttribute("checked"), expectedStates[1], `radio item 2 has state (checked=${expectedStates[1]})`);
|
||||
is(radioGroup2[1].hasAttribute("checked"), expectedStates[2], `radio item 3 has state (checked=${expectedStates[2]})`);
|
||||
}
|
||||
|
||||
function confirmCheckboxStates(expectedStates) {
|
||||
let checkboxItems = getTop().getElementsByAttribute("type", "checkbox");
|
||||
|
||||
is(checkboxItems.length, 3, "there should be 3 checkbox items in the context menu");
|
||||
|
||||
is(checkboxItems[0].hasAttribute("checked"), expectedStates[0], `checkbox item 1 has state (checked=${expectedStates[0]})`);
|
||||
is(checkboxItems[1].hasAttribute("checked"), expectedStates[1], `checkbox item 2 has state (checked=${expectedStates[1]})`);
|
||||
is(checkboxItems[2].hasAttribute("checked"), expectedStates[2], `checkbox item 3 has state (checked=${expectedStates[2]})`);
|
||||
}
|
||||
|
||||
yield openExtensionMenu();
|
||||
let extensionMenuRoot = yield openExtensionContextMenu();
|
||||
|
||||
// Check some menu items
|
||||
let top = getTop();
|
||||
let items = top.getElementsByAttribute("label", "image");
|
||||
let items = extensionMenuRoot.getElementsByAttribute("label", "image");
|
||||
is(items.length, 1, "contextMenu item for image was found (context=image)");
|
||||
let image = items[0];
|
||||
|
||||
items = top.getElementsByAttribute("label", "selection-edited");
|
||||
items = extensionMenuRoot.getElementsByAttribute("label", "selection-edited");
|
||||
is(items.length, 0, "contextMenu item for selection was not found (context=image)");
|
||||
|
||||
items = top.getElementsByAttribute("label", "parentToDel");
|
||||
items = extensionMenuRoot.getElementsByAttribute("label", "parentToDel");
|
||||
is(items.length, 0, "contextMenu item for removed parent was not found (context=image)");
|
||||
|
||||
items = top.getElementsByAttribute("label", "parent");
|
||||
items = extensionMenuRoot.getElementsByAttribute("label", "parent");
|
||||
is(items.length, 1, "contextMenu item for parent was found (context=image)");
|
||||
|
||||
is(items[0].childNodes[0].childNodes.length, 2, "child items for parent were found (context=image)");
|
||||
|
||||
// Click on ext-image item and check the click results
|
||||
yield closeContextMenu(image, {
|
||||
menuItemId: "ext-image",
|
||||
mediaType: "image",
|
||||
srcUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/ctxmenu-image.png",
|
||||
pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
|
||||
});
|
||||
yield closeExtensionContextMenu(image);
|
||||
|
||||
// Test radio groups
|
||||
yield openExtensionMenu();
|
||||
confirmRadioGroupStates([true, false, false]);
|
||||
items = getTop().getElementsByAttribute("type", "radio");
|
||||
yield closeContextMenu(items[1]);
|
||||
|
||||
yield openExtensionMenu();
|
||||
confirmRadioGroupStates([true, true, false]);
|
||||
items = getTop().getElementsByAttribute("type", "radio");
|
||||
yield closeContextMenu(items[2]);
|
||||
|
||||
yield openExtensionMenu();
|
||||
confirmRadioGroupStates([true, false, true]);
|
||||
items = getTop().getElementsByAttribute("type", "radio");
|
||||
yield closeContextMenu(items[0]);
|
||||
|
||||
yield openExtensionMenu();
|
||||
confirmRadioGroupStates([true, false, true]);
|
||||
|
||||
// Test checkboxes
|
||||
items = getTop().getElementsByAttribute("type", "checkbox");
|
||||
confirmCheckboxStates([false, true, false]);
|
||||
yield closeContextMenu(items[0]);
|
||||
|
||||
yield openExtensionMenu();
|
||||
confirmCheckboxStates([true, true, false]);
|
||||
items = getTop().getElementsByAttribute("type", "checkbox");
|
||||
yield closeContextMenu(items[2]);
|
||||
|
||||
yield openExtensionMenu();
|
||||
confirmCheckboxStates([true, true, true]);
|
||||
items = getTop().getElementsByAttribute("type", "checkbox");
|
||||
yield closeContextMenu(items[0]);
|
||||
|
||||
yield openExtensionMenu();
|
||||
confirmCheckboxStates([false, true, true]);
|
||||
items = getTop().getElementsByAttribute("type", "checkbox");
|
||||
yield closeContextMenu(items[2]);
|
||||
let result = yield extension.awaitMessage("onclick");
|
||||
checkClickInfo(result);
|
||||
result = yield extension.awaitMessage("browser.contextMenus.onClicked");
|
||||
checkClickInfo(result);
|
||||
|
||||
// Select some text
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
|
||||
@ -357,40 +193,51 @@ add_task(function* () {
|
||||
});
|
||||
|
||||
// Bring up context menu again
|
||||
yield openExtensionMenu();
|
||||
extensionMenuRoot = yield openExtensionContextMenu();
|
||||
|
||||
// Check some menu items
|
||||
top = getTop();
|
||||
items = top.getElementsByAttribute("label", "Without onclick property");
|
||||
items = extensionMenuRoot.getElementsByAttribute("label", "Without onclick property");
|
||||
is(items.length, 1, "contextMenu item was found (context=page)");
|
||||
|
||||
yield closeContextMenu(items[0], {
|
||||
yield closeExtensionContextMenu(items[0]);
|
||||
|
||||
expectedClickInfo = {
|
||||
menuItemId: "ext-without-onclick",
|
||||
pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
|
||||
}, false /* hasOnclickProperty */);
|
||||
};
|
||||
|
||||
result = yield extension.awaitMessage("browser.contextMenus.onClicked");
|
||||
checkClickInfo(result);
|
||||
|
||||
// Bring up context menu again
|
||||
yield openExtensionMenu();
|
||||
extensionMenuRoot = yield openExtensionContextMenu();
|
||||
|
||||
// Check some menu items
|
||||
top = getTop();
|
||||
items = top.getElementsByAttribute("label", "selection is: 'just some text 123456789012345678901234567890...'");
|
||||
items = extensionMenuRoot.getElementsByAttribute("label", "selection is: 'just some text 123456789012345678901234567890...'");
|
||||
is(items.length, 1, "contextMenu item for selection was found (context=selection)");
|
||||
let selectionItem = items[0];
|
||||
|
||||
items = top.getElementsByAttribute("label", "selection");
|
||||
items = extensionMenuRoot.getElementsByAttribute("label", "selection");
|
||||
is(items.length, 0, "contextMenu item label update worked (context=selection)");
|
||||
|
||||
yield closeContextMenu(selectionItem, {
|
||||
yield closeExtensionContextMenu(selectionItem);
|
||||
|
||||
expectedClickInfo = {
|
||||
menuItemId: "ext-selection",
|
||||
pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
|
||||
selectionText: "just some text 1234567890123456789012345678901234567890123456789012345678901234567890123456789012",
|
||||
});
|
||||
};
|
||||
|
||||
result = yield extension.awaitMessage("onclick");
|
||||
checkClickInfo(result);
|
||||
result = yield extension.awaitMessage("browser.contextMenus.onClicked");
|
||||
checkClickInfo(result);
|
||||
|
||||
let contentAreaContextMenu = yield openContextMenu("#img1");
|
||||
items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
|
||||
is(items.length, 0, "top level item was not found (after removeAll()");
|
||||
yield closeContextMenu();
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab1);
|
||||
});
|
||||
|
@ -0,0 +1,74 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
|
||||
"http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
|
||||
|
||||
gBrowser.selectedTab = tab1;
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["contextMenus"],
|
||||
},
|
||||
|
||||
background: function() {
|
||||
browser.contextMenus.create({
|
||||
title: "Checkbox",
|
||||
type: "checkbox",
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
type: "separator",
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "Checkbox",
|
||||
type: "checkbox",
|
||||
checked: true,
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "Checkbox",
|
||||
type: "checkbox",
|
||||
});
|
||||
|
||||
browser.test.notifyPass("contextmenus-checkboxes");
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contextmenus-checkboxes");
|
||||
|
||||
function confirmCheckboxStates(extensionMenuRoot, expectedStates) {
|
||||
let checkboxItems = extensionMenuRoot.getElementsByAttribute("type", "checkbox");
|
||||
|
||||
is(checkboxItems.length, 3, "there should be 3 checkbox items in the context menu");
|
||||
|
||||
is(checkboxItems[0].hasAttribute("checked"), expectedStates[0], `checkbox item 1 has state (checked=${expectedStates[0]})`);
|
||||
is(checkboxItems[1].hasAttribute("checked"), expectedStates[1], `checkbox item 2 has state (checked=${expectedStates[1]})`);
|
||||
is(checkboxItems[2].hasAttribute("checked"), expectedStates[2], `checkbox item 3 has state (checked=${expectedStates[2]})`);
|
||||
|
||||
return extensionMenuRoot.getElementsByAttribute("type", "checkbox");
|
||||
}
|
||||
|
||||
let extensionMenuRoot = yield openExtensionContextMenu();
|
||||
let items = confirmCheckboxStates(extensionMenuRoot, [false, true, false]);
|
||||
yield closeExtensionContextMenu(items[0]);
|
||||
|
||||
extensionMenuRoot = yield openExtensionContextMenu();
|
||||
items = confirmCheckboxStates(extensionMenuRoot, [true, true, false]);
|
||||
yield closeExtensionContextMenu(items[2]);
|
||||
|
||||
extensionMenuRoot = yield openExtensionContextMenu();
|
||||
items = confirmCheckboxStates(extensionMenuRoot, [true, true, true]);
|
||||
yield closeExtensionContextMenu(items[0]);
|
||||
|
||||
extensionMenuRoot = yield openExtensionContextMenu();
|
||||
items = confirmCheckboxStates(extensionMenuRoot, [false, true, true]);
|
||||
yield closeExtensionContextMenu(items[2]);
|
||||
|
||||
yield extension.unload();
|
||||
yield BrowserTestUtils.removeTab(tab1);
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
|
||||
"http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
|
||||
|
||||
let encodedImageData = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
|
||||
let decodedImageData = atob(encodedImageData);
|
||||
const IMAGE_ARRAYBUFFER = Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["contextMenus"],
|
||||
"icons": {
|
||||
"18": "extension.png",
|
||||
},
|
||||
},
|
||||
|
||||
files: {
|
||||
"extension.png": IMAGE_ARRAYBUFFER,
|
||||
},
|
||||
|
||||
background: function() {
|
||||
let menuitemId = browser.contextMenus.create({
|
||||
title: "child-to-delete",
|
||||
onclick: () => {
|
||||
browser.contextMenus.remove(menuitemId);
|
||||
},
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "child",
|
||||
});
|
||||
|
||||
browser.test.notifyPass("contextmenus-icons");
|
||||
},
|
||||
});
|
||||
|
||||
let confirmContextMenuIcon = (rootElement) => {
|
||||
let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/extension\.png$`);
|
||||
let imageUrl = rootElement.getAttribute("image");
|
||||
ok(expectedURL.test(imageUrl), "The context menu should display the extension icon next to the root element");
|
||||
};
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contextmenus-icons");
|
||||
|
||||
let extensionMenu = yield openExtensionContextMenu();
|
||||
|
||||
let contextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let topLevelMenuItem = contextMenu.getElementsByAttribute("ext-type", "top-level-menu")[0];
|
||||
confirmContextMenuIcon(topLevelMenuItem);
|
||||
|
||||
let childToDelete = extensionMenu.getElementsByAttribute("label", "child-to-delete")[0];
|
||||
yield closeExtensionContextMenu(childToDelete);
|
||||
|
||||
yield openExtensionContextMenu();
|
||||
|
||||
contextMenu = document.getElementById("contentAreaContextMenu");
|
||||
topLevelMenuItem = contextMenu.getElementsByAttribute("label", "child")[0];
|
||||
|
||||
confirmContextMenuIcon(topLevelMenuItem);
|
||||
yield closeContextMenu();
|
||||
|
||||
yield extension.unload();
|
||||
yield BrowserTestUtils.removeTab(tab1);
|
||||
});
|
@ -0,0 +1,78 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
|
||||
"http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
|
||||
|
||||
gBrowser.selectedTab = tab1;
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["contextMenus"],
|
||||
},
|
||||
|
||||
background: function() {
|
||||
browser.contextMenus.create({
|
||||
title: "radio-group-1",
|
||||
type: "radio",
|
||||
checked: true,
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
type: "separator",
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "radio-group-2",
|
||||
type: "radio",
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
title: "radio-group-2",
|
||||
type: "radio",
|
||||
});
|
||||
|
||||
browser.test.notifyPass("contextmenus-radio-groups");
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contextmenus-radio-groups");
|
||||
|
||||
function confirmRadioGroupStates(extensionMenuRoot, expectedStates) {
|
||||
let radioItems = extensionMenuRoot.getElementsByAttribute("type", "radio");
|
||||
let radioGroup1 = extensionMenuRoot.getElementsByAttribute("label", "radio-group-1");
|
||||
let radioGroup2 = extensionMenuRoot.getElementsByAttribute("label", "radio-group-2");
|
||||
|
||||
is(radioItems.length, 3, "there should be 3 radio items in the context menu");
|
||||
is(radioGroup1.length, 1, "the first radio group should only have 1 radio item");
|
||||
is(radioGroup2.length, 2, "the second radio group should only have 2 radio items");
|
||||
|
||||
is(radioGroup1[0].hasAttribute("checked"), expectedStates[0], `radio item 1 has state (checked=${expectedStates[0]})`);
|
||||
is(radioGroup2[0].hasAttribute("checked"), expectedStates[1], `radio item 2 has state (checked=${expectedStates[1]})`);
|
||||
is(radioGroup2[1].hasAttribute("checked"), expectedStates[2], `radio item 3 has state (checked=${expectedStates[2]})`);
|
||||
|
||||
return extensionMenuRoot.getElementsByAttribute("type", "radio");
|
||||
}
|
||||
|
||||
let extensionMenuRoot = yield openExtensionContextMenu();
|
||||
let items = confirmRadioGroupStates(extensionMenuRoot, [true, false, false]);
|
||||
yield closeExtensionContextMenu(items[1]);
|
||||
|
||||
extensionMenuRoot = yield openExtensionContextMenu();
|
||||
items = confirmRadioGroupStates(extensionMenuRoot, [true, true, false]);
|
||||
yield closeExtensionContextMenu(items[2]);
|
||||
|
||||
extensionMenuRoot = yield openExtensionContextMenu();
|
||||
items = confirmRadioGroupStates(extensionMenuRoot, [true, false, true]);
|
||||
yield closeExtensionContextMenu(items[0]);
|
||||
|
||||
extensionMenuRoot = yield openExtensionContextMenu();
|
||||
items = confirmRadioGroupStates(extensionMenuRoot, [true, false, true]);
|
||||
yield closeExtensionContextMenu(items[0]);
|
||||
|
||||
yield extension.unload();
|
||||
yield BrowserTestUtils.removeTab(tab1);
|
||||
});
|
@ -0,0 +1,84 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
|
||||
"http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
|
||||
|
||||
// Install an extension.
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["contextMenus"],
|
||||
},
|
||||
|
||||
background: function() {
|
||||
browser.contextMenus.create({title: "a"});
|
||||
browser.contextMenus.create({title: "b"});
|
||||
browser.test.notifyPass("contextmenus-icons");
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contextmenus-icons");
|
||||
|
||||
// Open the context menu.
|
||||
let contextMenu = yield openContextMenu("#img1");
|
||||
|
||||
// Confirm that the extension menu item exists.
|
||||
let topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
|
||||
is(topLevelExtensionMenuItems.length, 1, "the top level extension menu item exists");
|
||||
|
||||
yield closeContextMenu();
|
||||
|
||||
// Uninstall the extension.
|
||||
yield extension.unload();
|
||||
|
||||
// Open the context menu.
|
||||
contextMenu = yield openContextMenu("#img1");
|
||||
|
||||
// Confirm that the extension menu item has been removed.
|
||||
topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
|
||||
is(topLevelExtensionMenuItems.length, 0, "no top level extension menu items should exist");
|
||||
|
||||
yield closeContextMenu();
|
||||
|
||||
// Install a new extension.
|
||||
extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["contextMenus"],
|
||||
},
|
||||
background: function() {
|
||||
browser.contextMenus.create({title: "c"});
|
||||
browser.contextMenus.create({title: "d"});
|
||||
browser.test.notifyPass("contextmenus-uninstall-second-extension");
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contextmenus-uninstall-second-extension");
|
||||
|
||||
// Open the context menu.
|
||||
contextMenu = yield openContextMenu("#img1");
|
||||
|
||||
// Confirm that only the new extension menu item is in the context menu.
|
||||
topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
|
||||
is(topLevelExtensionMenuItems.length, 1, "only one top level extension menu item should exist");
|
||||
|
||||
// Close the context menu.
|
||||
yield closeContextMenu();
|
||||
|
||||
// Uninstall the extension.
|
||||
yield extension.unload();
|
||||
|
||||
// Open the context menu.
|
||||
contextMenu = yield openContextMenu("#img1");
|
||||
|
||||
// Confirm that no extension menu items exist.
|
||||
topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
|
||||
is(topLevelExtensionMenuItems.length, 0, "no top level extension menu items should exist");
|
||||
|
||||
yield closeContextMenu();
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab1);
|
||||
});
|
@ -8,6 +8,8 @@
|
||||
* getBrowserActionPopup getPageActionPopup
|
||||
* closeBrowserAction closePageAction
|
||||
* promisePopupShown promisePopupHidden
|
||||
* openContextMenu closeContextMenu
|
||||
* openExtensionContextMenu closeExtensionContextMenu
|
||||
*/
|
||||
|
||||
var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
@ -104,6 +106,44 @@ function closeBrowserAction(extension, win = window) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function* openContextMenu(id) {
|
||||
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(id, {type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
|
||||
yield popupShownPromise;
|
||||
return contentAreaContextMenu;
|
||||
}
|
||||
|
||||
function* closeContextMenu() {
|
||||
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
|
||||
contentAreaContextMenu.hidePopup();
|
||||
yield popupHiddenPromise;
|
||||
}
|
||||
|
||||
function* openExtensionContextMenu() {
|
||||
let contextMenu = yield openContextMenu("#img1");
|
||||
let topLevelMenu = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
|
||||
|
||||
// Return null if the extension only has one item and therefore no extension menu.
|
||||
if (topLevelMenu.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let extensionMenu = topLevelMenu[0].childNodes[0];
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(extensionMenu, {});
|
||||
yield popupShownPromise;
|
||||
return extensionMenu;
|
||||
}
|
||||
|
||||
function* closeExtensionContextMenu(itemToSelect) {
|
||||
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
|
||||
EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
|
||||
yield popupHiddenPromise;
|
||||
}
|
||||
|
||||
function getPageActionPopup(extension, win = window) {
|
||||
let panelId = makeWidgetId(extension.id) + "-panel";
|
||||
return win.document.getElementById(panelId);
|
||||
|
@ -94,8 +94,6 @@ browser.jar:
|
||||
skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
|
||||
skin/classic/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png)
|
||||
skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
|
||||
skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
|
||||
skin/classic/browser/tabbrowser/connecting@2x.png (tabbrowser/connecting@2x.png)
|
||||
skin/classic/browser/tabbrowser/newtab.svg (tabbrowser/newtab.svg)
|
||||
skin/classic/browser/tabbrowser/newtab-inverted.svg (tabbrowser/newtab-inverted.svg)
|
||||
skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
|
||||
|
@ -145,8 +145,6 @@ browser.jar:
|
||||
skin/classic/browser/tabbrowser/newtab@2x.png (tabbrowser/newtab@2x.png)
|
||||
skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
|
||||
skin/classic/browser/tabbrowser/newtab-inverted@2x.png (tabbrowser/newtab-inverted@2x.png)
|
||||
skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
|
||||
skin/classic/browser/tabbrowser/connecting@2x.png (tabbrowser/connecting@2x.png)
|
||||
skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
|
||||
skin/classic/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
|
||||
skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 29 KiB |
@ -100,6 +100,8 @@
|
||||
skin/classic/browser/social/chat-icons.svg (../shared/social/chat-icons.svg)
|
||||
skin/classic/browser/social/gear_default.png (../shared/social/gear_default.png)
|
||||
skin/classic/browser/social/gear_clicked.png (../shared/social/gear_clicked.png)
|
||||
skin/classic/browser/tabbrowser/connecting.png (../shared/tabbrowser/connecting.png)
|
||||
skin/classic/browser/tabbrowser/connecting@2x.png (../shared/tabbrowser/connecting@2x.png)
|
||||
skin/classic/browser/tabbrowser/crashed.svg (../shared/tabbrowser/crashed.svg)
|
||||
skin/classic/browser/tabbrowser/pendingpaint.png (../shared/tabbrowser/pendingpaint.png)
|
||||
* skin/classic/browser/tabbrowser/tab-audio.svg (../shared/tabbrowser/tab-audio.svg)
|
||||
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@ -153,8 +153,6 @@ browser.jar:
|
||||
skin/classic/browser/tabbrowser/newtab-XPVista7.svg (tabbrowser/newtab-XPVista7.svg)
|
||||
skin/classic/browser/tabbrowser/newtab-inverted.svg (tabbrowser/newtab-inverted.svg)
|
||||
skin/classic/browser/tabbrowser/newtab-inverted-XPVista7.svg (tabbrowser/newtab-inverted-XPVista7.svg)
|
||||
skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
|
||||
skin/classic/browser/tabbrowser/connecting@2x.png (tabbrowser/connecting@2x.png)
|
||||
skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
|
||||
skin/classic/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
|
||||
skin/classic/browser/tabbrowser/tab-arrow-left.svg (tabbrowser/tab-arrow-left.svg)
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 29 KiB |
@ -336,6 +336,22 @@ nsDOMWindowUtils::UpdateLayerTree()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::GetContentViewerSize(uint32_t *aDisplayWidth, uint32_t *aDisplayHeight)
|
||||
{
|
||||
nsIPresShell* presShell = GetPresShell();
|
||||
LayoutDeviceIntSize displaySize;
|
||||
|
||||
if (!presShell || !nsLayoutUtils::GetContentViewerSize(presShell->GetPresContext(), displaySize)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
*aDisplayWidth = displaySize.width;
|
||||
*aDisplayHeight = displaySize.height;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::GetViewportInfo(uint32_t aDisplayWidth,
|
||||
uint32_t aDisplayHeight,
|
||||
@ -540,14 +556,17 @@ nsDOMWindowUtils::SetResolutionAndScaleTo(float aResolution)
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::SetRestoreResolution(float aResolution)
|
||||
nsDOMWindowUtils::SetRestoreResolution(float aResolution,
|
||||
uint32_t aDisplayWidth,
|
||||
uint32_t aDisplayHeight)
|
||||
{
|
||||
nsIPresShell* presShell = GetPresShell();
|
||||
if (!presShell) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
presShell->SetRestoreResolution(aResolution);
|
||||
presShell->SetRestoreResolution(aResolution,
|
||||
LayoutDeviceIntSize(aDisplayWidth, aDisplayHeight));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -121,6 +121,11 @@ interface nsIDOMWindowUtils : nsISupports {
|
||||
out uint32_t aWidth, out uint32_t aHeight,
|
||||
out boolean aAutoSize);
|
||||
|
||||
/**
|
||||
* Information about the window size in device pixels.
|
||||
*/
|
||||
void getContentViewerSize(out uint32_t aDisplayWidth, out uint32_t aDisplayHeight);
|
||||
|
||||
/**
|
||||
* For any scrollable element, this allows you to override the
|
||||
* visible region and draw more than what is visible, which is
|
||||
@ -224,10 +229,15 @@ interface nsIDOMWindowUtils : nsISupports {
|
||||
|
||||
/**
|
||||
* Set a resolution on the presShell which is the "restored" from history.
|
||||
* The display dimensions are compared to their current values and used
|
||||
* to scale the resolution value if necessary, e.g. if the device was
|
||||
* rotated between saving and restoring of the session data.
|
||||
* This resolution should be used when painting for the first time. Calling
|
||||
* this too late may have no effect.
|
||||
*/
|
||||
void setRestoreResolution(in float aResolution);
|
||||
void setRestoreResolution(in float aResolution,
|
||||
in uint32_t aDisplayWidth,
|
||||
in uint32_t aDisplayHeight);
|
||||
|
||||
/**
|
||||
* Whether the resolution has been set by the user.
|
||||
|
@ -85,9 +85,13 @@ MobileViewportManager::Destroy()
|
||||
}
|
||||
|
||||
void
|
||||
MobileViewportManager::SetRestoreResolution(float aResolution)
|
||||
MobileViewportManager::SetRestoreResolution(float aResolution,
|
||||
LayoutDeviceIntSize aDisplaySize)
|
||||
{
|
||||
mRestoreResolution = Some(aResolution);
|
||||
ScreenIntSize restoreDisplaySize = ViewAs<ScreenPixel>(aDisplaySize,
|
||||
PixelCastJustification::LayoutDeviceIsScreenForBounds);
|
||||
mRestoreDisplaySize = Some(restoreDisplaySize);
|
||||
}
|
||||
|
||||
void
|
||||
@ -167,6 +171,21 @@ MobileViewportManager::ClampZoom(const CSSToScreenScale& aZoom,
|
||||
return zoom;
|
||||
}
|
||||
|
||||
LayoutDeviceToLayerScale
|
||||
MobileViewportManager::ScaleResolutionWithDisplayWidth(const LayoutDeviceToLayerScale& aRes,
|
||||
const float& aDisplayWidthChangeRatio,
|
||||
const CSSSize& aNewViewport,
|
||||
const CSSSize& aOldViewport)
|
||||
{
|
||||
float cssViewportChangeRatio = (aOldViewport.width == 0)
|
||||
? 1.0f : aNewViewport.width / aOldViewport.width;
|
||||
LayoutDeviceToLayerScale newRes(aRes.scale * aDisplayWidthChangeRatio
|
||||
/ cssViewportChangeRatio);
|
||||
MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, aRes.scale,
|
||||
aDisplayWidthChangeRatio, cssViewportChangeRatio, newRes.scale);
|
||||
return newRes;
|
||||
}
|
||||
|
||||
CSSToScreenScale
|
||||
MobileViewportManager::UpdateResolution(const nsViewportInfo& aViewportInfo,
|
||||
const ScreenIntSize& aDisplaySize,
|
||||
@ -180,7 +199,19 @@ MobileViewportManager::UpdateResolution(const nsViewportInfo& aViewportInfo,
|
||||
if (mIsFirstPaint) {
|
||||
CSSToScreenScale defaultZoom;
|
||||
if (mRestoreResolution) {
|
||||
defaultZoom = CSSToScreenScale(mRestoreResolution.value() * cssToDev.scale);
|
||||
LayoutDeviceToLayerScale restoreResolution(mRestoreResolution.value());
|
||||
if (mRestoreDisplaySize) {
|
||||
CSSSize prevViewport = mDocument->GetViewportInfo(mRestoreDisplaySize.value()).GetSize();
|
||||
float restoreDisplayWidthChangeRatio = (mRestoreDisplaySize.value().width > 0)
|
||||
? (float)aDisplaySize.width / (float)mRestoreDisplaySize.value().width : 1.0f;
|
||||
|
||||
restoreResolution =
|
||||
ScaleResolutionWithDisplayWidth(restoreResolution,
|
||||
restoreDisplayWidthChangeRatio,
|
||||
aViewport,
|
||||
prevViewport);
|
||||
}
|
||||
defaultZoom = CSSToScreenScale(restoreResolution.scale * cssToDev.scale);
|
||||
MVM_LOG("%p: restored zoom is %f\n", this, defaultZoom.scale);
|
||||
defaultZoom = ClampZoom(defaultZoom, aViewportInfo);
|
||||
} else {
|
||||
@ -231,14 +262,9 @@ MobileViewportManager::UpdateResolution(const nsViewportInfo& aViewportInfo,
|
||||
// tag is added or removed)
|
||||
// 4. neither screen size nor CSS viewport changes
|
||||
if (aDisplayWidthChangeRatio) {
|
||||
float cssViewportChangeRatio = (mMobileViewportSize.width == 0)
|
||||
? 1.0f : aViewport.width / mMobileViewportSize.width;
|
||||
LayoutDeviceToLayerScale newRes(res.scale * aDisplayWidthChangeRatio.value()
|
||||
/ cssViewportChangeRatio);
|
||||
MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, res.scale,
|
||||
aDisplayWidthChangeRatio.value(), cssViewportChangeRatio, newRes.scale);
|
||||
mPresShell->SetResolutionAndScaleTo(newRes.scale);
|
||||
res = newRes;
|
||||
res = ScaleResolutionWithDisplayWidth(res, aDisplayWidthChangeRatio.value(),
|
||||
aViewport, mMobileViewportSize);
|
||||
mPresShell->SetResolutionAndScaleTo(res.scale);
|
||||
}
|
||||
|
||||
return ViewTargetAs<ScreenPixel>(cssToDev * res / ParentLayerToLayerScale(1),
|
||||
|
@ -29,8 +29,12 @@ public:
|
||||
|
||||
/* Provide a resolution to use during the first paint instead of the default
|
||||
* resolution computed from the viewport info metadata. This is in the same
|
||||
* "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo. */
|
||||
void SetRestoreResolution(float aResolution);
|
||||
* "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo.
|
||||
* Also takes the previous display dimensions as they were at the time the
|
||||
* resolution was stored in order to correctly adjust the resolution if the
|
||||
* device was rotated in the meantime. */
|
||||
void SetRestoreResolution(float aResolution,
|
||||
mozilla::LayoutDeviceIntSize aDisplaySize);
|
||||
|
||||
/* Notify the MobileViewportManager that a reflow was requested in the
|
||||
* presShell.*/
|
||||
@ -58,14 +62,23 @@ private:
|
||||
mozilla::CSSToScreenScale ClampZoom(const mozilla::CSSToScreenScale& aZoom,
|
||||
const nsViewportInfo& aViewportInfo);
|
||||
|
||||
/* Helper to update the given resolution according to changed display and viewport widths. */
|
||||
mozilla::LayoutDeviceToLayerScale
|
||||
ScaleResolutionWithDisplayWidth(const mozilla::LayoutDeviceToLayerScale& aRes,
|
||||
const float& aDisplayWidthChangeRatio,
|
||||
const mozilla::CSSSize& aNewViewport,
|
||||
const mozilla::CSSSize& aOldViewport);
|
||||
|
||||
/* Updates the presShell resolution and returns the new zoom. */
|
||||
mozilla::CSSToScreenScale UpdateResolution(const nsViewportInfo& aViewportInfo,
|
||||
const mozilla::ScreenIntSize& aDisplaySize,
|
||||
const mozilla::CSSSize& aViewport,
|
||||
const mozilla::Maybe<float>& aDisplayWidthChangeRatio);
|
||||
|
||||
/* Updates the scroll-position-clamping scrollport size */
|
||||
void UpdateSPCSPS(const mozilla::ScreenIntSize& aDisplaySize,
|
||||
const mozilla::CSSToScreenScale& aZoom);
|
||||
|
||||
/* Updates the displayport margins for the presShell's root scrollable frame */
|
||||
void UpdateDisplayPortMargins();
|
||||
|
||||
@ -77,6 +90,7 @@ private:
|
||||
mozilla::LayoutDeviceIntSize mDisplaySize;
|
||||
mozilla::CSSSize mMobileViewportSize;
|
||||
mozilla::Maybe<float> mRestoreResolution;
|
||||
mozilla::Maybe<mozilla::ScreenIntSize> mRestoreDisplaySize;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "nsPresArena.h"
|
||||
#include "nsMargin.h"
|
||||
#include "nsFrameState.h"
|
||||
#include "Units.h"
|
||||
#include "Visibility.h"
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
@ -1446,7 +1447,8 @@ public:
|
||||
* Used by session restore code to restore a resolution before the first
|
||||
* paint.
|
||||
*/
|
||||
virtual void SetRestoreResolution(float aResolution) = 0;
|
||||
virtual void SetRestoreResolution(float aResolution,
|
||||
mozilla::LayoutDeviceIntSize aDisplaySize) = 0;
|
||||
|
||||
/**
|
||||
* Returns whether we are in a DrawWindow() call that used the
|
||||
|
@ -5633,10 +5633,11 @@ float PresShell::GetCumulativeNonRootScaleResolution()
|
||||
return resolution;
|
||||
}
|
||||
|
||||
void PresShell::SetRestoreResolution(float aResolution)
|
||||
void PresShell::SetRestoreResolution(float aResolution,
|
||||
LayoutDeviceIntSize aDisplaySize)
|
||||
{
|
||||
if (mMobileViewportManager) {
|
||||
mMobileViewportManager->SetRestoreResolution(aResolution);
|
||||
mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +240,8 @@ public:
|
||||
virtual bool ScaleToResolution() const override;
|
||||
virtual float GetCumulativeResolution() override;
|
||||
virtual float GetCumulativeNonRootScaleResolution() override;
|
||||
virtual void SetRestoreResolution(float aResolution) override;
|
||||
virtual void SetRestoreResolution(float aResolution,
|
||||
mozilla::LayoutDeviceIntSize aDisplaySize) override;
|
||||
|
||||
//nsIViewObserver interface
|
||||
|
||||
|
@ -753,11 +753,8 @@ SessionStore.prototype = {
|
||||
|
||||
// Save some data that'll help in adjusting the zoom level
|
||||
// when restoring in a different screen orientation.
|
||||
let viewportInfo = this._getViewportInfo(aWindow.outerWidth, aWindow.outerHeight, content);
|
||||
scrolldata.zoom.autoSize = viewportInfo.autoSize;
|
||||
log("onTabScroll() autoSize: " + scrolldata.zoom.autoSize);
|
||||
scrolldata.zoom.windowWidth = aWindow.outerWidth;
|
||||
log("onTabScroll() windowWidth: " + scrolldata.zoom.windowWidth);
|
||||
scrolldata.zoom.displaySize = this._getContentViewerSize(content);
|
||||
log("onTabScroll() displayWidth: " + scrolldata.zoom.displaySize.width);
|
||||
|
||||
// Save zoom and scroll data.
|
||||
data.scrolldata = scrolldata;
|
||||
@ -767,23 +764,16 @@ SessionStore.prototype = {
|
||||
this.saveStateDelayed();
|
||||
},
|
||||
|
||||
_getViewportInfo: function ss_getViewportInfo(aDisplayWidth, aDisplayHeight, aWindow) {
|
||||
let viewportInfo = {};
|
||||
let defaultZoom = {}, allowZoom = {}, minZoom = {}, maxZoom ={},
|
||||
width = {}, height = {}, autoSize = {};
|
||||
_getContentViewerSize: function ss_getContentViewerSize(aWindow) {
|
||||
let displaySize = {};
|
||||
let width = {}, height = {};
|
||||
aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
|
||||
Ci.nsIDOMWindowUtils).getViewportInfo(aDisplayWidth, aDisplayHeight,
|
||||
defaultZoom, allowZoom, minZoom, maxZoom, width, height, autoSize);
|
||||
Ci.nsIDOMWindowUtils).getContentViewerSize(width, height);
|
||||
|
||||
viewportInfo.defaultZoom = defaultZoom.value;
|
||||
viewportInfo.allowZoom = allowZoom.value;
|
||||
viewportInfo.minZoom = maxZoom.value;
|
||||
viewportInfo.maxZoom = maxZoom.value;
|
||||
viewportInfo.width = width.value;
|
||||
viewportInfo.height = height.value;
|
||||
viewportInfo.autoSize = autoSize.value;
|
||||
displaySize.width = width.value;
|
||||
displaySize.height = height.value;
|
||||
|
||||
return viewportInfo;
|
||||
return displaySize;
|
||||
},
|
||||
|
||||
saveStateDelayed: function ss_saveStateDelayed() {
|
||||
@ -1385,40 +1375,23 @@ SessionStore.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores the zoom level of the window. This needs to be called before
|
||||
* first paint/load (whichever comes first) to take any effect.
|
||||
*/
|
||||
* Restores the zoom level of the window. This needs to be called before
|
||||
* first paint/load (whichever comes first) to take any effect.
|
||||
*/
|
||||
_restoreZoom: function ss_restoreZoom(aScrollData, aBrowser) {
|
||||
if (aScrollData && aScrollData.zoom) {
|
||||
let recalculatedZoom = this._recalculateZoom(aScrollData.zoom);
|
||||
log("_restoreZoom(), resolution: " + recalculatedZoom);
|
||||
if (aScrollData && aScrollData.zoom && aScrollData.zoom.displaySize) {
|
||||
log("_restoreZoom(), resolution: " + aScrollData.zoom.resolution +
|
||||
", old displayWidth: " + aScrollData.zoom.displaySize.width);
|
||||
|
||||
let utils = aBrowser.contentWindow.QueryInterface(
|
||||
Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
// Restore zoom level.
|
||||
utils.setRestoreResolution(recalculatedZoom);
|
||||
utils.setRestoreResolution(aScrollData.zoom.resolution,
|
||||
aScrollData.zoom.displaySize.width,
|
||||
aScrollData.zoom.displaySize.height);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Recalculates the zoom level to account for a changed display width,
|
||||
* e.g. because the device was rotated.
|
||||
*/
|
||||
_recalculateZoom: function ss_recalculateZoom(aZoomData) {
|
||||
let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
// Pages with "width=device-width" won't need any zoom level scaling.
|
||||
if (!aZoomData.autoSize) {
|
||||
let oldWidth = aZoomData.windowWidth;
|
||||
let newWidth = browserWin.outerWidth;
|
||||
if (oldWidth != newWidth && oldWidth > 0 && newWidth > 0) {
|
||||
log("_recalculateZoom(), old resolution: " + aZoomData.resolution);
|
||||
return newWidth / oldWidth * aZoomData.resolution;
|
||||
}
|
||||
}
|
||||
return aZoomData.resolution;
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes serialized scroll positions and restores them into the given browser.
|
||||
*/
|
||||
|
@ -32,6 +32,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=810981
|
||||
|
||||
// Use something with enough content to allow for scrolling.
|
||||
const URL = "http://example.org/chrome/mobile/android/tests/browser/chrome/basic_article_mobile.html";
|
||||
// Something to test the zoom level scaling on rotation with.
|
||||
const URL_desktop = "http://example.org/chrome/mobile/android/tests/browser/chrome/basic_article.html";
|
||||
|
||||
function dispatchUIEvent(browser, type) {
|
||||
let event = browser.contentDocument.createEvent("UIEvents");
|
||||
@ -169,6 +171,74 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=810981
|
||||
BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
|
||||
});
|
||||
|
||||
add_task(function* test_sessionStoreZoomLevelRecalc() {
|
||||
const ZOOM = 4.2;
|
||||
const SCROLL_X = 42;
|
||||
const SCROLL_Y = 42;
|
||||
|
||||
chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let BrowserApp = chromeWin.BrowserApp;
|
||||
|
||||
// Creates a tab, sets a scroll position and zoom level and closes the tab.
|
||||
function createAndRemoveTab() {
|
||||
return Task.spawn(function () {
|
||||
// Create a new tab.
|
||||
tabScroll = BrowserApp.addTab(URL_desktop);
|
||||
let browser = tabScroll.browser;
|
||||
yield promiseBrowserEvent(browser, "pageshow");
|
||||
|
||||
// Modify scroll position and zoom level.
|
||||
setZoomLevel(browser, ZOOM);
|
||||
setScrollPosition(browser, SCROLL_X, SCROLL_Y);
|
||||
yield promiseTabEvent(browser, "SSTabScrollCaptured");
|
||||
|
||||
// Check that we've actually scrolled and zoomed.
|
||||
let ifreq = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let scrollX = {}, scrollY = {}, zoom = {};
|
||||
utils.getResolution(zoom);
|
||||
utils.getScrollXY(false, scrollX, scrollY);
|
||||
ok(fuzzyEquals(zoom.value, ZOOM), "zoom set correctly");
|
||||
is(scrollX.value, SCROLL_X, "scrollX set correctly");
|
||||
is(scrollY.value, SCROLL_Y, "scrollY set correctly");
|
||||
|
||||
// Remove the tab.
|
||||
BrowserApp.closeTab(tabScroll);
|
||||
yield promiseTabEvent(browser, "SSTabCloseProcessed");
|
||||
});
|
||||
}
|
||||
|
||||
yield createAndRemoveTab();
|
||||
let state = ss.getClosedTabs(chromeWin);
|
||||
let [{scrolldata}] = state;
|
||||
is(scrolldata.scroll, SCROLL_X + "," + SCROLL_Y, "stored scroll position is correct");
|
||||
ok(fuzzyEquals(scrolldata.zoom.resolution, ZOOM), "stored zoom level is correct");
|
||||
|
||||
// Pretend the zoom level was originally saved on a rotated device.
|
||||
let closedTabData = ss.getClosedTabs(chromeWin)[0];
|
||||
let displayWidth = scrolldata.zoom.displaySize.width;
|
||||
let displayHeight = scrolldata.zoom.displaySize.height;
|
||||
closedTabData.scrolldata.zoom.displaySize.width = displayHeight;
|
||||
closedTabData.scrolldata.zoom.displaySize.height = displayWidth;
|
||||
|
||||
// Restore the closed tab.
|
||||
let browser = ss.undoCloseTab(chromeWin, closedTabData);
|
||||
yield promiseBrowserEvent(browser, "pageshow");
|
||||
|
||||
// Check the scroll position and zoom level.
|
||||
let ifreq = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let scrollX = {}, scrollY = {}, zoom = {};
|
||||
utils.getResolution(zoom);
|
||||
utils.getScrollXY(false, scrollX, scrollY);
|
||||
ok(fuzzyEquals(zoom.value, ZOOM * displayWidth / displayHeight), "recalculated zoom restored correctly");
|
||||
is(scrollX.value, SCROLL_X, "scrollX restored correctly");
|
||||
is(scrollY.value, SCROLL_Y, "scrollY restored correctly");
|
||||
|
||||
// Remove the tab.
|
||||
BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
|
||||
});
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|