Bug 92737 - Part 3: Open multiple tabs when multiple items are dropped on remote content area. r=enndeakin

This commit is contained in:
Tooru Fujisawa 2015-11-11 07:35:12 +09:00
Родитель 9867f060f7
Коммит e933275fe8
11 изменённых файлов: 306 добавлений и 98 удалений

Просмотреть файл

@ -5654,9 +5654,14 @@ function handleDroppedLink(event, urlOrLinks, name)
let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
if (event.shiftKey)
inBackground = !inBackground;
// event is null if links are dropped in content process.
// inBackground should be false, as it's loading into current browser.
let inBackground = false;
if (event) {
let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
if (event.shiftKey)
inBackground = !inBackground;
}
Task.spawn(function*() {
let urls = [];
@ -5677,9 +5682,13 @@ function handleDroppedLink(event, urlOrLinks, name)
}
});
// Keep the event from being handled by the dragDrop listeners
// built-in to gecko if they happen to be above us.
event.preventDefault();
// If links are dropped in content process, event.preventDefault() should be
// called in content process.
if (event) {
// Keep the event from being handled by the dragDrop listeners
// built-in to gecko if they happen to be above us.
event.preventDefault();
}
}
function BrowserSetForcedCharacterSet(aCharset)

Просмотреть файл

@ -23,4 +23,3 @@ interface nsIRemoteBrowser : nsISupports
in unsigned long disabledLength,
[array, size_is(disabledLength)] in string disabledCommands);
};

Просмотреть файл

@ -4,6 +4,8 @@
#include "domstubs.idl"
#include "nsIDroppedLinkHandler.idl"
interface nsIContentFrameMessageManager;
interface nsIWebBrowserChrome3;
@ -28,6 +30,9 @@ interface nsITabChild : nsISupports
[noscript] void remoteSizeShellTo(in int32_t width, in int32_t height,
in int32_t shellItemWidth, in int32_t shellItemHeight);
[noscript] void remoteDropLinks(in unsigned long linksCount,
[array, size_is(linksCount)] in nsIDroppedLinkItem links);
readonly attribute uint64_t tabId;
};

Просмотреть файл

@ -186,6 +186,14 @@ parent:
async SizeShellTo(uint32_t aFlag, int32_t aWidth, int32_t aHeight,
int32_t aShellItemWidth, int32_t aShellItemHeight);
/**
* Called by the child to inform the parent that links are dropped into
* content area.
*
* aLinks A flat array of url, name, and type for each link
*/
async DropLinks(nsString[] aLinks);
async Event(RemoteDOMEvent aEvent);
sync SyncMessage(nsString aMessage, ClonedMessageData aData,

Просмотреть файл

@ -952,6 +952,37 @@ TabChild::RemoteSizeShellTo(int32_t aWidth, int32_t aHeight,
return sent ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
TabChild::RemoteDropLinks(uint32_t aLinksCount, nsIDroppedLinkItem** aLinks)
{
nsTArray<nsString> linksArray;
nsresult rv = NS_OK;
for (uint32_t i = 0; i < aLinksCount; i++) {
nsString tmp;
rv = aLinks[i]->GetUrl(tmp);
if (NS_FAILED(rv)) {
return rv;
}
linksArray.AppendElement(tmp);
rv = aLinks[i]->GetName(tmp);
if (NS_FAILED(rv)) {
return rv;
}
linksArray.AppendElement(tmp);
rv = aLinks[i]->GetType(tmp);
if (NS_FAILED(rv)) {
return rv;
}
linksArray.AppendElement(tmp);
}
bool sent = SendDropLinks(linksArray);
return sent ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
TabChild::SizeBrowserTo(int32_t aWidth, int32_t aHeight)
{

Просмотреть файл

@ -49,6 +49,7 @@
#include "nsFocusManager.h"
#include "nsFrameLoader.h"
#include "nsIBaseWindow.h"
#include "nsIBrowser.h"
#include "nsIContent.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeOwner.h"
@ -697,6 +698,21 @@ TabParent::RecvSizeShellTo(const uint32_t& aFlags, const int32_t& aWidth, const
return true;
}
bool
TabParent::RecvDropLinks(nsTArray<nsString>&& aLinks)
{
nsCOMPtr<nsIBrowser> browser = do_QueryInterface(mFrameElement);
if (browser) {
UniquePtr<const char16_t*[]> links;
links = MakeUnique<const char16_t*[]>(aLinks.Length());
for (uint32_t i = 0; i < aLinks.Length(); i++) {
links[i] = aLinks[i].get();
}
browser->DropLinks(aLinks.Length(), links.get());
}
return true;
}
bool
TabParent::RecvEvent(const RemoteDOMEvent& aEvent)
{

Просмотреть файл

@ -170,6 +170,8 @@ public:
const int32_t& aShellItemWidth,
const int32_t& aShellItemHeight) override;
virtual bool RecvDropLinks(nsTArray<nsString>&& aLinks) override;
virtual bool RecvEvent(const RemoteDOMEvent& aEvent) override;
virtual bool RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent) override;

Просмотреть файл

@ -14,4 +14,14 @@ interface nsIBrowser : nsISupports
* for any new tab parents.
*/
readonly attribute nsIDOMElement relatedBrowser;
/*
* Called by the child to inform the parent that links are dropped into
* content area.
*
* @param linksCount length of links
* @param links a flat array of url, name, and type for each link
*/
void dropLinks(in unsigned long linksCount,
[array, size_is(linksCount)] in wstring links);
};

Просмотреть файл

@ -1062,11 +1062,34 @@ nsDocShellTreeOwner::HandleEvent(nsIDOMEvent* aEvent)
} else if (eventType.EqualsLiteral("drop")) {
nsIWebNavigation* webnav = static_cast<nsIWebNavigation*>(mWebBrowser);
nsAutoString link, name;
uint32_t linksCount;
nsIDroppedLinkItem** links;
if (webnav &&
NS_SUCCEEDED(handler->DropLink(dragEvent, name, true, link))) {
if (!link.IsEmpty()) {
webnav->LoadURI(link.get(), 0, nullptr, nullptr, nullptr);
NS_SUCCEEDED(handler->DropLinks(dragEvent, true, &linksCount, &links))) {
if (linksCount >= 1) {
nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
if (webBrowserChrome) {
nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(webBrowserChrome);
if (tabChild) {
nsresult rv = tabChild->RemoteDropLinks(linksCount, links);
for (uint32_t i = 0; i < linksCount; i++) {
NS_RELEASE(links[i]);
}
free(links);
return rv;
}
}
nsAutoString url;
if (NS_SUCCEEDED(links[0]->GetUrl(url))) {
if (!url.IsEmpty()) {
webnav->LoadURI(url.get(), 0, nullptr, nullptr, nullptr);
}
}
for (uint32_t i = 0; i < linksCount; i++) {
NS_RELEASE(links[i]);
}
free(links);
}
} else {
aEvent->StopPropagation();

Просмотреть файл

@ -14,123 +14,204 @@
Components.utils.import("resource://testing-common/ContentTask.jsm");
function doDropAndStopLoad(browser, data, shouldExpectStateChange) {
function dropOnRemoteBrowserAsync(browser, data, shouldExpectStateChange) {
ContentTask.setTestScope(window); // Need this so is/isnot/ok are available inside the contenttask
return ContentTask.spawn(browser, {data, shouldExpectStateChange}, function*({data, shouldExpectStateChange}) {
let { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let nameReturned = "";
let uri = "";
let gotLoad = false;
function stopContent(uriLoaded) {
content.stop();
gotLoad = true;
uri = uriLoaded;
if (!content.document.documentElement) {
// Wait until the testing document gets loaded.
yield new Promise(resolve => {
let onload = function() {
content.window.removeEventListener("load", onload);
resolve();
};
content.window.addEventListener("load", onload);
});
}
let wp = docShell.QueryInterface(Ci.nsIWebProgress);
let progressListener = {
onStateChange: function (webProgress, req, flags, status) {
info("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n");
if (webProgress.isTopLevel &&
flags & Ci.nsIWebProgressListener.STATE_START) {
wp.removeProgressListener(progressListener);
let chan = req.QueryInterface(Ci.nsIChannel);
info(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`);
stopContent(chan.originalURI.spec);
}
},
QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"])
};
wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW);
let dataTransfer = new content.DataTransfer("dragstart", false);
dataTransfer.mozSetDataAt(data.type, data.data, 0);
for (let i = 0; i < data.length; i++) {
let types = data[i];
for (let j = 0; j < types.length; j++) {
dataTransfer.mozSetDataAt(types[j].type, types[j].data, i);
}
}
let event = content.document.createEvent("DragEvent");
event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
content.document.body.dispatchEvent(event);
let nameGetter = {};
let links = [];
try {
Services.droppedLinkHandler.dropLink(event, nameGetter, true);
nameReturned = nameGetter.value;
links = Services.droppedLinkHandler.dropLinks(event, true);
} catch (ex) {
if (shouldExpectStateChange) {
ok(false, "Should not have gotten an exception from the dropped link handler, but got: " + ex);
Cu.reportError(ex);
}
}
is(shouldExpectStateChange, gotLoad, "Should have gotten a load only if we expected it.");
if (!gotLoad) {
wp.removeProgressListener(progressListener);
}
return [uri, nameReturned];
return links;
});
}
function expectLink(browser, expectedLink, expectedName, data, testid, onbody=false) {
let lastLink = "";
let lastLinkName = "";
function dropOnBrowserSync() {
browser.droppedLinkHandler = function(event, link, linkname) {
info(`droppedLinkHandler called, received link ${link} and linkname ${linkname}`);
if (!expectedLink && !expectedName) {
ok(false, "droppedLinkHandler called for URI " + link + " which we didn't expect.");
function* expectLink(browser, expectedLinks, data, testid, onbody=false) {
let lastLinks = [];
let lastLinksPromise = new Promise(resolve => {
browser.droppedLinkHandler = function(event, links) {
info(`droppedLinkHandler called, received links ${JSON.stringify(links)}`);
if (expectedLinks.length == 0) {
ok(false, `droppedLinkHandler called for ${JSON.stringify(links)} which we didn't expect.`);
}
lastLink = link;
lastLinkName = linkname;
lastLinks = links;
resolve(links);
};
});
function dropOnBrowserSync() {
let dropEl = onbody ? browser.contentDocument.body : browser;
synthesizeDrop(dropEl, dropEl, data, "", dropEl.ownerDocument.defaultView);
return Promise.resolve([lastLink, lastLinkName]);
}
let dropInfoReceived;
let links;
if (browser.isRemoteBrowser) {
dropInfoReceived = doDropAndStopLoad(browser, data[0][0], !!expectedLink);
let remoteLinks = yield dropOnRemoteBrowserAsync(browser, data, expectedLinks.length != 0);
is(remoteLinks.length, expectedLinks.length, testid + " remote links length");
for (let i = 0, length = remoteLinks.length; i < length; i++) {
is(remoteLinks[i].url, expectedLinks[i].url, testid + "[" + i + "] remote link");
is(remoteLinks[i].name, expectedLinks[i].name, testid + "[" + i + "] remote name");
}
if (expectedLinks.length == 0) {
// There is no way to check if nothing happens asynchronously.
return;
}
links = yield lastLinksPromise;
} else {
dropInfoReceived = dropOnBrowserSync();
dropOnBrowserSync();
links = lastLinks;
}
return dropInfoReceived.then(([uri, linkname]) => {
is(uri, expectedLink, testid + " link");
is(linkname, expectedName, testid + " name");
});
}
is(links.length, expectedLinks.length, testid + " links length");
for (let i = 0, length = links.length; i < length; i++) {
is(links[i].url, expectedLinks[i].url, testid + "[" + i + "] link");
is(links[i].name, expectedLinks[i].name, testid + "[" + i + "] name");
}
};
function* dropLinksOnBrowser(browser, type) {
yield expectLink(browser, "http://www.mozilla.org/", "http://www.mozilla.org/",
[ [ { type: "text/plain", data: "http://www.mozilla.org/" } ] ],
"text/plain drop on browser " + type);
yield expectLink(browser, "", "",
[ [ { type: "text/link", data: "http://www.mozilla.org/" } ] ],
"text/link drop on browser " + type);
yield expectLink(browser, "http://www.example.com/", "http://www.example.com/",
[ [ { type: "text/uri-list", data: "http://www.example.com/\nhttp://www.mozilla.org" } ] ],
"text/uri-list drop on browser " + type);
yield expectLink(browser, "http://www.example.com/", "Example.com",
[ [ { type: "text/x-moz-url", data: "http://www.example.com/\nExample.com" } ] ],
"text/x-moz-url drop on browser " + type);
// Dropping single text/plain item with single link should open single
// page.
yield* expectLink(browser,
[ { url: "http://www.mozilla.org/",
name: "http://www.mozilla.org/" } ],
[ [ { type: "text/plain",
data: "http://www.mozilla.org/" } ] ],
"text/plain drop on browser " + type);
// Dropping single text/plain item with multiple links should open
// multiple pages.
yield* expectLink(browser,
[ { url: "http://www.mozilla.org/",
name: "http://www.mozilla.org/" },
{ url: "http://www.example.com/",
name: "http://www.example.com/" } ],
[ [ { type: "text/plain",
data: "http://www.mozilla.org/\nhttp://www.example.com/" } ] ],
"text/plain with 2 URLs drop on browser " + type);
// Dropping sinlge unsupported type item should not open anything.
yield* expectLink(browser,
[],
[ [ { type: "text/link",
data: "http://www.mozilla.org/" } ] ],
"text/link drop on browser " + type);
// Dropping single text/uri-list item with single link should open single
// page.
yield* expectLink(browser,
[ { url: "http://www.example.com/",
name: "http://www.example.com/" } ],
[ [ { type: "text/uri-list",
data: "http://www.example.com/" } ] ],
"text/uri-list drop on browser " + type);
// Dropping single text/uri-list item with multiple links should open
// multiple pages.
yield* expectLink(browser,
[ { url: "http://www.example.com/",
name: "http://www.example.com/" },
{ url: "http://www.mozilla.org/",
name: "http://www.mozilla.org/" }],
[ [ { type: "text/uri-list",
data: "http://www.example.com/\nhttp://www.mozilla.org/" } ] ],
"text/uri-list with 2 URLs drop on browser " + type);
// Name in text/x-moz-url should be handled.
yield* expectLink(browser,
[ { url: "http://www.example.com/",
name: "Example.com" } ],
[ [ { type: "text/x-moz-url",
data: "http://www.example.com/\nExample.com" } ] ],
"text/x-moz-url drop on browser " + type);
yield* expectLink(browser,
[ { url: "http://www.mozilla.org/",
name: "Mozilla.org" },
{ url: "http://www.example.com/",
name: "Example.com" } ],
[ [ { type: "text/x-moz-url",
data: "http://www.mozilla.org/\nMozilla.org\nhttp://www.example.com/\nExample.com" } ] ],
"text/x-moz-url with 2 URLs drop on browser " + type);
// Dropping multiple items should open multiple pages.
yield* expectLink(browser,
[ { url: "http://www.example.com/",
name: "Example.com" },
{ url: "http://www.mozilla.org/",
name: "http://www.mozilla.org/" }],
[ [ { type: "text/x-moz-url",
data: "http://www.example.com/\nExample.com" } ],
[ { type: "text/plain",
data: "http://www.mozilla.org/" } ] ],
"text/x-moz-url and text/plain drop on browser " + type);
// Dropping single item with multiple types should open single page.
yield* expectLink(browser,
[ { url: "http://www.example.org/",
name: "Example.com" } ],
[ [ { type: "text/plain",
data: "http://www.mozilla.org/" },
{ type: "text/x-moz-url",
data: "http://www.example.org/\nExample.com" } ] ],
"text/plain and text/x-moz-url drop on browser " + type);
// Dropping javascript or data: URLs should fail:
yield expectLink(browser, "", "",
[ [ { type: "text/plain", data: "javascript:'bad'" } ] ],
"text/plain javascript url drop on browser " + type);
yield expectLink(browser, "", "",
[ [ { type: "text/plain", data: "jAvascript:'also bad'" } ] ],
"text/plain mixed-case javascript url drop on browser " + type);
yield expectLink(browser, "", "",
[ [ { type: "text/plain", data: "data:text/html,bad" } ] ],
"text/plain data url drop on browser " + type);
yield* expectLink(browser,
[],
[ [ { type: "text/plain",
data: "javascript:'bad'" } ] ],
"text/plain javascript url drop on browser " + type);
yield* expectLink(browser,
[],
[ [ { type: "text/plain",
data: "jAvascript:'also bad'" } ] ],
"text/plain mixed-case javascript url drop on browser " + type);
yield* expectLink(browser,
[],
[ [ { type: "text/plain",
data: "data:text/html,bad" } ] ],
"text/plain data url drop on browser " + type);
// dropping a chrome url should fail as we don't have a source node set,
// Dropping a chrome url should fail as we don't have a source node set,
// defaulting to a source of file:///
yield expectLink(browser, "", "",
[ [ { type: "text/x-moz-url", data: "chrome://browser/content/browser.xul" } ] ],
"text/x-moz-url chrome url drop on browser " + type);
yield* expectLink(browser,
[],
[ [ { type: "text/x-moz-url",
data: "chrome://browser/content/browser.xul" } ] ],
"text/x-moz-url chrome url drop on browser " + type);
if (browser.type == "content") {
yield ContentTask.spawn(browser, null, function() {
@ -138,18 +219,22 @@ function* dropLinksOnBrowser(browser, type) {
});
// stopPropagation should not prevent the browser link handling from occuring
yield expectLink(browser, "http://www.mozilla.org/", "http://www.mozilla.org/",
[ [ { type: "text/uri-list", data: "http://www.mozilla.org/" } ] ],
"text/x-moz-url drop on browser with stopPropagation drop event", true);
yield* expectLink(browser,
[ { url: "http://www.mozilla.org/",
name: "http://www.mozilla.org/" } ],
[ [ { type: "text/uri-list",
data: "http://www.mozilla.org/" } ] ],
"text/x-moz-url drop on browser with stopPropagation drop event", true);
yield ContentTask.spawn(browser, null, function() {
content.window.cancelMode = true;
});
// canceling the event, however, should prevent the link from being handled
yield expectLink(browser, "", "",
[ [ { type: "text/uri-list", data: "http://www.mozilla.org/" } ] ],
"text/x-moz-url drop on browser with cancelled drop event", true);
// Canceling the event, however, should prevent the link from being handled.
yield* expectLink(browser,
[],
[ [ { type: "text/uri-list", data: "http://www.mozilla.org/" } ] ],
"text/x-moz-url drop on browser with cancelled drop event", true);
}
}

Просмотреть файл

@ -1336,6 +1336,26 @@
]]>
</body>
</method>
<method name="dropLinks">
<parameter name="aLinksCount"/>
<parameter name="aLinks"/>
<body><![CDATA[
if (!this.droppedLinkHandler) {
return false;
}
let links = [];
for (let i = 0; i < aLinksCount; i += 3) {
links.push({
url: aLinks[i],
name: aLinks[i + 1],
type: aLinks[i + 2],
});
}
this.droppedLinkHandler(null, links);
return true;
]]></body>
</method>
</implementation>
<handlers>