Bug 1559392 - Support drag+drop of add-ons into HTML about:addons r=rpl

Depends on D65834

Differential Revision: https://phabricator.services.mozilla.com/D65835

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mark Striemer 2020-04-02 02:06:25 +00:00
parent 04fd0b27d7
commit 71bcb7f89c
6 changed files with 271 additions and 130 deletions

View File

@ -27,9 +27,11 @@
<script defer src="chrome://mozapps/content/extensions/message-bar.js"></script>
<script defer src="chrome://mozapps/content/extensions/abuse-reports.js"></script>
<script defer src="chrome://mozapps/content/extensions/shortcuts.js"></script>
<script defer src="chrome://mozapps/content/extensions/drag-drop-addon-installer.js"></script>
<script defer src="chrome://mozapps/content/extensions/aboutaddons.js"></script>
</head>
<body>
<drag-drop-addon-installer></drag-drop-addon-installer>
<div id="full">
<div id="sidebar">
<categories-box id="categories" orientation="vertical">

View File

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from aboutaddonsCommon.js */
"use strict";
class DragDropAddonInstaller extends HTMLElement {
connectedCallback() {
window.addEventListener("drop", this);
}
disconnectedCallback() {
window.removeEventListener("drop", this);
}
canInstallFromEvent(e) {
let types = e.dataTransfer.types;
return (
types.includes("text/uri-list") ||
types.includes("text/x-moz-url") ||
types.includes("application/x-moz-file")
);
}
handleEvent(e) {
if (!XPINSTALL_ENABLED) {
// Nothing to do if we can't install add-ons.
return;
}
if (e.type == "drop" && this.canInstallFromEvent(e)) {
this.onDrop(e);
}
}
async onDrop(e) {
e.preventDefault();
let dataTransfer = e.dataTransfer;
let browser = getBrowserElement();
let urls = [];
// Convert every dropped item into a url, without this should be sync.
for (let i = 0; i < dataTransfer.mozItemCount; i++) {
let url = dataTransfer.mozGetDataAt("text/uri-list", i);
if (!url) {
url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
}
if (url) {
url = url.split("\n")[0];
} else {
let file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
if (file) {
url = Services.io.newFileURI(file).spec;
}
}
if (url) {
urls.push(url);
}
}
// Install the add-ons, the await clears the event data so we do this last.
for (let url of urls) {
let install = await AddonManager.getInstallForURL(url, {
telemetryInfo: {
source: "about:addons",
method: "drag-and-drop",
},
});
AddonManager.installAddonFromAOM(
browser,
document.documentURIObject,
install
);
}
}
}
customElements.define("drag-drop-addon-installer", DragDropAddonInstaller);

View File

@ -22,6 +22,7 @@ toolkit.jar:
content/mozapps/extensions/abuse-report-panel.css (content/abuse-report-panel.css)
content/mozapps/extensions/abuse-report-panel.js (content/abuse-report-panel.js)
content/mozapps/extensions/default-theme.svg (content/default-theme.svg)
content/mozapps/extensions/drag-drop-addon-installer.js (content/drag-drop-addon-installer.js)
content/mozapps/extensions/firefox-compact-dark.svg (content/firefox-compact-dark.svg)
content/mozapps/extensions/firefox-compact-light.svg (content/firefox-compact-light.svg)
content/mozapps/extensions/message-bar.css (content/message-bar.css)

View File

@ -4,7 +4,9 @@ prefs =
tags = addons
support-files =
addons/browser_dragdrop1.xpi
addons/browser_dragdrop1.zip
addons/browser_dragdrop2.xpi
addons/browser_dragdrop2.zip
addons/browser_dragdrop_incompat.xpi
addons/browser_installssl.xpi
addons/browser_theme.xpi
@ -32,7 +34,9 @@ support-files =
generated-files =
addons/browser_dragdrop1.xpi
addons/browser_dragdrop1.zip
addons/browser_dragdrop2.xpi
addons/browser_dragdrop2.zip
addons/browser_dragdrop_incompat.xpi
addons/browser_installssl.xpi
addons/browser_theme.xpi
@ -48,7 +52,7 @@ skip-if = (!debug && os == 'win') #Bug 1489496
[browser_bug572561.js]
[browser_checkAddonCompatibility.js]
[browser_dragdrop.js]
skip-if = true # Bug 1559392
skip-if = true # Bug 1626824
[browser_extension_sideloading_permission.js]
[browser_file_xpi_no_process_switch.js]
[browser_globalwarnings.js]

