зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1273203. Remove the getStructuredData API from BrowserElement. r=kanru
This commit is contained in:
Родитель
19ddb4f17c
Коммит
cf35c3398f
|
@ -26,7 +26,6 @@ const METHODS = {
|
|||
findNext: {},
|
||||
clearMatch: {},
|
||||
executeScript: { alwaysFails: true }, // needs browser:universalxss
|
||||
getStructuredData: {},
|
||||
getWebManifest: {},
|
||||
mute: {},
|
||||
unmute: {},
|
||||
|
|
|
@ -13,7 +13,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Microformats.js");
|
||||
Cu.import("resource://gre/modules/ExtensionContent.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "acs",
|
||||
|
@ -285,7 +284,6 @@ BrowserElementChild.prototype = {
|
|||
"get-audio-channel-muted": this._recvGetAudioChannelMuted,
|
||||
"set-audio-channel-muted": this._recvSetAudioChannelMuted,
|
||||
"get-is-audio-channel-active": this._recvIsAudioChannelActive,
|
||||
"get-structured-data": this._recvGetStructuredData,
|
||||
"get-web-manifest": this._recvGetWebManifest,
|
||||
}
|
||||
|
||||
|
@ -1546,300 +1544,6 @@ BrowserElementChild.prototype = {
|
|||
sendAsyncMsg('got-set-input-method-active', msgData);
|
||||
},
|
||||
|
||||
_processMicroformatValue(field, value) {
|
||||
if (['node', 'resolvedNode', 'semanticType'].includes(field)) {
|
||||
return null;
|
||||
} else if (Array.isArray(value)) {
|
||||
var result = value.map(i => this._processMicroformatValue(field, i))
|
||||
.filter(i => i !== null);
|
||||
return result.length ? result : null;
|
||||
} else if (typeof value == 'string') {
|
||||
return value;
|
||||
} else if (typeof value == 'object' && value !== null) {
|
||||
return this._processMicroformatItem(value);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// This function takes legacy Microformat data (hCard and hCalendar)
|
||||
// and produces the same result that the equivalent Microdata data
|
||||
// would produce.
|
||||
_processMicroformatItem(microformatData) {
|
||||
var result = {};
|
||||
|
||||
if (microformatData.semanticType == 'geo') {
|
||||
return microformatData.latitude + ';' + microformatData.longitude;
|
||||
}
|
||||
|
||||
if (microformatData.semanticType == 'hCard') {
|
||||
result.type = ["http://microformats.org/profile/hcard"];
|
||||
} else if (microformatData.semanticType == 'hCalendar') {
|
||||
result.type = ["http://microformats.org/profile/hcalendar#vevent"];
|
||||
}
|
||||
|
||||
for (let field of Object.getOwnPropertyNames(microformatData)) {
|
||||
var processed = this._processMicroformatValue(field, microformatData[field]);
|
||||
if (processed === null) {
|
||||
continue;
|
||||
}
|
||||
if (!result.properties) {
|
||||
result.properties = {};
|
||||
}
|
||||
if (Array.isArray(processed)) {
|
||||
result.properties[field] = processed;
|
||||
} else {
|
||||
result.properties[field] = [processed];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
_findItemProperties: function(node, properties, alreadyProcessed) {
|
||||
if (node.itemProp) {
|
||||
var value;
|
||||
|
||||
if (node.itemScope) {
|
||||
value = this._processItem(node, alreadyProcessed);
|
||||
} else {
|
||||
value = node.itemValue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < node.itemProp.length; ++i) {
|
||||
var property = node.itemProp[i];
|
||||
if (!properties[property]) {
|
||||
properties[property] = [];
|
||||
}
|
||||
|
||||
properties[property].push(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.itemScope) {
|
||||
var childNodes = node.childNodes;
|
||||
for (var childNode of childNodes) {
|
||||
this._findItemProperties(childNode, properties, alreadyProcessed);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_processItem: function(node, alreadyProcessed = []) {
|
||||
if (alreadyProcessed.includes(node)) {
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
alreadyProcessed.push(node);
|
||||
|
||||
var result = {};
|
||||
|
||||
if (node.itemId) {
|
||||
result.id = node.itemId;
|
||||
}
|
||||
if (node.itemType) {
|
||||
result.type = [];
|
||||
for (let i = 0; i < node.itemType.length; ++i) {
|
||||
result.type.push(node.itemType[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var properties = {};
|
||||
|
||||
var childNodes = node.childNodes;
|
||||
for (var childNode of childNodes) {
|
||||
this._findItemProperties(childNode, properties, alreadyProcessed);
|
||||
}
|
||||
|
||||
if (node.itemRef) {
|
||||
for (let i = 0; i < node.itemRef.length; ++i) {
|
||||
var refNode = content.document.getElementById(node.itemRef[i]);
|
||||
this._findItemProperties(refNode, properties, alreadyProcessed);
|
||||
}
|
||||
}
|
||||
|
||||
result.properties = properties;
|
||||
return result;
|
||||
},
|
||||
|
||||
_recvGetStructuredData: function(data) {
|
||||
var result = {
|
||||
items: []
|
||||
};
|
||||
|
||||
var microdataItems = content.document.getItems();
|
||||
|
||||
for (let microdataItem of microdataItems) {
|
||||
result.items.push(this._processItem(microdataItem));
|
||||
}
|
||||
|
||||
var hCardItems = Microformats.get("hCard", content.document);
|
||||
for (let hCardItem of hCardItems) {
|
||||
if (!hCardItem.node.itemScope) { // If it's also marked with Microdata, ignore the Microformat
|
||||
result.items.push(this._processMicroformatItem(hCardItem));
|
||||
}
|
||||
}
|
||||
|
||||
var hCalendarItems = Microformats.get("hCalendar", content.document);
|
||||
for (let hCalendarItem of hCalendarItems) {
|
||||
if (!hCalendarItem.node.itemScope) { // If it's also marked with Microdata, ignore the Microformat
|
||||
result.items.push(this._processMicroformatItem(hCalendarItem));
|
||||
}
|
||||
}
|
||||
|
||||
var resultString = JSON.stringify(result);
|
||||
|
||||
sendAsyncMsg('got-structured-data', {
|
||||
id: data.json.id,
|
||||
successRv: resultString
|
||||
});
|
||||
},
|
||||
|
||||
_processMicroformatValue(field, value) {
|
||||
if (['node', 'resolvedNode', 'semanticType'].includes(field)) {
|
||||
return null;
|
||||
} else if (Array.isArray(value)) {
|
||||
var result = value.map(i => this._processMicroformatValue(field, i))
|
||||
.filter(i => i !== null);
|
||||
return result.length ? result : null;
|
||||
} else if (typeof value == 'string') {
|
||||
return value;
|
||||
} else if (typeof value == 'object' && value !== null) {
|
||||
return this._processMicroformatItem(value);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// This function takes legacy Microformat data (hCard and hCalendar)
|
||||
// and produces the same result that the equivalent Microdata data
|
||||
// would produce.
|
||||
_processMicroformatItem(microformatData) {
|
||||
var result = {};
|
||||
|
||||
if (microformatData.semanticType == 'geo') {
|
||||
return microformatData.latitude + ';' + microformatData.longitude;
|
||||
}
|
||||
|
||||
if (microformatData.semanticType == 'hCard') {
|
||||
result.type = ["http://microformats.org/profile/hcard"];
|
||||
} else if (microformatData.semanticType == 'hCalendar') {
|
||||
result.type = ["http://microformats.org/profile/hcalendar#vevent"];
|
||||
}
|
||||
|
||||
for (let field of Object.getOwnPropertyNames(microformatData)) {
|
||||
var processed = this._processMicroformatValue(field, microformatData[field]);
|
||||
if (processed === null) {
|
||||
continue;
|
||||
}
|
||||
if (!result.properties) {
|
||||
result.properties = {};
|
||||
}
|
||||
if (Array.isArray(processed)) {
|
||||
result.properties[field] = processed;
|
||||
} else {
|
||||
result.properties[field] = [processed];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
_findItemProperties: function(node, properties, alreadyProcessed) {
|
||||
if (node.itemProp) {
|
||||
var value;
|
||||
|
||||
if (node.itemScope) {
|
||||
value = this._processItem(node, alreadyProcessed);
|
||||
} else {
|
||||
value = node.itemValue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < node.itemProp.length; ++i) {
|
||||
var property = node.itemProp[i];
|
||||
if (!properties[property]) {
|
||||
properties[property] = [];
|
||||
}
|
||||
|
||||
properties[property].push(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.itemScope) {
|
||||
var childNodes = node.childNodes;
|
||||
for (var childNode of childNodes) {
|
||||
this._findItemProperties(childNode, properties, alreadyProcessed);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_processItem: function(node, alreadyProcessed = []) {
|
||||
if (alreadyProcessed.includes(node)) {
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
alreadyProcessed.push(node);
|
||||
|
||||
var result = {};
|
||||
|
||||
if (node.itemId) {
|
||||
result.id = node.itemId;
|
||||
}
|
||||
if (node.itemType) {
|
||||
result.type = [];
|
||||
for (let i = 0; i < node.itemType.length; ++i) {
|
||||
result.type.push(node.itemType[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var properties = {};
|
||||
|
||||
var childNodes = node.childNodes;
|
||||
for (var childNode of childNodes) {
|
||||
this._findItemProperties(childNode, properties, alreadyProcessed);
|
||||
}
|
||||
|
||||
if (node.itemRef) {
|
||||
for (let i = 0; i < node.itemRef.length; ++i) {
|
||||
var refNode = content.document.getElementById(node.itemRef[i]);
|
||||
this._findItemProperties(refNode, properties, alreadyProcessed);
|
||||
}
|
||||
}
|
||||
|
||||
result.properties = properties;
|
||||
return result;
|
||||
},
|
||||
|
||||
_recvGetStructuredData: function(data) {
|
||||
var result = {
|
||||
items: []
|
||||
};
|
||||
|
||||
var microdataItems = content.document.getItems();
|
||||
|
||||
for (let microdataItem of microdataItems) {
|
||||
result.items.push(this._processItem(microdataItem));
|
||||
}
|
||||
|
||||
var hCardItems = Microformats.get("hCard", content.document);
|
||||
for (let hCardItem of hCardItems) {
|
||||
if (!hCardItem.node.itemScope) { // If it's also marked with Microdata, ignore the Microformat
|
||||
result.items.push(this._processMicroformatItem(hCardItem));
|
||||
}
|
||||
}
|
||||
|
||||
var hCalendarItems = Microformats.get("hCalendar", content.document);
|
||||
for (let hCalendarItem of hCalendarItems) {
|
||||
if (!hCalendarItem.node.itemScope) { // If it's also marked with Microdata, ignore the Microformat
|
||||
result.items.push(this._processMicroformatItem(hCalendarItem));
|
||||
}
|
||||
}
|
||||
|
||||
var resultString = JSON.stringify(result);
|
||||
|
||||
sendAsyncMsg('got-structured-data', {
|
||||
id: data.json.id,
|
||||
successRv: resultString
|
||||
});
|
||||
},
|
||||
|
||||
// The docShell keeps a weak reference to the progress listener, so we need
|
||||
// to keep a strong ref to it ourselves.
|
||||
_progressListener: {
|
||||
|
|
|
@ -389,7 +389,6 @@ BrowserElementParent.prototype = {
|
|||
"got-audio-channel-muted": this._gotDOMRequestResult,
|
||||
"got-set-audio-channel-muted": this._gotDOMRequestResult,
|
||||
"got-is-audio-channel-active": this._gotDOMRequestResult,
|
||||
"got-structured-data": this._gotDOMRequestResult,
|
||||
"got-web-manifest": this._gotDOMRequestResult,
|
||||
};
|
||||
|
||||
|
@ -1208,8 +1207,6 @@ BrowserElementParent.prototype = {
|
|||
return req;
|
||||
},
|
||||
|
||||
getStructuredData: defineDOMRequestMethod('get-structured-data'),
|
||||
|
||||
getWebManifest: defineDOMRequestMethod('get-web-manifest'),
|
||||
/**
|
||||
* Called when the visibility of the window which owns this iframe changes.
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
/* Any copyright is dedicated to the public domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/*globals async, is, SimpleTest, browserElementTestHelpers*/
|
||||
|
||||
// Bug 119580 - getStructuredData tests
|
||||
'use strict';
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
browserElementTestHelpers.setEnabledPref(true);
|
||||
browserElementTestHelpers.addPermission();
|
||||
|
||||
const EMPTY_URL = 'file_empty.html';
|
||||
const MICRODATA_URL = 'file_microdata.html';
|
||||
const MICRODATA_ITEMREF_URL = 'file_microdata_itemref.html';
|
||||
const MICRODATA_BAD_ITEMREF_URL = 'file_microdata_bad_itemref.html';
|
||||
const MICROFORMATS_URL = 'file_microformats.html';
|
||||
|
||||
var test1 = async(function* () {
|
||||
var structuredData = yield requestStructuredData(EMPTY_URL);
|
||||
is(structuredData.items && structuredData.items.length, 0,
|
||||
'There should be 0 items.');
|
||||
});
|
||||
|
||||
var test2 = async(function* () {
|
||||
var structuredData = yield requestStructuredData(MICRODATA_URL);
|
||||
is(structuredData.items && structuredData.items.length, 2,
|
||||
'There should be two items.');
|
||||
is(structuredData.items[0].type[0], 'http://schema.org/Recipe',
|
||||
'Can get item type.');
|
||||
is(structuredData.items[0].properties['datePublished'][0], '2009-05-08',
|
||||
'Can get item property.');
|
||||
is(structuredData.items[1]
|
||||
.properties["aggregateRating"][0]
|
||||
.properties["ratingValue"][0],
|
||||
'4', 'Can get nested item property.');
|
||||
});
|
||||
|
||||
var test3 = async(function* () {
|
||||
var structuredData = yield requestStructuredData(MICROFORMATS_URL);
|
||||
is(structuredData.items && structuredData.items.length, 2,
|
||||
'There should be two items.');
|
||||
is(structuredData.items[0].type[0], 'http://microformats.org/profile/hcard',
|
||||
'Got hCard object.');
|
||||
is(structuredData.items[0]
|
||||
.properties["adr"][0]
|
||||
.properties["country-name"][0],
|
||||
'France', 'Can read hCard properties.');
|
||||
is(structuredData.items[0]
|
||||
.properties["adr"][0]
|
||||
.properties["type"]
|
||||
.includes('home') &&
|
||||
structuredData.items[0]
|
||||
.properties["adr"][0]
|
||||
.properties["type"]
|
||||
.includes('postal'),
|
||||
true, 'Property can contain multiple values.');
|
||||
is(structuredData.items[0]
|
||||
.properties["geo"][0],
|
||||
'48.816667;2.366667', 'Geo value is formatted as per WHATWG spec.');
|
||||
|
||||
is(structuredData.items[1].type[0],
|
||||
'http://microformats.org/profile/hcalendar#vevent',
|
||||
'Got hCalendar object.');
|
||||
is(structuredData.items[1]
|
||||
.properties["dtstart"][0],
|
||||
'2005-10-05', 'Can read hCalendar properties');
|
||||
});
|
||||
|
||||
var test4 = async(function* () {
|
||||
var structuredData = yield requestStructuredData(MICRODATA_ITEMREF_URL);
|
||||
is(structuredData.items[0].properties["license"][0],
|
||||
'http://www.opensource.org/licenses/mit-license.php', 'itemref works.');
|
||||
is(structuredData.items[1].properties["license"][0],
|
||||
'http://www.opensource.org/licenses/mit-license.php',
|
||||
'Two items can successfully share an itemref.');
|
||||
});
|
||||
|
||||
var test5 = async(function* () {
|
||||
var structuredData = yield requestStructuredData(MICRODATA_BAD_ITEMREF_URL);
|
||||
is(structuredData.items[0]
|
||||
.properties["band"][0]
|
||||
.properties["cycle"][0]
|
||||
.properties["band"][0],
|
||||
'ERROR', 'Cyclic reference should be detected as an error.');
|
||||
});
|
||||
|
||||
Promise
|
||||
.all([test1(), test2(), test3(), test4(), test5()])
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
function requestStructuredData(url) {
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('mozbrowser', 'true');
|
||||
iframe.src = url;
|
||||
document.body.appendChild(iframe);
|
||||
return new Promise((resolve, reject) => {
|
||||
iframe.addEventListener('mozbrowserloadend', function loadend() {
|
||||
iframe.removeEventListener('mozbrowserloadend', loadend);
|
||||
SimpleTest.executeSoon(() => {
|
||||
var req = iframe.getStructuredData();
|
||||
req.onsuccess = (ev) => {
|
||||
document.body.removeChild(iframe);
|
||||
resolve(JSON.parse(req.result));
|
||||
};
|
||||
req.onerror = (ev) => {
|
||||
document.body.removeChild(iframe);
|
||||
reject(new Error(req.error));
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -14,7 +14,6 @@ support-files =
|
|||
|
||||
[test_browserElement_oop_AudioChannelSeeking.html]
|
||||
tags = audiochannel
|
||||
[test_browserElement_oop_getStructuredData.html]
|
||||
[test_browserElement_oop_Viewmode.html]
|
||||
[test_browserElement_oop_ThemeColor.html]
|
||||
[test_browserElement_inproc_ErrorSecurity.html]
|
||||
|
|
|
@ -41,7 +41,6 @@ support-files =
|
|||
browserElement_FrameWrongURI.js
|
||||
browserElement_GetScreenshot.js
|
||||
browserElement_GetScreenshotDppx.js
|
||||
browserElement_getStructuredData.js
|
||||
browserElement_getWebManifest.js
|
||||
browserElement_Iconchange.js
|
||||
browserElement_LoadEvents.js
|
||||
|
@ -265,7 +264,6 @@ tags = audiochannel
|
|||
[test_browserElement_inproc_AudioChannel_nested.html]
|
||||
tags = audiochannel
|
||||
[test_browserElement_inproc_SetNFCFocus.html]
|
||||
[test_browserElement_inproc_getStructuredData.html]
|
||||
[test_browserElement_inproc_OpenWindowEmpty.html]
|
||||
skip-if = (toolkit == 'gonk') # Test doesn't work on B2G emulator
|
||||
[test_browserElement_inproc_ActiveStateChangeOnChangingMutedOrVolume.html]
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Bug 1195801</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="browserElementTestHelpers.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript;version=1.8" src="async.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="browserElement_getStructuredData.js"></script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,14 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Bug 1195801</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="browserElementTestHelpers.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript;version=1.8" src="async.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="browserElement_getStructuredData.js"></script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -110,16 +110,4 @@ interface nsIBrowserElementAPI : nsISupports
|
|||
* http://w3c.github.io/manifest/
|
||||
*/
|
||||
nsIDOMDOMRequest getWebManifest();
|
||||
|
||||
/**
|
||||
* Returns a JSON string representing Microdata objects on the page.
|
||||
* Format is described at:
|
||||
* https://html.spec.whatwg.org/multipage/microdata.html#json
|
||||
*
|
||||
* Also contains hCard and hCalendar objects after converting them
|
||||
* to equivalent Microdata objects described at:
|
||||
* https://html.spec.whatwg.org/multipage/microdata.html#vcard
|
||||
* https://html.spec.whatwg.org/multipage/microdata.html#vevent
|
||||
*/
|
||||
nsIDOMDOMRequest getStructuredData();
|
||||
};
|
||||
|
|
|
@ -785,22 +785,6 @@ nsBrowserElement::ExecuteScript(const nsAString& aScript,
|
|||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::GetStructuredData(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsresult rv = mBrowserElementAPI->GetStructuredData(getter_AddRefs(req));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::GetWebManifest(ErrorResult& aRv)
|
||||
{
|
||||
|
|
|
@ -112,8 +112,6 @@ public:
|
|||
const dom::BrowserElementExecuteScriptOptions& aOptions,
|
||||
ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<dom::DOMRequest> GetStructuredData(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<dom::DOMRequest> GetWebManifest(ErrorResult& aRv);
|
||||
|
||||
void SetNFCFocus(bool isFocus,
|
||||
|
|
|
@ -175,11 +175,6 @@ interface BrowserElementPrivileged {
|
|||
DOMRequest executeScript(DOMString script,
|
||||
optional BrowserElementExecuteScriptOptions options);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckAllPermissions="browser"]
|
||||
DOMRequest getStructuredData();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckAllPermissions="browser"]
|
||||
|
|
Загрузка…
Ссылка в новой задаче