diff --git a/addons/web/static/src/webclient/actions/action_service.js b/addons/web/static/src/webclient/actions/action_service.js index 6024a0fb82a..3f6998703e5 100644 --- a/addons/web/static/src/webclient/actions/action_service.js +++ b/addons/web/static/src/webclient/actions/action_service.js @@ -879,7 +879,9 @@ function makeActionManager(env) { if (url && !(url.startsWith("http") || url.startsWith("/"))) { url = "/" + url; } - if (action.target === "self") { + if (action.target === "download") { + browser.location.assign(url); + } else if (action.target === "self") { let willUnload = false; const onUnload = () => { willUnload = true; diff --git a/addons/web/static/tests/webclient/actions/url_action_tests.js b/addons/web/static/tests/webclient/actions/url_action_tests.js index 10d085ef49b..e79149da88b 100644 --- a/addons/web/static/tests/webclient/actions/url_action_tests.js +++ b/addons/web/static/tests/webclient/actions/url_action_tests.js @@ -3,14 +3,16 @@ import { registry } from "@web/core/registry"; import { makeTestEnv } from "../../helpers/mock_env"; import { setupWebClientRegistries, doAction, getActionManagerServerData } from "./../helpers"; -import { patchWithCleanup } from "@web/../tests/helpers/utils"; +import { getFixture, patchWithCleanup } from "@web/../tests/helpers/utils"; import { browser } from "@web/core/browser/browser"; +let target; let serverData; const serviceRegistry = registry.category("services"); QUnit.module("ActionManager", (hooks) => { hooks.beforeEach(() => { + target = getFixture(); serverData = getActionManagerServerData(); }); @@ -66,7 +68,7 @@ QUnit.module("ActionManager", (hooks) => { await doAction(env, { type: "ir.actions.act_url" }, options); assert.verifySteps(["browser open", "onClose"]); }); - + QUnit.test("execute an 'ir.actions.act_url' action with url javascript:", async (assert) => { assert.expect(2); patchWithCleanup(browser.location, { @@ -83,4 +85,21 @@ QUnit.module("ActionManager", (hooks) => { }); assert.verifySteps(["/javascript:alert()"]); }); + + QUnit.test("execute an 'ir.actions.act_url' action with target 'download'", async (assert) => { + patchWithCleanup(browser.location, { + assign: (url) => { + assert.step(url); + }, + }); + setupWebClientRegistries(); + const env = await makeTestEnv({ serverData }); + await doAction(env, { + type: "ir.actions.act_url", + target: "download", + url: "/my/test/url", + }); + assert.containsNone(target, ".o_blockUI", "ui should not be blocked"); + assert.verifySteps(["/my/test/url"]); + }); }); diff --git a/addons/web/views/partner_view.xml b/addons/web/views/partner_view.xml index 314960f307e..5c15aca0e87 100644 --- a/addons/web/views/partner_view.xml +++ b/addons/web/views/partner_view.xml @@ -10,7 +10,7 @@ action = { 'type': 'ir.actions.act_url', 'url': '/web/partner/%d/vcard' % record.id, - 'target': 'self', + 'target': 'download', } diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index a6fe1307295..388b8fa5d42 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -392,7 +392,7 @@ class IrActionsActUrl(models.Model): type = fields.Char(default='ir.actions.act_url') url = fields.Text(string='Action URL', required=True) - target = fields.Selection([('new', 'New Window'), ('self', 'This Window')], + target = fields.Selection([('new', 'New Window'), ('self', 'This Window'), ('download', 'Download')], string='Action Target', default='new', required=True) def _get_readable_fields(self):