View File

@ -2,21 +2,17 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// This tests simulated drag and drop of files into the add-ons manager.
// We test with the add-ons manager in its own tab if in Firefox otherwise
// in its own window.
// Tests are only simulations of the drag and drop events, we cannot really do
// this automatically.
const ABOUT_ADDONS_URL = "chrome://mozapps/content/extensions/aboutaddons.html";
// Instead of loading EventUtils.js into the test scope in browser-test.js for all tests,
// we only need EventUtils.js for a few files which is why we are using loadSubScript.
var gManagerWindow;
var EventUtils = {};
Services.scriptloader.loadSubScript(
"chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
EventUtils
const dragService = Cc["@mozilla.org/widget/dragservice;1"].getService(
Ci.nsIDragService
);
// Test that the drag-drop-addon-installer component installs add-ons and is
// included in about:addons. There is an issue with EventUtils.synthesizeDrop
// where it throws an exception when you give it an subbrowser so we test
// the component directly.
async function checkInstallConfirmation(...names) {
let notificationCount = 0;
let observer = {
@ -74,145 +70,201 @@ async function checkInstallConfirmation(...names) {
Services.obs.removeObserver(observer, "addon-install-started");
}
function getViewContainer(gManagerWindow) {
return gManagerWindow.document.getElementById("category-box");
function getDragOverTarget(win) {
return win.document.querySelector("categories-box");
}
function getDropTarget(win) {
return win.document.querySelector("drag-drop-addon-installer");
}
function withTestPage(fn) {
return BrowserTestUtils.withNewTab(
{ url: ABOUT_ADDONS_URL, gBrowser },
async browser => {
let win = browser.contentWindow;
await win.customElements.whenDefined("drag-drop-addon-installer");
await fn(browser);
}
);
}
function initDragSession({ dragData, dropEffect }) {
let dropAction;
switch (dropEffect) {
case null:
case undefined:
case "move":
dropAction = _EU_Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
break;
case "copy":
dropAction = _EU_Ci.nsIDragService.DRAGDROP_ACTION_COPY;
break;
case "link":
dropAction = _EU_Ci.nsIDragService.DRAGDROP_ACTION_LINK;
break;
default:
throw new Error(`${dropEffect} is an invalid drop effect value`);
}
const dataTransfer = new DataTransfer();
dataTransfer.dropEffect = dropEffect;
for (let i = 0; i < dragData.length; i++) {
const item = dragData[i];
for (let j = 0; j < item.length; j++) {
dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
}
}
dragService.startDragSessionForTests(dropAction);
const session = dragService.getCurrentSession();
session.dataTransfer = dataTransfer;
return session;
}
async function simulateDragAndDrop(win, dragData) {
const dropTarget = getDropTarget(win);
const dragOverTarget = getDragOverTarget(win);
const dropEffect = "move";
const session = initDragSession({ dragData, dropEffect });
info("Simulate drag over and wait for the drop target to be visible");
EventUtils.synthesizeDragOver(
dragOverTarget,
dragOverTarget,
dragData,
dropEffect,
win
);
// This make sure that the fake dataTransfer has still
// the expected drop effect after the synthesizeDragOver call.
session.dataTransfer.dropEffect = "move";
await BrowserTestUtils.waitForCondition(
() => !dropTarget.hidden,
"Wait for the drop target element to be visible"
);
info("Simulate drop dragData on drop target");
EventUtils.synthesizeDropAfterDragOver(
null,
session.dataTransfer,
dropTarget,
win,
{ _domDispatchOnly: true }
);
dragService.endDragSession(true);
}
// Simulates dropping a URL onto the manager
add_task(async function test_drop_url() {
let url = TESTROOT + "addons/browser_dragdrop1.xpi";
gManagerWindow = await open_manager("addons://list/extension");
let promise = checkInstallConfirmation("Drag Drop test 1");
let viewContainer = getViewContainer(gManagerWindow);
let effect = EventUtils.synthesizeDrop(
viewContainer,
viewContainer,
[[{ type: "text/x-moz-url", data: url }]],
"copy",
gManagerWindow
);
is(effect, "copy", "Drag should be accepted");
await promise;
await close_manager(gManagerWindow);
for (let fileType of ["xpi", "zip"]) {
await withTestPage(async browser => {
const url = TESTROOT + `addons/browser_dragdrop1.${fileType}`;
const promise = checkInstallConfirmation("Drag Drop test 1");
await simulateDragAndDrop(browser.contentWindow, [
[{ type: "text/x-moz-url", data: url }],
]);
await promise;
});
}
});
// Simulates dropping a file onto the manager
add_task(async function test_drop_file() {
let fileurl = get_addon_file_url("browser_dragdrop1.xpi");
gManagerWindow = await open_manager("addons://list/extension");
await wait_for_view_load(gManagerWindow);
let promise = checkInstallConfirmation("Drag Drop test 1");
let viewContainer = getViewContainer(gManagerWindow);
let effect = EventUtils.synthesizeDrop(
viewContainer,
viewContainer,
[[{ type: "application/x-moz-file", data: fileurl.file }]],
"copy",
gManagerWindow
);
is(effect, "copy", "Drag should be accepted");
await promise;
await close_manager(gManagerWindow);
for (let fileType of ["xpi", "zip"]) {
await withTestPage(async browser => {
let fileurl = get_addon_file_url(`browser_dragdrop1.${fileType}`);
let promise = checkInstallConfirmation("Drag Drop test 1");
await simulateDragAndDrop(browser.contentWindow, [
[{ type: "application/x-moz-file", data: fileurl.file }],
]);
await promise;
});
}
});
// Simulates dropping two urls onto the manager
add_task(async function test_drop_multiple_urls() {
let url1 = TESTROOT + "addons/browser_dragdrop1.xpi";
let url2 = TESTROOT2 + "addons/browser_dragdrop2.xpi";
gManagerWindow = await open_manager("addons://list/extension");
await wait_for_view_load(gManagerWindow);
let promise = checkInstallConfirmation(
"Drag Drop test 1",
"Drag Drop test 2"
);
let viewContainer = getViewContainer(gManagerWindow);
let effect = EventUtils.synthesizeDrop(
viewContainer,
viewContainer,
[
await withTestPage(async browser => {
let url1 = TESTROOT + "addons/browser_dragdrop1.xpi";
let url2 = TESTROOT2 + "addons/browser_dragdrop2.zip";
let promise = checkInstallConfirmation(
"Drag Drop test 1",
"Drag Drop test 2"
);
await simulateDragAndDrop(browser.contentWindow, [
[{ type: "text/x-moz-url", data: url1 }],
[{ type: "text/x-moz-url", data: url2 }],
],
"copy",
gManagerWindow
);
is(effect, "copy", "Drag should be accepted");
await promise;
await close_manager(gManagerWindow);
});
]);
await promise;
});
}).skip(); // TODO(rpl): this fails because mozSetDataAt throws IndexSizeError.
// Simulates dropping two files onto the manager
add_task(async function test_drop_multiple_files() {
let fileurl1 = get_addon_file_url("browser_dragdrop1.xpi");
let fileurl2 = get_addon_file_url("browser_dragdrop2.xpi");
gManagerWindow = await open_manager("addons://list/extension");
await wait_for_view_load(gManagerWindow);
let promise = checkInstallConfirmation(
"Drag Drop test 1",
"Drag Drop test 2"
);
let viewContainer = getViewContainer(gManagerWindow);
let effect = EventUtils.synthesizeDrop(
viewContainer,
viewContainer,
[
await withTestPage(async browser => {
let fileurl1 = get_addon_file_url("browser_dragdrop1.zip");
let fileurl2 = get_addon_file_url("browser_dragdrop2.xpi");
let promise = checkInstallConfirmation(
"Drag Drop test 1",
"Drag Drop test 2"
);
await simulateDragAndDrop(browser.contentWindow, [
[{ type: "application/x-moz-file", data: fileurl1.file }],
[{ type: "application/x-moz-file", data: fileurl2.file }],
],
"copy",
gManagerWindow
);
is(effect, "copy", "Drag should be accepted");
await promise;
await close_manager(gManagerWindow);
});
]);
await promise;
});
}).skip(); // TODO(rpl): this fails because mozSetDataAt throws IndexSizeError.
// Simulates dropping a file and a url onto the manager (weird, but should still work)
add_task(async function test_drop_file_and_url() {
let url = TESTROOT + "addons/browser_dragdrop1.xpi";
let fileurl = get_addon_file_url("browser_dragdrop2.xpi");
gManagerWindow = await open_manager("addons://list/extension");
await wait_for_view_load(gManagerWindow);
let promise = checkInstallConfirmation(
"Drag Drop test 1",
"Drag Drop test 2"
);
let viewContainer = getViewContainer(gManagerWindow);
let effect = EventUtils.synthesizeDrop(
viewContainer,
viewContainer,
[
await withTestPage(async browser => {
let url = TESTROOT + "addons/browser_dragdrop1.xpi";
let fileurl = get_addon_file_url("browser_dragdrop2.zip");
let promise = checkInstallConfirmation(
"Drag Drop test 1",
"Drag Drop test 2"
);
await simulateDragAndDrop(browser.contentWindow, [
[{ type: "text/x-moz-url", data: url }],
[{ type: "application/x-moz-file", data: fileurl.file }],
],
"copy",
gManagerWindow
);
is(effect, "copy", "Drag should be accepted");
await promise;
await close_manager(gManagerWindow);
});
]);
await promise;
});
}).skip(); // TODO(rpl): this fails because mozSetDataAt throws IndexSizeError.
// Test that drag-and-drop of an incompatible addon generates
// an error.
add_task(async function test_drop_incompat_file() {
let gManagerWindow = await open_manager("addons://list/extension");
await wait_for_view_load(gManagerWindow);
await withTestPage(async browser => {
let url = `${TESTROOT}/addons/browser_dragdrop_incompat.xpi`;
let errorPromise = TestUtils.topicObserved("addon-install-failed");
let panelPromise = promisePopupNotificationShown("addon-install-failed");
await simulateDragAndDrop(browser.contentWindow, [
[{ type: "text/x-moz-url", data: url }],
]);
let url = `${TESTROOT}/addons/browser_dragdrop_incompat.xpi`;
let viewContainer = getViewContainer(gManagerWindow);
EventUtils.synthesizeDrop(
viewContainer,
viewContainer,
[[{ type: "text/x-moz-url", data: url }]],
"copy",
gManagerWindow
);
await errorPromise;
ok(true, "Got addon-install-failed event");
await close_manager(gManagerWindow);
let panel = await panelPromise;
ok(panel, "Got addon-install-failed popup");
panel.button.click();
});
});

View File

@ -20,12 +20,13 @@ addons = [
output_dir = OBJDIR_FILES._tests.testing.mochitest.browser.toolkit.mozapps.extensions.test.browser.addons
for addon in addons:
indir = 'addons/%s' % addon
xpi = '%s.xpi' % indir
for file_type in ['xpi', 'zip']:
indir = 'addons/%s' % addon
path = '%s.%s' % (indir, file_type)
GENERATED_FILES += [xpi]
GENERATED_FILES[xpi].script = '../create_xpi.py'
GENERATED_FILES[xpi].inputs = [indir]
GENERATED_FILES += [path]
GENERATED_FILES[path].script = '../create_xpi.py'
GENERATED_FILES[path].inputs = [indir]
output_dir += ['!%s' % xpi]
output_dir += ['!%s' % path]