зеркало из https://github.com/mozilla/gecko-dev.git
Bug 941085: File uploads support in Marionette
Adds support for W3C WebDriver compatible file uploads, where additional calls to sendKeys on <input type=file multiple> will append files, rather than reset the field. r=dburns --HG-- extra : rebase_source : 5f058fd1fcf767a5b45ebb6ba4c32994eb52212e
This commit is contained in:
Родитель
5abff40de2
Коммит
f785cf403f
|
@ -18,7 +18,6 @@ from runner import (
|
||||||
MarionetteTestResult,
|
MarionetteTestResult,
|
||||||
MarionetteTextTestRunner,
|
MarionetteTextTestRunner,
|
||||||
MemoryEnduranceTestCaseMixin,
|
MemoryEnduranceTestCaseMixin,
|
||||||
MozHttpd,
|
|
||||||
OptionParser,
|
OptionParser,
|
||||||
TestManifest,
|
TestManifest,
|
||||||
TestResult,
|
TestResult,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
from base import (
|
from base import (
|
||||||
B2GTestResultMixin, BaseMarionetteOptions, BaseMarionetteTestRunner,
|
B2GTestResultMixin, BaseMarionetteOptions, BaseMarionetteTestRunner,
|
||||||
Marionette, MarionetteTest, MarionetteTestResult, MarionetteTextTestRunner,
|
Marionette, MarionetteTest, MarionetteTestResult, MarionetteTextTestRunner,
|
||||||
MozHttpd, OptionParser, TestManifest, TestResult, TestResultCollection
|
OptionParser, TestManifest, TestResult, TestResultCollection
|
||||||
)
|
)
|
||||||
from mixins import (
|
from mixins import (
|
||||||
B2GTestCaseMixin, B2GTestResultMixin, EnduranceOptionsMixin,
|
B2GTestCaseMixin, B2GTestResultMixin, EnduranceOptionsMixin,
|
||||||
|
|
|
@ -20,12 +20,13 @@ from manifestparser import TestManifest
|
||||||
from manifestparser.filters import tags
|
from manifestparser.filters import tags
|
||||||
from marionette_driver.marionette import Marionette
|
from marionette_driver.marionette import Marionette
|
||||||
from mixins.b2g import B2GTestResultMixin, get_b2g_pid, get_dm
|
from mixins.b2g import B2GTestResultMixin, get_b2g_pid, get_dm
|
||||||
from mozhttpd import MozHttpd
|
|
||||||
from mozlog.structured.structuredlog import get_default_logger
|
from mozlog.structured.structuredlog import get_default_logger
|
||||||
from moztest.adapters.unit import StructuredTestRunner, StructuredTestResult
|
from moztest.adapters.unit import StructuredTestRunner, StructuredTestResult
|
||||||
from moztest.results import TestResultCollection, TestResult, relevant_line
|
from moztest.results import TestResultCollection, TestResult, relevant_line
|
||||||
import mozversion
|
import mozversion
|
||||||
|
|
||||||
|
import httpd
|
||||||
|
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
@ -638,24 +639,6 @@ class BaseMarionetteTestRunner(object):
|
||||||
self.skipped = 0
|
self.skipped = 0
|
||||||
self.failures = []
|
self.failures = []
|
||||||
|
|
||||||
def start_httpd(self, need_external_ip):
|
|
||||||
if self.server_root is None or os.path.isdir(self.server_root):
|
|
||||||
host = '127.0.0.1'
|
|
||||||
if need_external_ip:
|
|
||||||
host = moznetwork.get_ip()
|
|
||||||
docroot = self.server_root or os.path.join(os.path.dirname(here), 'www')
|
|
||||||
if not os.path.isdir(docroot):
|
|
||||||
raise Exception('Server root %s is not a valid path' % docroot)
|
|
||||||
self.httpd = MozHttpd(host=host,
|
|
||||||
port=0,
|
|
||||||
docroot=docroot)
|
|
||||||
self.httpd.start()
|
|
||||||
self.marionette.baseurl = 'http://%s:%d/' % (host, self.httpd.httpd.server_port)
|
|
||||||
self.logger.info('running webserver on %s' % self.marionette.baseurl)
|
|
||||||
else:
|
|
||||||
self.marionette.baseurl = self.server_root
|
|
||||||
self.logger.info('using content from %s' % self.marionette.baseurl)
|
|
||||||
|
|
||||||
def _build_kwargs(self):
|
def _build_kwargs(self):
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'device_serial': self.device_serial,
|
'device_serial': self.device_serial,
|
||||||
|
@ -780,7 +763,9 @@ setReq.onerror = function() {
|
||||||
|
|
||||||
if not self.httpd:
|
if not self.httpd:
|
||||||
self.logger.info("starting httpd")
|
self.logger.info("starting httpd")
|
||||||
self.start_httpd(need_external_ip)
|
self.httpd = self.create_httpd(need_external_ip)
|
||||||
|
self.marionette.baseurl = self.httpd.get_url()
|
||||||
|
self.logger.info("running httpd on %s" % self.marionette.baseurl)
|
||||||
|
|
||||||
for test in tests:
|
for test in tests:
|
||||||
self.add_test(test)
|
self.add_test(test)
|
||||||
|
@ -861,6 +846,15 @@ setReq.onerror = function() {
|
||||||
|
|
||||||
self.logger.suite_end()
|
self.logger.suite_end()
|
||||||
|
|
||||||
|
def create_httpd(self, need_external_ip):
|
||||||
|
ip = "127.0.0.1"
|
||||||
|
if need_external_ip:
|
||||||
|
ip = moznetwork.get_ip()
|
||||||
|
root = self.server_root or os.path.join(os.path.dirname(here), "www")
|
||||||
|
rv = httpd.FixtureServer(root, host=ip)
|
||||||
|
rv.start()
|
||||||
|
return rv
|
||||||
|
|
||||||
def add_test(self, test, expected='pass', test_container=None):
|
def add_test(self, test, expected='pass', test_container=None):
|
||||||
filepath = os.path.abspath(test)
|
filepath = os.path.abspath(test)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# 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 os
|
||||||
|
|
||||||
|
from mozhttpd import MozHttpd
|
||||||
|
|
||||||
|
|
||||||
|
class FixtureServer(object):
|
||||||
|
|
||||||
|
def __init__(self, root, host="127.0.0.1", port=0):
|
||||||
|
if not os.path.isdir(root):
|
||||||
|
raise Exception("Server root is not a valid path: %s" % root)
|
||||||
|
self.root = root
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self._server = None
|
||||||
|
|
||||||
|
def start(self, block=False):
|
||||||
|
if self.alive:
|
||||||
|
return
|
||||||
|
self._server = MozHttpd(host=self.host, port=self.port, docroot=self.root, urlhandlers=[
|
||||||
|
{"method": "POST", "path": "/file_upload", "function": upload_handler}])
|
||||||
|
self._server.start(block=block)
|
||||||
|
self.port = self._server.httpd.server_port
|
||||||
|
self.base_url = self.get_url()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if not self.alive:
|
||||||
|
return
|
||||||
|
self._server.stop()
|
||||||
|
self._server = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alive(self):
|
||||||
|
return self._server is not None
|
||||||
|
|
||||||
|
def get_url(self, path="/"):
|
||||||
|
if not self.alive:
|
||||||
|
raise "Server not started"
|
||||||
|
return self._server.get_url(path)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_handler(query, postdata=None):
|
||||||
|
return (200, {}, query.headers.getheader("Content-Type"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
root = os.path.join(os.path.dirname(here), "www")
|
||||||
|
httpd = FixtureServer(root, port=2829)
|
||||||
|
print "Started fixture server on http://%s:%d/" % (httpd.host, httpd.port)
|
||||||
|
try:
|
||||||
|
httpd.start(True)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
|
@ -0,0 +1,135 @@
|
||||||
|
# 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 contextlib
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
from tempfile import NamedTemporaryFile as tempfile
|
||||||
|
|
||||||
|
from marionette import MarionetteTestCase, skip
|
||||||
|
from marionette_driver import By, errors, expected
|
||||||
|
from marionette_driver.wait import Wait
|
||||||
|
|
||||||
|
|
||||||
|
single = "data:text/html,%s" % urllib.quote("<input type=file>")
|
||||||
|
multiple = "data:text/html,%s" % urllib.quote("<input type=file multiple>")
|
||||||
|
upload = lambda url: "data:text/html,%s" % urllib.quote("""
|
||||||
|
<form action='%s' method=post enctype='multipart/form-data'>
|
||||||
|
<input type=file>
|
||||||
|
<input type=submit>
|
||||||
|
</form>""" % url)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileUpload(MarionetteTestCase):
|
||||||
|
|
||||||
|
def test_sets_one_file(self):
|
||||||
|
self.marionette.navigate(single)
|
||||||
|
input = self.input
|
||||||
|
|
||||||
|
exp = None
|
||||||
|
with tempfile() as f:
|
||||||
|
input.send_keys(f.name)
|
||||||
|
exp = [f.name]
|
||||||
|
|
||||||
|
files = self.get_files(input)
|
||||||
|
self.assertEqual(len(files), 1)
|
||||||
|
self.assertFilesEqual(files, exp)
|
||||||
|
|
||||||
|
def test_sets_multiple_files(self):
|
||||||
|
self.marionette.navigate(multiple)
|
||||||
|
input = self.input
|
||||||
|
|
||||||
|
exp = None
|
||||||
|
with contextlib.nested(tempfile(), tempfile()) as (a, b):
|
||||||
|
input.send_keys(a.name)
|
||||||
|
input.send_keys(b.name)
|
||||||
|
exp = [a.name, b.name]
|
||||||
|
|
||||||
|
files = self.get_files(input)
|
||||||
|
self.assertEqual(len(files), 2)
|
||||||
|
self.assertFilesEqual(files, exp)
|
||||||
|
|
||||||
|
def test_sets_multiple_indentical_files(self):
|
||||||
|
self.marionette.navigate(multiple)
|
||||||
|
input = self.input
|
||||||
|
|
||||||
|
exp = []
|
||||||
|
with tempfile() as f:
|
||||||
|
input.send_keys(f.name)
|
||||||
|
input.send_keys(f.name)
|
||||||
|
exp = f.name
|
||||||
|
|
||||||
|
files = self.get_files(input)
|
||||||
|
self.assertEqual(len(files), 2)
|
||||||
|
self.assertFilesEqual(files, exp)
|
||||||
|
|
||||||
|
def test_clear_file(self):
|
||||||
|
self.marionette.navigate(single)
|
||||||
|
input = self.input
|
||||||
|
|
||||||
|
with tempfile() as f:
|
||||||
|
input.send_keys(f.name)
|
||||||
|
|
||||||
|
self.assertEqual(len(self.get_files(input)), 1)
|
||||||
|
input.clear()
|
||||||
|
self.assertEqual(len(self.get_files(input)), 0)
|
||||||
|
|
||||||
|
def test_clear_files(self):
|
||||||
|
self.marionette.navigate(multiple)
|
||||||
|
input = self.input
|
||||||
|
|
||||||
|
with contextlib.nested(tempfile(), tempfile()) as (a, b):
|
||||||
|
input.send_keys(a.name)
|
||||||
|
input.send_keys(b.name)
|
||||||
|
|
||||||
|
self.assertEqual(len(self.get_files(input)), 2)
|
||||||
|
input.clear()
|
||||||
|
self.assertEqual(len(self.get_files(input)), 0)
|
||||||
|
|
||||||
|
def test_illegal_file(self):
|
||||||
|
self.marionette.navigate(single)
|
||||||
|
with self.assertRaisesRegexp(errors.MarionetteException, "File not found"):
|
||||||
|
self.input.send_keys("rochefort")
|
||||||
|
|
||||||
|
def test_upload(self):
|
||||||
|
self.marionette.navigate(
|
||||||
|
upload(self.marionette.absolute_url("file_upload")))
|
||||||
|
|
||||||
|
with tempfile() as f:
|
||||||
|
f.write("camembert")
|
||||||
|
f.flush()
|
||||||
|
self.input.send_keys(f.name)
|
||||||
|
self.submit.click()
|
||||||
|
|
||||||
|
self.assertIn("multipart/form-data", self.body.text)
|
||||||
|
|
||||||
|
def find_inputs(self):
|
||||||
|
return self.marionette.find_elements("tag name", "input")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def input(self):
|
||||||
|
return self.find_inputs()[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def submit(self):
|
||||||
|
return self.find_inputs()[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self):
|
||||||
|
return Wait(self.marionette).until(
|
||||||
|
expected.element_present(By.TAG_NAME, "body"))
|
||||||
|
|
||||||
|
def get_files(self, el):
|
||||||
|
# This is horribly complex because (1) Marionette doesn't serialise arrays properly,
|
||||||
|
# and (2) accessing File.name in the content JS throws a permissions
|
||||||
|
# error.
|
||||||
|
fl = self.marionette.execute_script(
|
||||||
|
"return arguments[0].files", script_args=[el])
|
||||||
|
return [f["name"] for f in [v for k, v in fl.iteritems() if k.isdigit()]]
|
||||||
|
|
||||||
|
def assertFilesEqual(self, act, exp):
|
||||||
|
# File array returned from browser doesn't contain full path names,
|
||||||
|
# this cuts off the path of the expected files.
|
||||||
|
filenames = [f.rsplit("/", 0)[-1] for f in act]
|
||||||
|
self.assertListEqual(filenames, act)
|
|
@ -154,3 +154,6 @@ b2g = false
|
||||||
b2g = false
|
b2g = false
|
||||||
[test_teardown_context_preserved.py]
|
[test_teardown_context_preserved.py]
|
||||||
b2g = false
|
b2g = false
|
||||||
|
[test_file_upload.py]
|
||||||
|
b2g = false
|
||||||
|
skip-if = os == "win" # http://bugs.python.org/issue14574
|
||||||
|
|
|
@ -884,8 +884,9 @@ GeckoDriver.prototype.executeScriptInSandbox = function(
|
||||||
directInject,
|
directInject,
|
||||||
async,
|
async,
|
||||||
timeout) {
|
timeout) {
|
||||||
if (directInject && async && (timeout == null || timeout == 0))
|
if (directInject && async && (timeout == null || timeout == 0)) {
|
||||||
throw new TimeoutError("Please set a timeout");
|
throw new TimeoutError("Please set a timeout");
|
||||||
|
}
|
||||||
|
|
||||||
if (this.importedScripts.exists()) {
|
if (this.importedScripts.exists()) {
|
||||||
let stream = Cc["@mozilla.org/network/file-input-stream;1"]
|
let stream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||||
|
@ -2307,6 +2308,10 @@ GeckoDriver.prototype.getElementRect = function(cmd, resp) {
|
||||||
GeckoDriver.prototype.sendKeysToElement = function(cmd, resp) {
|
GeckoDriver.prototype.sendKeysToElement = function(cmd, resp) {
|
||||||
let {id, value} = cmd.parameters;
|
let {id, value} = cmd.parameters;
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
throw new IllegalArgumentError(`Expected character sequence: ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
switch (this.context) {
|
switch (this.context) {
|
||||||
case Context.CHROME:
|
case Context.CHROME:
|
||||||
let win = this.getCurrentWindow();
|
let win = this.getCurrentWindow();
|
||||||
|
@ -2322,7 +2327,36 @@ GeckoDriver.prototype.sendKeysToElement = function(cmd, resp) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Context.CONTENT:
|
case Context.CONTENT:
|
||||||
|
let err;
|
||||||
|
let listener = function(msg) {
|
||||||
|
this.mm.removeMessageListener("Marionette:setElementValue", listener);
|
||||||
|
|
||||||
|
let val = msg.data.value;
|
||||||
|
let el = msg.objects.element;
|
||||||
|
let win = this.getCurrentWindow();
|
||||||
|
|
||||||
|
if (el.type == "file") {
|
||||||
|
Cu.importGlobalProperties(["File"]);
|
||||||
|
let fs = Array.prototype.slice.call(el.files);
|
||||||
|
let file;
|
||||||
|
try {
|
||||||
|
file = new File(val);
|
||||||
|
} catch (e) {
|
||||||
|
err = new IllegalArgumentError(`File not found: ${val}`);
|
||||||
|
}
|
||||||
|
fs.push(file);
|
||||||
|
el.mozSetFileArray(fs);
|
||||||
|
} else {
|
||||||
|
el.value = val;
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
this.mm.addMessageListener("Marionette:setElementValue", listener);
|
||||||
yield this.listener.sendKeysToElement({id: id, value: value});
|
yield this.listener.sendKeysToElement({id: id, value: value});
|
||||||
|
this.mm.removeMessageListener("Marionette:setElementValue", listener);
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,11 @@ const errors = [
|
||||||
"ElementNotVisibleError",
|
"ElementNotVisibleError",
|
||||||
"FrameSendFailureError",
|
"FrameSendFailureError",
|
||||||
"FrameSendNotInitializedError",
|
"FrameSendNotInitializedError",
|
||||||
|
<<<<<<< dest
|
||||||
"InvalidElementStateError",
|
"InvalidElementStateError",
|
||||||
|
=======
|
||||||
|
"IllegalArgumentError",
|
||||||
|
>>>>>>> source
|
||||||
"JavaScriptError",
|
"JavaScriptError",
|
||||||
"NoAlertOpenError",
|
"NoAlertOpenError",
|
||||||
"NoSuchElementError",
|
"NoSuchElementError",
|
||||||
|
@ -295,11 +299,23 @@ this.UnsupportedOperationError = function(msg) {
|
||||||
};
|
};
|
||||||
UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype);
|
UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype);
|
||||||
|
|
||||||
|
this.IllegalArgumentError = function(msg) {
|
||||||
|
WebDriverError.call(this, msg);
|
||||||
|
this.name = "IllegalArgumentError";
|
||||||
|
this.status = "illegal argument";
|
||||||
|
this.code = 13; // unknown error
|
||||||
|
};
|
||||||
|
IllegalArgumentError.prototype = Object.create(WebDriverError.prototype);
|
||||||
|
|
||||||
const errorObjs = [
|
const errorObjs = [
|
||||||
this.ElementNotVisibleError,
|
this.ElementNotVisibleError,
|
||||||
this.FrameSendFailureError,
|
this.FrameSendFailureError,
|
||||||
this.FrameSendNotInitializedError,
|
this.FrameSendNotInitializedError,
|
||||||
|
<<<<<<< dest
|
||||||
this.InvalidElementStateError,
|
this.InvalidElementStateError,
|
||||||
|
=======
|
||||||
|
this.IllegalArgumentError,
|
||||||
|
>>>>>>> source
|
||||||
this.JavaScriptError,
|
this.JavaScriptError,
|
||||||
this.NoAlertOpenError,
|
this.NoAlertOpenError,
|
||||||
this.NoSuchElementError,
|
this.NoSuchElementError,
|
||||||
|
|
|
@ -35,9 +35,10 @@ let isB2G = false;
|
||||||
|
|
||||||
let marionetteTestName;
|
let marionetteTestName;
|
||||||
let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIDOMWindowUtils);
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
let listenerId = null; //unique ID of this listener
|
let listenerId = null; // unique ID of this listener
|
||||||
let curFrame = content;
|
let curFrame = content;
|
||||||
|
let isRemoteBrowser = () => curFrame.contentWindow !== null;
|
||||||
let previousFrame = null;
|
let previousFrame = null;
|
||||||
let elementManager = new ElementManager([]);
|
let elementManager = new ElementManager([]);
|
||||||
let accessibility = new Accessibility();
|
let accessibility = new Accessibility();
|
||||||
|
@ -1548,11 +1549,39 @@ function isElementSelected(msg) {
|
||||||
*/
|
*/
|
||||||
function sendKeysToElement(msg) {
|
function sendKeysToElement(msg) {
|
||||||
let command_id = msg.json.command_id;
|
let command_id = msg.json.command_id;
|
||||||
|
let val = msg.json.value;
|
||||||
|
|
||||||
let el = elementManager.getKnownElement(msg.json.id, curFrame);
|
let el = elementManager.getKnownElement(msg.json.id, curFrame);
|
||||||
let keysToSend = msg.json.value;
|
if (el.type == "file") {
|
||||||
|
let p = val.join("");
|
||||||
|
|
||||||
utils.sendKeysToElement(curFrame, el, keysToSend, sendOk, sendError, command_id);
|
// for some reason using mozSetFileArray doesn't work with e10s
|
||||||
|
// enabled (probably a bug), but a workaround is to elevate the element's
|
||||||
|
// privileges with SpecialPowers
|
||||||
|
//
|
||||||
|
// this extra branch can be removed when the e10s bug 1149998 is fixed
|
||||||
|
if (isRemoteBrowser()) {
|
||||||
|
let fs = Array.prototype.slice.call(el.files);
|
||||||
|
let file;
|
||||||
|
try {
|
||||||
|
file = new File(p);
|
||||||
|
} catch (e) {
|
||||||
|
let err = new IllegalArgumentError(`File not found: ${val}`);
|
||||||
|
sendError(err.message, err.code, err.stack, command_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fs.push(file);
|
||||||
|
|
||||||
|
let wel = new SpecialPowers(utils.window).wrap(el);
|
||||||
|
wel.mozSetFileArray(fs);
|
||||||
|
} else {
|
||||||
|
sendSyncMessage("Marionette:setElementValue", {value: p}, {element: el});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendOk(command_id);
|
||||||
|
} else {
|
||||||
|
utils.sendKeysToElement(curFrame, el, val, sendOk, sendError, command_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1582,10 +1611,13 @@ function clearElement(msg) {
|
||||||
let command_id = msg.json.command_id;
|
let command_id = msg.json.command_id;
|
||||||
try {
|
try {
|
||||||
let el = elementManager.getKnownElement(msg.json.id, curFrame);
|
let el = elementManager.getKnownElement(msg.json.id, curFrame);
|
||||||
utils.clearElement(el);
|
if (el.type == "file") {
|
||||||
|
el.value = null;
|
||||||
|
} else {
|
||||||
|
utils.clearElement(el);
|
||||||
|
}
|
||||||
sendOk(command_id);
|
sendOk(command_id);
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
|
||||||
sendError(e.message, e.code, e.stack, command_id);
|
sendError(e.message, e.code, e.stack, command_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче