author | Wes Kocher <wkocher@mozilla.com> |
Fri, 08 Jul 2016 15:02:31 -0700 | |
changeset 304238 | bbb29a9b88dd680dbb59577cbe4dc6e58d117100 |
parent 304237 | fd8ff97bc294ec2d0f57408dae0c3b1e50ff62fa (current diff) |
parent 304215 | a30b1295e5968d8d45397eb94c42a00925b14c5c (diff) |
child 304240 | 7a7f440d578febfd991a3f657ae8f57815d27909 |
child 304279 | 14b16ac3899119428c9c434d4e4e2c75f7a1e5f7 |
child 304322 | 7cd43da4f5bcecf51de8b8555e43f4691350df03 |
push id | 30520 |
push user | kwierso@gmail.com |
push date | Fri, 08 Jul 2016 22:13:56 +0000 |
treeherder | autoland@d5d91aa3a430 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 50.0a1 |
first release with | nightly linux32
bbb29a9b88dd
/
50.0a1
/
20160709030233
/
files
nightly linux64
bbb29a9b88dd
/
50.0a1
/
20160709030233
/
files
nightly mac
bbb29a9b88dd
/
50.0a1
/
20160709030201
/
files
nightly win32
bbb29a9b88dd
/
50.0a1
/
20160709030233
/
files
nightly win64
bbb29a9b88dd
/
50.0a1
/
20160709030233
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
50.0a1
/
20160709030233
/
pushlog to previous
nightly linux64
50.0a1
/
20160709030233
/
pushlog to previous
nightly mac
50.0a1
/
20160709030201
/
pushlog to previous
nightly win32
50.0a1
/
20160709030233
/
pushlog to previous
nightly win64
50.0a1
/
20160709030233
/
pushlog to previous
|
browser/themes/linux/tabbrowser/connecting.png | file | annotate | diff | comparison | revisions | |
browser/themes/linux/tabbrowser/connecting@2x.png | file | annotate | diff | comparison | revisions | |
browser/themes/osx/tabbrowser/connecting.png | file | annotate | diff | comparison | revisions | |
browser/themes/osx/tabbrowser/connecting@2x.png | file | annotate | diff | comparison | revisions | |
browser/themes/windows/tabbrowser/connecting.png | file | annotate | diff | comparison | revisions | |
browser/themes/windows/tabbrowser/connecting@2x.png | file | annotate | diff | comparison | revisions |
--- a/browser/components/extensions/ext-contextMenus.js +++ b/browser/components/extensions/ext-contextMenus.js @@ -449,26 +449,26 @@ MenuItem.prototype = { return true; }, }; 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", false); } }); extensions.on("shutdown", (type, extension) => { gContextMenuMap.delete(extension); + gRootItems.delete(extension); if (--gExtensionCount == 0) { Services.obs.removeObserver(contextMenuObserver, "on-build-contextmenu"); } }); /* eslint-enable mozilla/balanced-listeners */ extensions.registerSchemaAPI("contextMenus", (extension, context) => {
--- a/browser/components/extensions/test/browser/.eslintrc +++ b/browser/components/extensions/test/browser/.eslintrc @@ -14,13 +14,17 @@ "PanelUI": false, // Test harness globals "ExtensionTestUtils": false, "TestUtils": false, "clickBrowserAction": true, "clickPageAction": true, - "CustomizableUI": true, + "closeContextMenu": true, + "closeExtensionContextMenu": true, "focusWindow": true, "makeWidgetId": true, + "openContextMenu": true, + "openExtensionContextMenu": true, + "CustomizableUI": true, } }
--- a/browser/components/extensions/test/browser/browser.ini +++ b/browser/components/extensions/test/browser/browser.ini @@ -23,16 +23,20 @@ support-files = [browser_ext_browserAction_popup.js] [browser_ext_browserAction_popup_resize.js] [browser_ext_browserAction_simple.js] [browser_ext_commands_execute_page_action.js] [browser_ext_commands_getAll.js] [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] [browser_ext_incognito_popup.js] [browser_ext_lastError.js] [browser_ext_optionsPage_privileges.js] [browser_ext_pageAction_context.js] [browser_ext_pageAction_popup.js]
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js @@ -22,46 +22,25 @@ add_task(function* () { }); browser.test.notifyPass(); }, }); 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"); - - let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden"); - EventUtils.synthesizeMouseAtCenter(item[0], {}); - yield popupHiddenPromise; + yield closeContextMenu(); - 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(); yield BrowserTestUtils.removeTab(tab1); }); /* globals content */ /* eslint-disable mozilla/no-cpows-in-tests */ @@ -137,260 +116,128 @@ add_task(function* () { browser.contextMenus.create({ title: "child2", parentId: parentToDel, onclick: genericOnClick, }); 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", }); 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(); - - let contentAreaContextMenu; - - 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]; - } + yield extension.awaitFinish("contextmenus"); - function* openExtensionMenu() { - contentAreaContextMenu = document.getElementById("contentAreaContextMenu"); - let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown"); - yield BrowserTestUtils.synthesizeMouseAtCenter("#img1", { - type: "contextmenu", - button: 2, - }, gBrowser.selectedBrowser); - yield popupShownPromise; + 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", + }; - popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown"); - EventUtils.synthesizeMouseAtCenter(getTop(), {}); - yield popupShownPromise; + 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]); + } + is(expectedClickInfo.pageSrc, result.tab.url); } - 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); - } - 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; - } - - 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", - }); - - // 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 closeExtensionContextMenu(image); - 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) { let doc = content.document; let range = doc.createRange(); let selection = content.getSelection(); selection.removeAllRanges(); let textNode = doc.getElementById("img1").previousSibling; range.setStart(textNode, 0); range.setEnd(textNode, 100); selection.addRange(range); }); // 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); });
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js @@ -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); +});
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js @@ -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); +});
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_radioGroups.js @@ -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); +});
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_uninstall.js @@ -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); +});
--- a/browser/components/extensions/test/browser/head.js +++ b/browser/components/extensions/test/browser/head.js @@ -3,16 +3,18 @@ "use strict"; /* exported CustomizableUI makeWidgetId focusWindow forceGC * getBrowserActionWidget * clickBrowserAction clickPageAction * getBrowserActionPopup getPageActionPopup * closeBrowserAction closePageAction * promisePopupShown promisePopupHidden + * openContextMenu closeContextMenu + * openExtensionContextMenu closeExtensionContextMenu */ var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm"); var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm"); // Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable // times in debug builds, which results in intermittent timeouts. Until we have // a better solution, we force a GC after certain strategic tests, which tend to @@ -99,16 +101,54 @@ function closeBrowserAction(extension, w let group = getBrowserActionWidget(extension); let node = win.document.getElementById(group.viewId); CustomizableUI.hidePanelForNode(node); 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); } function clickPageAction(extension, win = window) { // This would normally be set automatically on navigation, and cleared // when the user types a value into the URL bar, to show and hide page
--- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -89,18 +89,16 @@ browser.jar: * skin/classic/browser/preferences/in-content/dialog.css (preferences/in-content/dialog.css) skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/social/services-16.png (social/services-16.png) skin/classic/browser/social/services-64.png (social/services-64.png) skin/classic/browser/social/share-button.png (social/share-button.png) 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) 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) skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png) skin/classic/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png) skin/classic/browser/tabbrowser/tab-background-end@2x.png (tabbrowser/tab-background-end@2x.png)
deleted file mode 100644 index e564fb5708f3d9da4c623bb3bdfccb960566e327..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index 97e2b2eb67ecc58e98dd839187143ab6870699e5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
--- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -140,18 +140,16 @@ browser.jar: skin/classic/browser/social/services-64@2x.png (social/services-64@2x.png) skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png (tabbrowser/alltabs-box-bkgnd-icon.png) skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted.png (tabbrowser/alltabs-box-bkgnd-icon-inverted.png) skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png (tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png) skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png) 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) skin/classic/browser/tabbrowser/tab-arrow-left@2x.png (tabbrowser/tab-arrow-left@2x.png) skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png) skin/classic/browser/tabbrowser/tab-arrow-left-inverted@2x.png (tabbrowser/tab-arrow-left-inverted@2x.png) skin/classic/browser/tabbrowser/tab-arrow-right.png (tabbrowser/tab-arrow-right.png) skin/classic/browser/tabbrowser/tab-arrow-right@2x.png (tabbrowser/tab-arrow-right@2x.png)
deleted file mode 100644 index e564fb5708f3d9da4c623bb3bdfccb960566e327..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 index 97e2b2eb67ecc58e98dd839187143ab6870699e5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
--- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -95,16 +95,18 @@ skin/classic/browser/search-indicator-badge-add.png (../shared/search/search-indicator-badge-add.png) skin/classic/browser/search-indicator-badge-add@2x.png (../shared/search/search-indicator-badge-add@2x.png) skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg) skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg) skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg) 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) skin/classic/browser/tabbrowser/tab-audio-small.svg (../shared/tabbrowser/tab-audio-small.svg) skin/classic/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png) skin/classic/browser/theme-switcher-icon.png (../shared/theme-switcher-icon.png) skin/classic/browser/theme-switcher-icon@2x.png (../shared/theme-switcher-icon@2x.png) skin/classic/browser/translating-16.png (../shared/translation/translating-16.png)
rename from browser/themes/windows/tabbrowser/connecting.png rename to browser/themes/shared/tabbrowser/connecting.png
rename from browser/themes/windows/tabbrowser/connecting@2x.png rename to browser/themes/shared/tabbrowser/connecting@2x.png
--- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -148,18 +148,16 @@ browser.jar: * skin/classic/browser/preferences/in-content/dialog.css (preferences/in-content/dialog.css) skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/social/services-16.png (social/services-16.png) skin/classic/browser/social/services-64.png (social/services-64.png) skin/classic/browser/tabbrowser/newtab.svg (tabbrowser/newtab.svg) 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) skin/classic/browser/tabbrowser/tab-arrow-left-XPVista7.svg (tabbrowser/tab-arrow-left-XPVista7.svg) skin/classic/browser/tabbrowser/tab-arrow-left-inverted.svg (tabbrowser/tab-arrow-left-inverted.svg) skin/classic/browser/tabbrowser/tab-background-start.png (tabbrowser/tab-background-start.png) skin/classic/browser/tabbrowser/tab-background-start@2x.png (tabbrowser/tab-background-start@2x.png) skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -332,16 +332,32 @@ nsDOMWindowUtils::UpdateLayerTree() presShell->Paint(view, view->GetBounds(), nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_SYNC_DECODE_IMAGES); } } 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, double *aDefaultZoom, bool *aAllowZoom, double *aMinZoom, double *aMaxZoom, uint32_t *aWidth, uint32_t *aHeight, bool *aAutoSize) { nsIDocument* doc = GetDocument(); @@ -535,24 +551,27 @@ nsDOMWindowUtils::SetResolutionAndScaleT } presShell->SetResolutionAndScaleTo(aResolution); return NS_OK; } 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; } NS_IMETHODIMP nsDOMWindowUtils::GetResolution(float* aResolution) { nsIPresShell* presShell = GetPresShell();
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -117,16 +117,21 @@ interface nsIDOMWindowUtils : nsISupport */ void getViewportInfo(in uint32_t aDisplayWidth, in uint32_t aDisplayHeight, out double aDefaultZoom, out boolean aAllowZoom, out double aMinZoom, out double aMaxZoom, 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 * useful for asynchronous drawing. The "displayport" will be * <xPx, yPx, widthPx, heightPx> in units of CSS pixels, * regardless of the size of the enclosing container. This * will *not* trigger reflow. * * For the root scroll area, pass in the root document element. @@ -219,20 +224,25 @@ interface nsIDOMWindowUtils : nsISupport * for pinch-zoom on mobile platforms. * * The caller of this method must have chrome privileges. */ void setResolutionAndScaleTo(in float aResolution); /** * 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. * This gives a way to check whether the provided resolution is the default * value or restored from a previous session. * * Can only be accessed with chrome privileges. */
--- a/layout/base/MobileViewportManager.cpp +++ b/layout/base/MobileViewportManager.cpp @@ -80,19 +80,23 @@ MobileViewportManager::Destroy() observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data()); } mDocument = nullptr; mPresShell = nullptr; } 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 MobileViewportManager::RequestReflow() { MVM_LOG("%p: got a reflow request\n", this); RefreshViewportSize(false); } @@ -162,30 +166,57 @@ MobileViewportManager::ClampZoom(const C } if (zoom > aViewportInfo.GetMaxZoom()) { zoom = aViewportInfo.GetMaxZoom(); MVM_LOG("%p: Clamped to %f\n", this, zoom.scale); } 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, const CSSSize& aViewport, const Maybe<float>& aDisplayWidthChangeRatio) { CSSToLayoutDeviceScale cssToDev = mPresShell->GetPresContext()->CSSToDevPixelScale(); LayoutDeviceToLayerScale res(mPresShell->GetResolution()); 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 { defaultZoom = aViewportInfo.GetDefaultZoom(); MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale); if (!aViewportInfo.IsDefaultZoomValid()) { defaultZoom = MaxScaleRatio(ScreenSize(aDisplaySize), aViewport); MVM_LOG("%p: Intrinsic computed zoom is %f\n", this, defaultZoom.scale); @@ -226,24 +257,19 @@ MobileViewportManager::UpdateResolution( // 1. screen size changes, CSS viewport does not (pages with no meta viewport // or a fixed size viewport) // 2. screen size changes, CSS viewport also does (pages with a device-width // viewport) // 3. screen size remains constant, but CSS viewport changes (meta viewport // 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), PixelCastJustification::ScreenIsParentLayerForRoot); } void MobileViewportManager::UpdateSPCSPS(const ScreenIntSize& aDisplaySize,
--- a/layout/base/MobileViewportManager.h +++ b/layout/base/MobileViewportManager.h @@ -24,18 +24,22 @@ public: NS_DECL_NSIOBSERVER MobileViewportManager(nsIPresShell* aPresShell, nsIDocument* aDocument); void Destroy(); /* 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.*/ void RequestReflow(); /* Notify the MobileViewportManager that the resolution on the presShell was * updated, and the SPCSPS needs to be updated. */ void ResolutionUpdated(); @@ -53,31 +57,41 @@ private: /* Secondary main helper method to update just the SPCSPS. */ void RefreshSPCSPS(); /* Helper to clamp the given zoom by the min/max in the viewport info. */ 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(); nsCOMPtr<nsIDocument> mDocument; nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this nsCOMPtr<nsIDOMEventTarget> mEventTarget; bool mIsFirstPaint; bool mPainted; mozilla::LayoutDeviceIntSize mDisplaySize; mozilla::CSSSize mMobileViewportSize; mozilla::Maybe<float> mRestoreResolution; + mozilla::Maybe<mozilla::ScreenIntSize> mRestoreDisplaySize; }; #endif
--- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -43,16 +43,17 @@ #include "nsWeakReference.h" #include <stdio.h> // for FILE definition #include "nsChangeHint.h" #include "nsRefPtrHashtable.h" #include "nsClassHashtable.h" #include "nsPresArena.h" #include "nsMargin.h" #include "nsFrameState.h" +#include "Units.h" #include "Visibility.h" #ifdef MOZ_B2G #include "nsIHardwareKeyHandler.h" #endif class nsDocShell; class nsIDocument; @@ -1441,17 +1442,18 @@ public: * SetResolutionAndScaleTo(), and set to false by a call to SetResolution(). */ virtual bool ScaleToResolution() const = 0; /** * 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 * DRAWWINDOW_DO_NOT_FLUSH flag. */ bool InDrawWindowNotFlushing() const { return mRenderFlags & STATE_DRAWWINDOW_NOT_FLUSHING; }
--- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -5628,20 +5628,21 @@ float PresShell::GetCumulativeNonRootSca currentShell = parentCtx->PresShell(); } else { currentShell = nullptr; } } return resolution; } -void PresShell::SetRestoreResolution(float aResolution) +void PresShell::SetRestoreResolution(float aResolution, + LayoutDeviceIntSize aDisplaySize) { if (mMobileViewportManager) { - mMobileViewportManager->SetRestoreResolution(aResolution); + mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize); } } void PresShell::SetRenderingState(const RenderingState& aState) { if (mRenderFlags != aState.mRenderFlags) { // Rendering state changed in a way that forces us to flush any // retained layers we might already have.
--- a/layout/base/nsPresShell.h +++ b/layout/base/nsPresShell.h @@ -235,17 +235,18 @@ public: return SetResolutionImpl(aResolution, /* aScaleToResolution = */ false); } virtual nsresult SetResolutionAndScaleTo(float aResolution) override { return SetResolutionImpl(aResolution, /* aScaleToResolution = */ true); } 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 virtual void Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion, uint32_t aFlags) override; virtual nsresult HandleEvent(nsIFrame* aFrame, mozilla::WidgetGUIEvent* aEvent, bool aDontRetargetEvents,
--- a/mobile/android/components/SessionStore.js +++ b/mobile/android/components/SessionStore.js @@ -748,47 +748,37 @@ SessionStore.prototype = { content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface( Ci.nsIDOMWindowUtils).getResolution(zoom); scrolldata.zoom = {}; scrolldata.zoom.resolution = zoom.value; log("onTabScroll() zoom level: " + zoom.value); // 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; log("onTabScroll() ran for tab " + aWindow.BrowserApp.getTabForBrowser(aBrowser).id); let evt = new Event("SSTabScrollCaptured", {"bubbles":true, "cancelable":false}); aBrowser.dispatchEvent(evt); 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() { if (!this._saveTimer) { // Interval until the next disk operation is allowed let minimalDelay = this._lastSaveTime + this._interval - Date.now(); // If we have to wait, set a timer, otherwise saveState directly @@ -1380,51 +1370,34 @@ SessionStore.prototype = { _restoreTextData: function ss_restoreTextData(aFormData, aBrowser) { if (aFormData) { log("_restoreTextData()"); FormData.restoreTree(aBrowser.contentWindow, aFormData); } }, /** - * 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. */ _restoreScrollPosition: function ss_restoreScrollPosition(aScrollData, aBrowser) { if (aScrollData) { log("_restoreScrollPosition()"); ScrollPosition.restoreTree(aBrowser.contentWindow, aScrollData); } },
--- a/mobile/android/tests/browser/chrome/test_session_scroll_position.html +++ b/mobile/android/tests/browser/chrome/test_session_scroll_position.html @@ -27,16 +27,18 @@ https://bugzilla.mozilla.org/show_bug.cg // The chrome window. let chromeWin; // Track the tabs where the tests are happening. let tabScroll; // 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"); event.initUIEvent(type, true, false, browser.contentDocument.defaultView, 0); browser.dispatchEvent(event); } function setScrollPosition(browser, x, y) { @@ -164,16 +166,84 @@ https://bugzilla.mozilla.org/show_bug.cg ok(fuzzyEquals(zoom.value, ZOOM), "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)); }); + 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> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=810981">Mozilla Bug 810981</a> <p id="display"></p> <div id="content" style="display: none"> </div>