зеркало из https://github.com/mozilla/pjs.git
Bug 693743: Some 3rd party add-ons are getting installed into the profile and are not disabled on start-up. r=Unfocused
This commit is contained in:
Родитель
b8cf9ef60b
Коммит
900e46b2fb
|
@ -77,7 +77,7 @@ pref("extensions.update.autoUpdateDefault", true);
|
|||
// Disable add-ons installed into the shared user and shared system areas by
|
||||
// default. This does not include the application directory. See the SCOPE
|
||||
// constants in AddonManager.jsm for values to use here
|
||||
pref("extensions.autoDisableScopes", 10);
|
||||
pref("extensions.autoDisableScopes", 15);
|
||||
|
||||
// Dictionary download preference
|
||||
pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");
|
||||
|
|
|
@ -1021,9 +1021,9 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t
|
|||
|
||||
installRDFFilename = "install.rdf"
|
||||
|
||||
extensionsRootDir = os.path.join(profileDir, "extensions")
|
||||
extensionsRootDir = os.path.join(profileDir, "extensions", "staged")
|
||||
if not os.path.isdir(extensionsRootDir):
|
||||
os.mkdir(extensionsRootDir)
|
||||
os.makedirs(extensionsRootDir)
|
||||
|
||||
if os.path.isfile(extensionSource):
|
||||
reader = ZipFileReader(extensionSource)
|
||||
|
|
|
@ -815,7 +815,7 @@ overlay chrome://navigator/content/navigator.xul chrome://mochikit/content/brows
|
|||
options.profilePath, "mochikit@mozilla.org")
|
||||
|
||||
# Write chrome.manifest.
|
||||
with open(os.path.join(options.profilePath, "extensions", "mochikit@mozilla.org", "chrome.manifest"), "a") as mfile:
|
||||
with open(os.path.join(options.profilePath, "extensions", "staged", "mochikit@mozilla.org", "chrome.manifest"), "a") as mfile:
|
||||
mfile.write(chrome)
|
||||
|
||||
def copyTestsJarToProfile(self, options):
|
||||
|
|
|
@ -556,6 +556,10 @@ AddonWrapper.prototype = {
|
|||
return AddonManager.SCOPE_PROFILE;
|
||||
},
|
||||
|
||||
get foreignInstall() {
|
||||
return false;
|
||||
},
|
||||
|
||||
// Lightweight themes are always compatible
|
||||
isCompatibleWith: function(appVersion, platformVersion) {
|
||||
return true;
|
||||
|
|
|
@ -339,6 +339,10 @@ PluginWrapper.prototype = {
|
|||
return true;
|
||||
},
|
||||
|
||||
get foreignInstall() {
|
||||
return true;
|
||||
},
|
||||
|
||||
isCompatibleWith: function(aAppVerison, aPlatformVersion) {
|
||||
return true;
|
||||
},
|
||||
|
|
|
@ -122,7 +122,7 @@ const TOOLKIT_ID = "toolkit@mozilla.org";
|
|||
|
||||
const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
|
||||
|
||||
const DB_SCHEMA = 5;
|
||||
const DB_SCHEMA = 6;
|
||||
const REQ_VERSION = 2;
|
||||
|
||||
#ifdef MOZ_COMPATIBILITY_NIGHTLY
|
||||
|
@ -146,7 +146,7 @@ const DB_METADATA = ["installDate", "updateDate", "size", "sourceURI",
|
|||
"releaseNotesURI", "applyBackgroundUpdates"];
|
||||
const DB_BOOL_METADATA = ["visible", "active", "userDisabled", "appDisabled",
|
||||
"pendingUninstall", "bootstrap", "skinnable",
|
||||
"softDisabled"];
|
||||
"softDisabled", "foreignInstall"];
|
||||
|
||||
const BOOTSTRAP_REASONS = {
|
||||
APP_STARTUP : 1,
|
||||
|
@ -2556,10 +2556,16 @@ var XPIProvider = {
|
|||
newAddon = aManifests[aInstallLocation.name][aId];
|
||||
|
||||
try {
|
||||
// Otherwise load the manifest from the add-on
|
||||
// Otherwise the add-on has appeared in the install location.
|
||||
if (!newAddon) {
|
||||
// Load the manifest from the add-on.
|
||||
let file = aInstallLocation.getLocationForID(aId);
|
||||
newAddon = loadManifestFromFile(file);
|
||||
|
||||
// The default theme is never a foreign install
|
||||
if (newAddon.type != "theme" || newAddon.internalName != XPIProvider.defaultSkin)
|
||||
newAddon.foreignInstall = true;
|
||||
|
||||
}
|
||||
// The add-on in the manifest should match the add-on ID.
|
||||
if (newAddon.id != aId)
|
||||
|
@ -2583,9 +2589,10 @@ var XPIProvider = {
|
|||
newAddon.installDate = aAddonState.mtime;
|
||||
newAddon.updateDate = aAddonState.mtime;
|
||||
|
||||
// Check if the add-on is in a scope where add-ons should install disabled
|
||||
// Check if the add-on is a foreign install and is in a scope where
|
||||
// add-ons that were dropped in should default to disabled.
|
||||
let disablingScopes = Prefs.getIntPref(PREF_EM_AUTO_DISABLED_SCOPES, 0);
|
||||
if (aInstallLocation.scope & disablingScopes)
|
||||
if (newAddon.foreignInstall && aInstallLocation.scope & disablingScopes)
|
||||
newAddon.userDisabled = true;
|
||||
|
||||
// If there is migration data then apply it.
|
||||
|
@ -2605,6 +2612,8 @@ var XPIProvider = {
|
|||
newAddon.sourceURI = aMigrateData.sourceURI;
|
||||
if ("releaseNotesURI" in aMigrateData)
|
||||
newAddon.releaseNotesURI = aMigrateData.releaseNotesURI;
|
||||
if ("foreignInstall" in aMigrateData)
|
||||
newAddon.foreignInstall = aMigrateData.foreignInstall;
|
||||
|
||||
// Some properties should only be migrated if the add-on hasn't changed.
|
||||
// The version property isn't a perfect check for this but covers the
|
||||
|
@ -3860,7 +3869,8 @@ const FIELDS_ADDON = "internal_id, id, location, version, type, internalName, "
|
|||
"iconURL, icon64URL, defaultLocale, visible, active, " +
|
||||
"userDisabled, appDisabled, pendingUninstall, descriptor, " +
|
||||
"installDate, updateDate, applyBackgroundUpdates, bootstrap, " +
|
||||
"skinnable, size, sourceURI, releaseNotesURI, softDisabled";
|
||||
"skinnable, size, sourceURI, releaseNotesURI, softDisabled, " +
|
||||
"foreignInstall";
|
||||
|
||||
/**
|
||||
* A helper function to log an SQL error.
|
||||
|
@ -4005,7 +4015,8 @@ var XPIDatabase = {
|
|||
":userDisabled, :appDisabled, :pendingUninstall, " +
|
||||
":descriptor, :installDate, :updateDate, " +
|
||||
":applyBackgroundUpdates, :bootstrap, :skinnable, " +
|
||||
":size, :sourceURI, :releaseNotesURI, :softDisabled)",
|
||||
":size, :sourceURI, :releaseNotesURI, :softDisabled, " +
|
||||
":foreignInstall)",
|
||||
addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
|
||||
"(:internal_id, :name, :locale)",
|
||||
addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
|
||||
|
@ -4371,6 +4382,9 @@ var XPIDatabase = {
|
|||
// Build a list of sql statements that might recover useful data from this
|
||||
// and future versions of the schema
|
||||
var sql = [];
|
||||
sql.push("SELECT internal_id, id, location, userDisabled, " +
|
||||
"softDisabled, installDate, version, applyBackgroundUpdates, " +
|
||||
"sourceURI, releaseNotesURI, foreignInstall FROM addon");
|
||||
sql.push("SELECT internal_id, id, location, userDisabled, " +
|
||||
"softDisabled, installDate, version, applyBackgroundUpdates, " +
|
||||
"sourceURI, releaseNotesURI FROM addon");
|
||||
|
@ -4413,6 +4427,8 @@ var XPIDatabase = {
|
|||
migrateData[row.location][row.id].sourceURI = row.sourceURI;
|
||||
if ("releaseNotesURI" in row)
|
||||
migrateData[row.location][row.id].releaseNotesURI = row.releaseNotesURI;
|
||||
if ("foreignInstall" in row)
|
||||
migrateData[row.location][row.id].foreignInstall = row.foreignInstall;
|
||||
}
|
||||
|
||||
var taStmt = this.connection.createStatement("SELECT id, minVersion, " +
|
||||
|
@ -4533,6 +4549,7 @@ var XPIDatabase = {
|
|||
"bootstrap INTEGER, skinnable INTEGER, " +
|
||||
"size INTEGER, sourceURI TEXT, " +
|
||||
"releaseNotesURI TEXT, softDisabled INTEGER, " +
|
||||
"foreignInstall INTEGER, " +
|
||||
"UNIQUE (id, location)");
|
||||
this.connection.createTable("targetApplication",
|
||||
"addon_internal_id INTEGER, " +
|
||||
|
@ -5207,6 +5224,7 @@ var XPIDatabase = {
|
|||
this.removeAddonMetadata(aOldAddon);
|
||||
aNewAddon.installDate = aOldAddon.installDate;
|
||||
aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
|
||||
aNewAddon.foreignInstall = aOldAddon.foreignInstall;
|
||||
aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled &&
|
||||
!aNewAddon.appDisabled)
|
||||
this.addAddonMetadata(aNewAddon, aDescriptor);
|
||||
|
@ -6822,6 +6840,7 @@ AddonInternal.prototype = {
|
|||
softDisabled: false,
|
||||
sourceURI: null,
|
||||
releaseNotesURI: null,
|
||||
foreignInstall: false,
|
||||
|
||||
get selectedLocale() {
|
||||
if (this._selectedLocale)
|
||||
|
@ -7094,7 +7113,7 @@ function AddonWrapper(aAddon) {
|
|||
|
||||
["id", "version", "type", "isCompatible", "isPlatformCompatible",
|
||||
"providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
|
||||
"softDisabled", "skinnable", "size"].forEach(function(aProp) {
|
||||
"softDisabled", "skinnable", "size", "foreignInstall"].forEach(function(aProp) {
|
||||
this.__defineGetter__(aProp, function() aAddon[aProp]);
|
||||
}, this);
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>addon7@tests.mozilla.org</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test 7</em:name>
|
||||
<em:description>Test Description</em:description>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>xpcshell@tests.mozilla.org</em:id>
|
||||
<em:minVersion>1</em:minVersion>
|
||||
<em:maxVersion>2</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
|
@ -89,6 +89,7 @@ function run_test_1() {
|
|||
do_check_eq(a1.version, "1.0");
|
||||
do_check_true(a1.isActive);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
do_check_false(a1.foreignInstall);
|
||||
|
||||
run_test_2();
|
||||
});
|
||||
|
@ -122,6 +123,7 @@ function run_test_3() {
|
|||
do_check_eq(a1.version, "2.0");
|
||||
do_check_true(a1.isActive);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
do_check_false(a1.foreignInstall);
|
||||
|
||||
run_test_4();
|
||||
});
|
||||
|
|
|
@ -163,6 +163,7 @@ function check_test_1() {
|
|||
do_check_true(do_get_addon("test_install1").exists());
|
||||
do_check_in_crash_annotation(a1.id, a1.version);
|
||||
do_check_eq(a1.size, ADDON1_SIZE);
|
||||
do_check_false(a1.foreignInstall);
|
||||
|
||||
do_check_eq(a1.sourceURI.spec,
|
||||
Services.io.newFileURI(do_get_addon("test_install1")).spec);
|
||||
|
@ -382,6 +383,7 @@ function check_test_5(install) {
|
|||
do_check_in_crash_annotation(a2.id, a2.version);
|
||||
do_check_eq(a2.sourceURI.spec,
|
||||
"http://localhost:4444/addons/test_install2_2.xpi");
|
||||
do_check_false(a2.foreignInstall);
|
||||
|
||||
do_check_eq(a2.installDate.getTime(), gInstallDate);
|
||||
// Update date should be later (or the same if this test is too fast)
|
||||
|
|
|
@ -111,29 +111,31 @@ function prepare_profile() {
|
|||
a6.userDisabled = true;
|
||||
|
||||
a6.findUpdates({
|
||||
onUpdateAvailable: function(aAddon, aInstall) {
|
||||
completeAllInstalls([aInstall], function() {
|
||||
restartManager();
|
||||
onUpdateAvailable: function(aAddon, aInstall6) {
|
||||
AddonManager.getInstallForURL("http://localhost:4444/addons/test_migrate4_7.xpi", function(aInstall7) {
|
||||
completeAllInstalls([aInstall6, aInstall7], function() {
|
||||
restartManager();
|
||||
|
||||
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
|
||||
"addon2@tests.mozilla.org",
|
||||
"addon3@tests.mozilla.org",
|
||||
"addon4@tests.mozilla.org",
|
||||
"addon5@tests.mozilla.org",
|
||||
"addon6@tests.mozilla.org"],
|
||||
function([a1, a2, a3, a4, a5, a6]) {
|
||||
a3.userDisabled = true;
|
||||
a4.userDisabled = false;
|
||||
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
|
||||
"addon2@tests.mozilla.org",
|
||||
"addon3@tests.mozilla.org",
|
||||
"addon4@tests.mozilla.org",
|
||||
"addon5@tests.mozilla.org",
|
||||
"addon6@tests.mozilla.org"],
|
||||
function([a1, a2, a3, a4, a5, a6]) {
|
||||
a3.userDisabled = true;
|
||||
a4.userDisabled = false;
|
||||
|
||||
a5.findUpdates({
|
||||
onUpdateFinished: function() {
|
||||
shutdownManager();
|
||||
a5.findUpdates({
|
||||
onUpdateFinished: function() {
|
||||
shutdownManager();
|
||||
|
||||
perform_migration();
|
||||
}
|
||||
}, AddonManager.UPDATE_WHEN_USER_REQUESTED);
|
||||
perform_migration();
|
||||
}
|
||||
}, AddonManager.UPDATE_WHEN_USER_REQUESTED);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, "application/x-xpinstall");
|
||||
}
|
||||
}, AddonManager.UPDATE_WHEN_USER_REQUESTED);
|
||||
});
|
||||
|
@ -166,14 +168,16 @@ function test_results() {
|
|||
"addon3@tests.mozilla.org",
|
||||
"addon4@tests.mozilla.org",
|
||||
"addon5@tests.mozilla.org",
|
||||
"addon6@tests.mozilla.org"],
|
||||
function([a1, a2, a3, a4, a5, a6]) {
|
||||
"addon6@tests.mozilla.org",
|
||||
"addon7@tests.mozilla.org"],
|
||||
function([a1, a2, a3, a4, a5, a6, a7]) {
|
||||
// addon1 was enabled
|
||||
do_check_neq(a1, null);
|
||||
do_check_false(a1.userDisabled);
|
||||
do_check_false(a1.appDisabled);
|
||||
do_check_true(a1.isActive);
|
||||
do_check_true(a1.applyBackgroundUpdates);
|
||||
do_check_true(a1.foreignInstall);
|
||||
|
||||
// addon2 was disabled
|
||||
do_check_neq(a2, null);
|
||||
|
@ -181,6 +185,7 @@ function test_results() {
|
|||
do_check_false(a2.appDisabled);
|
||||
do_check_false(a2.isActive);
|
||||
do_check_false(a2.applyBackgroundUpdates);
|
||||
do_check_true(a2.foreignInstall);
|
||||
|
||||
// addon3 was pending-disable in the database
|
||||
do_check_neq(a3, null);
|
||||
|
@ -188,6 +193,7 @@ function test_results() {
|
|||
do_check_false(a3.appDisabled);
|
||||
do_check_false(a3.isActive);
|
||||
do_check_true(a3.applyBackgroundUpdates);
|
||||
do_check_true(a3.foreignInstall);
|
||||
|
||||
// addon4 was pending-enable in the database
|
||||
do_check_neq(a4, null);
|
||||
|
@ -195,6 +201,7 @@ function test_results() {
|
|||
do_check_false(a4.appDisabled);
|
||||
do_check_true(a4.isActive);
|
||||
do_check_true(a4.applyBackgroundUpdates);
|
||||
do_check_true(a4.foreignInstall);
|
||||
|
||||
// addon5 was enabled in the database but needed a compatibiltiy update
|
||||
do_check_neq(a5, null);
|
||||
|
@ -202,6 +209,7 @@ function test_results() {
|
|||
do_check_false(a5.appDisabled);
|
||||
do_check_true(a5.isActive);
|
||||
do_check_true(a5.applyBackgroundUpdates);
|
||||
do_check_true(a5.foreignInstall);
|
||||
|
||||
// addon6 was disabled and compatible but a new version has been installed
|
||||
do_check_neq(a6, null);
|
||||
|
@ -210,8 +218,20 @@ function test_results() {
|
|||
do_check_false(a6.appDisabled);
|
||||
do_check_false(a6.isActive);
|
||||
do_check_true(a6.applyBackgroundUpdates);
|
||||
do_check_true(a6.foreignInstall);
|
||||
do_check_eq(a6.sourceURI.spec, "http://localhost:4444/addons/test_migrate4_6.xpi");
|
||||
do_check_eq(a6.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
|
||||
|
||||
// addon7 was installed manually
|
||||
do_check_neq(a7, null);
|
||||
do_check_eq(a7.version, "1.0");
|
||||
do_check_false(a7.userDisabled);
|
||||
do_check_false(a7.appDisabled);
|
||||
do_check_true(a7.isActive);
|
||||
do_check_true(a7.applyBackgroundUpdates);
|
||||
do_check_false(a7.foreignInstall);
|
||||
do_check_eq(a7.sourceURI.spec, "http://localhost:4444/addons/test_migrate4_7.xpi");
|
||||
do_check_eq(a7.releaseNotesURI, null);
|
||||
testserver.stop(do_test_finished);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -202,6 +202,7 @@ function run_test_1() {
|
|||
do_check_in_crash_annotation(addon1.id, addon1.version);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
do_check_eq(a1.sourceURI, null);
|
||||
do_check_true(a1.foreignInstall);
|
||||
|
||||
do_check_neq(a2, null);
|
||||
do_check_eq(a2.id, "addon2@tests.mozilla.org");
|
||||
|
@ -213,6 +214,7 @@ function run_test_1() {
|
|||
do_check_in_crash_annotation(addon2.id, addon2.version);
|
||||
do_check_eq(a2.scope, AddonManager.SCOPE_PROFILE);
|
||||
do_check_eq(a2.sourceURI, null);
|
||||
do_check_true(a2.foreignInstall);
|
||||
|
||||
do_check_neq(a3, null);
|
||||
do_check_eq(a3.id, "addon3@tests.mozilla.org");
|
||||
|
@ -224,6 +226,7 @@ function run_test_1() {
|
|||
do_check_in_crash_annotation(addon3.id, addon3.version);
|
||||
do_check_eq(a3.scope, AddonManager.SCOPE_PROFILE);
|
||||
do_check_eq(a3.sourceURI, null);
|
||||
do_check_true(a3.foreignInstall);
|
||||
|
||||
do_check_eq(a4, null);
|
||||
do_check_false(isExtensionInAddonsList(profileDir, "addon4@tests.mozilla.org"));
|
||||
|
@ -297,6 +300,7 @@ function run_test_2() {
|
|||
do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_UPGRADE));
|
||||
do_check_in_crash_annotation(addon1.id, a1.version);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
do_check_true(a1.foreignInstall);
|
||||
|
||||
do_check_neq(a2, null);
|
||||
do_check_eq(a2.id, "addon2@tests.mozilla.org");
|
||||
|
@ -308,6 +312,7 @@ function run_test_2() {
|
|||
do_check_true(hasFlag(a2.permissions, AddonManager.PERM_CAN_UPGRADE));
|
||||
do_check_in_crash_annotation(addon2.id, a2.version);
|
||||
do_check_eq(a2.scope, AddonManager.SCOPE_PROFILE);
|
||||
do_check_true(a2.foreignInstall);
|
||||
|
||||
do_check_eq(a3, null);
|
||||
do_check_false(isExtensionInAddonsList(profileDir, "addon3@tests.mozilla.org"));
|
||||
|
|
|
@ -94,12 +94,14 @@ function run_test() {
|
|||
function([d, t1, t2]) {
|
||||
do_check_neq(d, null);
|
||||
do_check_false(d.skinnable);
|
||||
do_check_false(d.foreignInstall);
|
||||
|
||||
do_check_neq(t1, null);
|
||||
do_check_false(t1.userDisabled);
|
||||
do_check_false(t1.appDisabled);
|
||||
do_check_true(t1.isActive);
|
||||
do_check_true(t1.skinnable);
|
||||
do_check_true(t1.foreignInstall);
|
||||
do_check_eq(t1.screenshots, null);
|
||||
do_check_true(isThemeInAddonsList(profileDir, t1.id));
|
||||
do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_DISABLE));
|
||||
|
@ -112,6 +114,7 @@ function run_test() {
|
|||
do_check_false(t2.appDisabled);
|
||||
do_check_false(t2.isActive);
|
||||
do_check_false(t2.skinnable);
|
||||
do_check_true(t2.foreignInstall);
|
||||
do_check_eq(t2.screenshots, null);
|
||||
do_check_false(isThemeInAddonsList(profileDir, t2.id));
|
||||
do_check_false(hasFlag(t2.permissions, AddonManager.PERM_CAN_DISABLE));
|
||||
|
|
|
@ -90,6 +90,7 @@ function run_test_1() {
|
|||
do_check_eq(a1.version, "1.0");
|
||||
do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT);
|
||||
do_check_eq(a1.releaseNotesURI, null);
|
||||
do_check_true(a1.foreignInstall);
|
||||
|
||||
a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
|
||||
|
||||
|
@ -219,6 +220,7 @@ function check_test_2() {
|
|||
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
|
||||
do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE);
|
||||
do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
|
||||
do_check_true(a1.foreignInstall);
|
||||
|
||||
a1.uninstall();
|
||||
restartManager();
|
||||
|
|
Загрузка…
Ссылка в новой задаче