Bug 1787713 - Improve the composition mochitests. r=aleca
Differential Revision: https://phabricator.services.mozilla.com/D155826 --HG-- extra : rebase_source : 60f8d677723b5341c4eb0080aa863530761b043c
This commit is contained in:
Родитель
bbb6fec901
Коммит
0de835acdd
|
@ -5973,6 +5973,13 @@ async function CompleteGenericSendMessage(msgType) {
|
|||
) {
|
||||
Services.telemetry.scalarAdd("tb.filelink.ignored", 1);
|
||||
}
|
||||
} else if (
|
||||
msgType == Ci.nsIMsgCompDeliverMode.Save ||
|
||||
msgType == Ci.nsIMsgCompDeliverMode.SaveAsDraft ||
|
||||
msgType == Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft ||
|
||||
msgType == Ci.nsIMsgCompDeliverMode.SaveAsTemplate
|
||||
) {
|
||||
window.dispatchEvent(new CustomEvent("aftersave"));
|
||||
}
|
||||
|
||||
if (sendError) {
|
||||
|
|
|
@ -14,6 +14,7 @@ var { gMockFilePicker, gMockFilePickReg } = ChromeUtils.import(
|
|||
var {
|
||||
close_compose_window,
|
||||
open_compose_new_mail,
|
||||
save_compose_message,
|
||||
setup_msg_contents,
|
||||
wait_for_compose_window,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
|
@ -513,10 +514,8 @@ async function createAndCloseDraftWithCloudAttachment(cloudFileAccount) {
|
|||
);
|
||||
|
||||
// Now close the message with saving it as draft.
|
||||
plan_for_modal_dialog("commonDialogWindow", click_save_message);
|
||||
cwc.window.goDoCommand("cmd_close");
|
||||
wait_for_modal_dialog("commonDialogWindow");
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
// The draft message was saved into Local Folders/Drafts.
|
||||
be_in_folder(gDrafts);
|
||||
|
|
|
@ -12,6 +12,7 @@ var {
|
|||
add_attachments,
|
||||
close_compose_window,
|
||||
open_compose_new_mail,
|
||||
save_compose_message,
|
||||
setup_msg_contents,
|
||||
wait_for_compose_window,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
|
@ -659,7 +660,7 @@ add_task(async function test_disabled_attachment_reminder() {
|
|||
* Bug 833909
|
||||
* Test reminder comes up when a draft with keywords is opened.
|
||||
*/
|
||||
add_task(function test_reminder_in_draft() {
|
||||
add_task(async function test_reminder_in_draft() {
|
||||
// Open a sample message with no attachment keywords.
|
||||
let cwc = open_compose_new_mail();
|
||||
setup_msg_contents(
|
||||
|
@ -682,10 +683,8 @@ add_task(function test_reminder_in_draft() {
|
|||
wait_for_reminder_state(cwc, true);
|
||||
|
||||
// Now close the message with saving it as draft.
|
||||
plan_for_modal_dialog("commonDialogWindow", click_save_message);
|
||||
cwc.window.goDoCommand("cmd_close");
|
||||
wait_for_modal_dialog("commonDialogWindow");
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
// The draft message was saved into Local Folders/Drafts.
|
||||
be_in_folder(gDrafts);
|
||||
|
|
|
@ -14,6 +14,7 @@ var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm");
|
|||
var {
|
||||
close_compose_window,
|
||||
open_compose_with_reply,
|
||||
save_compose_message,
|
||||
wait_for_compose_window,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
var {
|
||||
|
@ -110,13 +111,7 @@ add_task(async function test_wrong_reply_charset() {
|
|||
Assert.equal(getMsgHeaders(msg).get("").charset, "invalid-charset");
|
||||
|
||||
let rwc = open_compose_with_reply();
|
||||
// Ctrl+S = save as draft.
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
rwc.window
|
||||
);
|
||||
waitForSaveOperation(rwc);
|
||||
await save_compose_message(rwc.window);
|
||||
await TestUtils.waitForCondition(
|
||||
() => folder.getTotalMessages(false) == 2,
|
||||
"message saved to drafts folder"
|
||||
|
@ -139,12 +134,7 @@ add_task(async function test_wrong_reply_charset() {
|
|||
// Click on the "Edit" button in the draft notification.
|
||||
EventUtils.synthesizeMouseAtCenter(box.buttonContainer.firstElementChild, {});
|
||||
rwc = wait_for_compose_window();
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
rwc.window
|
||||
);
|
||||
waitForSaveOperation(rwc);
|
||||
await save_compose_message(rwc.window);
|
||||
close_compose_window(rwc);
|
||||
msg = select_click_row(0);
|
||||
await TestUtils.waitForCondition(
|
||||
|
@ -180,13 +170,7 @@ add_task(async function test_no_mojibake() {
|
|||
);
|
||||
|
||||
let rwc = open_compose_with_reply();
|
||||
// Ctrl+S = save as draft.
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
rwc.window
|
||||
);
|
||||
waitForSaveOperation(rwc);
|
||||
await save_compose_message(rwc.window);
|
||||
await TestUtils.waitForCondition(
|
||||
() => folder.getTotalMessages(false) == 2,
|
||||
"message saved to drafts folder"
|
||||
|
@ -218,12 +202,7 @@ add_task(async function test_no_mojibake() {
|
|||
// Click on the "Edit" button in the draft notification.
|
||||
EventUtils.synthesizeMouseAtCenter(box.buttonContainer.firstElementChild, {});
|
||||
rwc = wait_for_compose_window();
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
rwc.window
|
||||
);
|
||||
waitForSaveOperation(rwc);
|
||||
await save_compose_message(rwc.window);
|
||||
close_compose_window(rwc);
|
||||
msg = select_click_row(0);
|
||||
Assert.equal(
|
||||
|
|
|
@ -9,6 +9,7 @@ var {
|
|||
close_compose_window,
|
||||
get_msg_source,
|
||||
open_compose_new_mail,
|
||||
save_compose_message,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
var { be_in_folder, select_click_row, get_special_folder } = ChromeUtils.import(
|
||||
"resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
|
||||
|
@ -38,13 +39,12 @@ add_task(async function test_customHeaders() {
|
|||
inputs[2].value = "moderator@tinderbox.com";
|
||||
inputs[3].value = "<message-id-1234@tinderbox.com>";
|
||||
|
||||
cwc.window.SaveAsDraft();
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
await TestUtils.waitForCondition(
|
||||
() => draftsFolder.getTotalMessages(false) == 1,
|
||||
"message saved to drafts folder"
|
||||
);
|
||||
close_compose_window(cwc);
|
||||
|
||||
be_in_folder(draftsFolder);
|
||||
let draftMsg = select_click_row(0);
|
||||
|
|
|
@ -14,6 +14,7 @@ var {
|
|||
get_compose_body,
|
||||
get_msg_source,
|
||||
open_compose_new_mail,
|
||||
save_compose_message,
|
||||
setup_msg_contents,
|
||||
wait_for_compose_window,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
|
@ -83,14 +84,9 @@ add_task(async function test_open_draft_again() {
|
|||
|
||||
// Type something and save, then check that we only have one draft.
|
||||
cwc.type(cwc.e("messageEditor"), "Hello!");
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
cwc.window
|
||||
);
|
||||
waitForSaveOperation(cwc);
|
||||
Assert.equal(draftsFolder.getTotalMessages(false), 1);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
Assert.equal(draftsFolder.getTotalMessages(false), 1);
|
||||
|
||||
press_delete(mc); // clean up after ourselves
|
||||
});
|
||||
|
@ -136,10 +132,7 @@ async function internal_check_delivery_format(editDraft) {
|
|||
cwc.close_popup_sequence(formatMenu);
|
||||
}
|
||||
|
||||
cwc.window.SaveAsDraft();
|
||||
waitForSaveOperation(cwc);
|
||||
wait_for_window_focused(cwc.window);
|
||||
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
// Open a new composition see if the menu is again at default value, not the one
|
||||
|
@ -209,18 +202,13 @@ add_task(async function test_edit_as_new_in_draft() {
|
|||
let cwc = wait_for_compose_window();
|
||||
|
||||
cwc.type(cwc.e("messageEditor"), "Hello!");
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
cwc.window
|
||||
);
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => draftsFolder.getTotalMessages(false) == 2,
|
||||
"message saved to drafts folder"
|
||||
);
|
||||
close_compose_window(cwc);
|
||||
|
||||
// Clean up the created drafts and count again.
|
||||
press_delete(mc);
|
||||
|
@ -241,14 +229,13 @@ add_task(async function test_content_language_header() {
|
|||
"Hello, we speak en-US"
|
||||
);
|
||||
|
||||
cwc.window.SaveAsDraft();
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => draftsFolder.getTotalMessages(false) == 1,
|
||||
"message saved to drafts folder"
|
||||
);
|
||||
wait_for_window_focused(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
be_in_folder(draftsFolder);
|
||||
let draftMsg = select_click_row(0);
|
||||
|
@ -283,14 +270,13 @@ add_task(async function test_content_language_header_suppression() {
|
|||
"Hello, we speak blank"
|
||||
);
|
||||
|
||||
cwc.window.SaveAsDraft();
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => draftsFolder.getTotalMessages(false) == 1,
|
||||
"message saved to drafts folder"
|
||||
);
|
||||
wait_for_window_focused(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
be_in_folder(draftsFolder);
|
||||
let draftMsg = select_click_row(0);
|
||||
|
@ -327,15 +313,13 @@ add_task(async function test_remove_space_stuffing_format_flowed() {
|
|||
"NoSpace\n OneSpace\n TwoSpaces"
|
||||
);
|
||||
|
||||
cwc.window.SaveAsDraft();
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => draftsFolder.getTotalMessages(false) == 1,
|
||||
"message saved to drafts folder"
|
||||
);
|
||||
wait_for_window_focused(cwc.window);
|
||||
|
||||
close_compose_window(cwc);
|
||||
|
||||
be_in_folder(draftsFolder);
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ var {
|
|||
get_compose_body,
|
||||
open_compose_with_forward,
|
||||
open_compose_with_reply,
|
||||
save_compose_message,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
var {
|
||||
be_in_folder,
|
||||
|
@ -49,12 +50,9 @@ add_task(async function test_reply_to_eml_save_as_draft() {
|
|||
let replyWin = open_compose_with_reply(msgc);
|
||||
|
||||
// Ctrl+S saves as draft.
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
replyWin.window
|
||||
);
|
||||
waitForSaveOperation(replyWin);
|
||||
await save_compose_message(replyWin.window);
|
||||
close_compose_window(replyWin);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => gDrafts.getTotalMessages(false) == 1,
|
||||
"message saved to drafts folder"
|
||||
|
@ -68,7 +66,6 @@ add_task(async function test_reply_to_eml_save_as_draft() {
|
|||
}
|
||||
press_delete(); // Delete the draft.
|
||||
|
||||
close_compose_window(replyWin); // close compose window
|
||||
close_window(msgc); // close base .eml message
|
||||
});
|
||||
|
||||
|
@ -83,13 +80,9 @@ add_task(async function test_forward_eml_save_as_draft() {
|
|||
|
||||
let replyWin = open_compose_with_forward(msgc);
|
||||
|
||||
// Ctrl+S saves as draft.
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
replyWin.window
|
||||
);
|
||||
waitForSaveOperation(replyWin);
|
||||
await save_compose_message(replyWin.window);
|
||||
close_compose_window(replyWin);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => gDrafts.getTotalMessages(false) == 1,
|
||||
"message saved to drafts folder"
|
||||
|
@ -103,7 +96,6 @@ add_task(async function test_forward_eml_save_as_draft() {
|
|||
}
|
||||
press_delete(); // Delete the draft.
|
||||
|
||||
close_compose_window(replyWin); // close compose window
|
||||
close_window(msgc); // close base .eml message
|
||||
});
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ var {
|
|||
close_compose_window,
|
||||
get_msg_source,
|
||||
open_compose_with_forward_as_attachments,
|
||||
save_compose_message,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
var {
|
||||
be_in_folder,
|
||||
|
@ -44,14 +45,7 @@ async function forwardDirect(aFilePath, aExpectedText) {
|
|||
|
||||
let cwc = open_compose_with_forward_as_attachments(msgc);
|
||||
|
||||
// Ctrl+S saves as draft.
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
cwc.window
|
||||
);
|
||||
waitForSaveOperation(cwc);
|
||||
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
close_window(msgc);
|
||||
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
|
||||
var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm");
|
||||
|
||||
var { close_compose_window, open_compose_new_mail } = ChromeUtils.import(
|
||||
"resource://testing-common/mozmill/ComposeHelpers.jsm"
|
||||
);
|
||||
var {
|
||||
close_compose_window,
|
||||
open_compose_new_mail,
|
||||
save_compose_message,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
var {
|
||||
be_in_folder,
|
||||
get_special_folder,
|
||||
|
@ -106,18 +108,12 @@ add_task(async function test_basic_multipart_related() {
|
|||
wait_for_modal_dialog();
|
||||
wait_for_window_close();
|
||||
|
||||
// Ctrl+S = save as draft.
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
compWin.window
|
||||
);
|
||||
waitForSaveOperation(compWin);
|
||||
await save_compose_message(compWin.window);
|
||||
close_compose_window(compWin);
|
||||
await TestUtils.waitForCondition(
|
||||
() => gDrafts.getTotalMessages(false) == 1,
|
||||
"message saved to drafts folder"
|
||||
);
|
||||
close_compose_window(compWin);
|
||||
|
||||
// Make sure that the headers are right on this one.
|
||||
be_in_folder(gDrafts);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
var {
|
||||
close_compose_window,
|
||||
open_compose_new_mail,
|
||||
save_compose_message,
|
||||
wait_for_compose_window,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
var {
|
||||
|
@ -248,8 +249,7 @@ add_task(async function test_display_of_identities() {
|
|||
|
||||
// Bug 1152045, check that the email address from the selected identity
|
||||
// is properly used for the From field in the created message.
|
||||
cwc.window.SaveAsDraft();
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
be_in_folder(gDrafts);
|
||||
|
|
|
@ -12,6 +12,7 @@ var {
|
|||
close_compose_window,
|
||||
get_msg_source,
|
||||
open_compose_with_reply,
|
||||
save_compose_message,
|
||||
} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
|
||||
var {
|
||||
be_in_folder,
|
||||
|
@ -49,17 +50,13 @@ async function subtest_reply_format_flowed(aFlowed) {
|
|||
close_window(msgc);
|
||||
|
||||
// Now save the message as a draft.
|
||||
EventUtils.synthesizeKey(
|
||||
"s",
|
||||
{ shiftKey: false, accelKey: true },
|
||||
cwc.window
|
||||
);
|
||||
waitForSaveOperation(cwc);
|
||||
await save_compose_message(cwc.window);
|
||||
close_compose_window(cwc);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => gDrafts.getTotalMessages(false) == 1,
|
||||
"message saved to drafts folder"
|
||||
);
|
||||
close_compose_window(cwc);
|
||||
|
||||
// Now check the message content in the drafts folder.
|
||||
be_in_folder(gDrafts);
|
||||
|
|
|
@ -33,13 +33,6 @@ registerCleanupFunction(() => {
|
|||
}
|
||||
});
|
||||
|
||||
function waitForSaveOperation(cwc) {
|
||||
utils.waitFor(
|
||||
() => !cwc.window.gSaveOperationInProgress && !cwc.window.gWindowLock,
|
||||
"Saving of draft did not finish"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the body part of an MIME message.
|
||||
* @param {string} content - The message content.
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { save_compose_message } = ChromeUtils.import(
|
||||
"resource://testing-common/mozmill/ComposeHelpers.jsm"
|
||||
);
|
||||
const {
|
||||
open_message_from_file,
|
||||
be_in_folder,
|
||||
|
@ -126,13 +129,13 @@ add_task(async function testDraftReplyToEncryptedMessageKeepsRePrefix() {
|
|||
|
||||
let replyWindow = await replyWindowPromise;
|
||||
await BrowserTestUtils.waitForEvent(replyWindow, "focus", true);
|
||||
replyWindow.document.querySelector("#button-save").click();
|
||||
await save_compose_message(replyWindow);
|
||||
replyWindow.close();
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => draftsFolder.getTotalMessages(true) > 0,
|
||||
"message saved to drafts folder"
|
||||
);
|
||||
replyWindow.close();
|
||||
|
||||
let draftWindowPromise = waitForComposeWindow();
|
||||
select_click_row(0);
|
||||
|
|
|
@ -26,6 +26,7 @@ const EXPORTED_SYMBOLS = [
|
|||
"open_compose_with_reply",
|
||||
"open_compose_with_reply_to_all",
|
||||
"open_compose_with_reply_to_list",
|
||||
"save_compose_message",
|
||||
"setup_msg_contents",
|
||||
"type_in_composer",
|
||||
"wait_for_compose_window",
|
||||
|
@ -236,6 +237,17 @@ function open_compose_from_draft(aController) {
|
|||
return wait_for_compose_window();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the message being composed and waits for the save to complete.
|
||||
*
|
||||
* @param {Window} win - A messengercompose.xhtml window.
|
||||
*/
|
||||
async function save_compose_message(win) {
|
||||
let savePromise = BrowserTestUtils.waitForEvent(win, "aftersave");
|
||||
win.document.querySelector("#button-save").click();
|
||||
await savePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the requested compose window.
|
||||
*
|
||||
|
@ -292,46 +304,15 @@ function _wait_for_compose_window(aController, replyWindow) {
|
|||
aController = mc;
|
||||
}
|
||||
|
||||
let editor = replyWindow.window.document.querySelector("editor");
|
||||
|
||||
if (editor.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
|
||||
let editorObserver = {
|
||||
editorLoaded: false,
|
||||
|
||||
observe(aSubject, aTopic, aData) {
|
||||
if (aTopic == "obs_documentCreated") {
|
||||
this.editorLoaded = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
editor.commandManager.addCommandObserver(
|
||||
editorObserver,
|
||||
"obs_documentCreated"
|
||||
);
|
||||
|
||||
utils.waitFor(
|
||||
() => editorObserver.editorLoaded,
|
||||
"Timeout waiting for compose window editor to load",
|
||||
10000,
|
||||
100
|
||||
);
|
||||
|
||||
// Let the event queue clear.
|
||||
aController.sleep(0);
|
||||
|
||||
editor.commandManager.removeCommandObserver(
|
||||
editorObserver,
|
||||
"obs_documentCreated"
|
||||
);
|
||||
}
|
||||
|
||||
// Although the above is reasonable, testing has shown that the some elements
|
||||
// need to have a little longer to try and load the initial data.
|
||||
// As I can't see a simpler way at the moment, we'll just have to make it a
|
||||
// sleep :-(
|
||||
|
||||
aController.sleep(1000);
|
||||
utils.waitFor(
|
||||
() => Services.focus.activeWindow == replyWindow.window,
|
||||
"waiting for the compose window to have focus"
|
||||
);
|
||||
utils.waitFor(
|
||||
() => replyWindow.window.composeEditorReady,
|
||||
"waiting for the compose editor to be ready"
|
||||
);
|
||||
replyWindow.sleep(0);
|
||||
|
||||
return replyWindow;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче