1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "frameworks/bridge/common/media_query/media_queryer.h"
17
18 #include <list>
19 #include <regex>
20
21 #include "base/log/log.h"
22 #include "base/utils/utils.h"
23 #include "core/common/container.h"
24 #include "frameworks/bridge/common/media_query/media_query_info.h"
25 #include "frameworks/bridge/common/utils/utils.h"
26
27 namespace OHOS::Ace::Framework {
28 namespace {
29
30 constexpr double NOT_FOUND = -1.0;
31 enum class MediaError {
32 NONE,
33 SYNTAX,
34 };
35 using ConditionParser =
36 std::function<bool(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason)>;
37
38 class MediaQueryerRule {
39 public:
MediaQueryerRule(const std::regex & regex,const ConditionParser & parser,uint32_t matchResultSize)40 MediaQueryerRule(const std::regex& regex, const ConditionParser& parser, uint32_t matchResultSize)
41 : regex_(regex), parser_(parser), matchResultSize_(matchResultSize)
42 {}
MediaQueryerRule(const std::regex & regex)43 explicit MediaQueryerRule(const std::regex& regex) : regex_(regex) {}
44 ~MediaQueryerRule() = default;
45
ParseCondition(std::smatch & matchResults,const MediaFeature & mediaFeature,MediaError & failReason) const46 bool ParseCondition(std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) const
47 {
48 CHECK_NULL_RETURN_NOLOG(parser_, false);
49 if (matchResults.size() != matchResultSize_) {
50 failReason = MediaError::SYNTAX;
51 return false;
52 }
53 return parser_(matchResults, mediaFeature, failReason);
54 }
Match(const std::string & condition,std::smatch & matchResults) const55 bool Match(const std::string& condition, std::smatch& matchResults) const
56 {
57 return std::regex_match(condition, matchResults, regex_);
58 }
Match(const std::string & condition) const59 bool Match(const std::string& condition) const
60 {
61 return std::regex_match(condition, regex_);
62 }
63
64 private:
65 const std::regex regex_;
66 const ConditionParser parser_;
67 const uint32_t matchResultSize_ = 0;
68 };
69
70 namespace RelationShip {
71 const std::string GREAT_OR_EQUAL(">=");
72 const std::string GREAT_NOT_EQUAL = ">";
73 const std::string LESS_OR_EQUAL = "<=";
74 const std::string LESS_NOT_EQUAL = "<";
75 }; // namespace RelationShip
76
77 /**
78 * transfer unit the same with condition value unit
79 * @param value: device value should be transfer unit the same with condition value
80 * @param unit: condition value unit, such as: dpi/dpcm/dppx
81 */
TransferValue(double value,const std::string & unit)82 double TransferValue(double value, const std::string& unit)
83 {
84 double transfer = 1.0;
85 if (unit == "dpi") {
86 transfer = 96.0; // 1px = 96 dpi
87 } else if (unit == "vp") {
88 auto container = Container::Current();
89 if (container) {
90 auto pipeline = container->GetPipelineContext();
91 if (pipeline && !NearZero(pipeline->GetDipScale())) {
92 transfer = 1.0 / pipeline->GetDipScale();
93 } else {
94 LOGW("No pipeline, use default scale.");
95 }
96 } else {
97 LOGW("No container, use default scale.");
98 }
99 } else if (unit == "dpcm") {
100 transfer = 36.0; // 1px = 36 dpcm
101 } else {
102 transfer = 1.0; // default same with device unit: px
103 }
104 return value * transfer;
105 }
106
CalculateExpression(double lvalue,const std::string & relationship,double rvalue,MediaError & failReason)107 bool CalculateExpression(double lvalue, const std::string& relationship, double rvalue, MediaError& failReason)
108 {
109 if (relationship == RelationShip::GREAT_OR_EQUAL) {
110 return GreatOrEqual(lvalue, rvalue);
111 } else if (relationship == RelationShip::GREAT_NOT_EQUAL) {
112 return GreatNotEqual(lvalue, rvalue);
113 } else if (relationship == RelationShip::LESS_OR_EQUAL) {
114 return LessOrEqual(lvalue, rvalue);
115 } else if (relationship == RelationShip::LESS_NOT_EQUAL) {
116 return LessNotEqual(lvalue, rvalue);
117 } else {
118 failReason = MediaError::SYNTAX;
119 }
120 return false;
121 }
122
123 const MediaQueryerRule CONDITION_WITH_SCREEN(
124 std::regex("(((only|not)screen)|screen)((and|or|,)\\([\\w\\.:><=-]+\\))*"));
125 const MediaQueryerRule CONDITION_WITHOUT_SCREEN(std::regex("\\([\\w\\.:><=-]+\\)((and|or|,)\\([\\w\\.:><=-]+\\))*"));
126 const MediaQueryerRule CONDITION_WITH_AND(std::regex("(\\([\\.a-z0-9:>=<-]+\\))(and\\([\\.a-z0-9:>=<-]+\\))+"));
127
128 // condition such as: (100 < width < 1000)
129 const MediaQueryerRule CSS_LEVEL4_MULTI(
130 std::regex(
131 "\\(([\\d\\.]+)(dpi|dppx|dpcm|px|vp)?(>=|<=|>|<)([a-z0-9:-]+)(>|<|>=|<=)([\\d\\.]+)(dpi|dppx|dpcm|px|vp)?\\)"),
__anonb32c47cc0202(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 132 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
133 static constexpr int32_t LEFT_CONDITION_VALUE = 6;
134 static constexpr int32_t LEFT_UNIT = 7;
135 static constexpr int32_t LEFT_RELATIONSHIP = 5;
136 static constexpr int32_t MEDIA_FEATURE = 4;
137 static constexpr int32_t RIGHT_CONDITION_VALUE = 1;
138 static constexpr int32_t RIGHT_UNIT = 2;
139 static constexpr int32_t RIGHT_RELATIONSHIP = 3;
140
141 auto mediaFeatureValue = mediaFeature->GetDouble(matchResults[MEDIA_FEATURE], NOT_FOUND);
142 return CalculateExpression(TransferValue(mediaFeatureValue, matchResults[LEFT_UNIT]),
143 matchResults[LEFT_RELATIONSHIP], StringToDouble(matchResults[LEFT_CONDITION_VALUE]), failReason) &&
144 CalculateExpression(StringToDouble(matchResults[RIGHT_CONDITION_VALUE]),
145 matchResults[RIGHT_RELATIONSHIP], TransferValue(mediaFeatureValue, matchResults[RIGHT_UNIT]),
146 failReason);
147 },
148 8);
149
150 // condition such as: width < 1000
151 const MediaQueryerRule CSS_LEVEL4_LEFT(
152 std::regex("\\(([^m][a-z-]+)(>=|<=|>|<)([\\d\\.]+)(dpi|dppx|dpcm|px|vp)?\\)"),
__anonb32c47cc0302(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 153 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
154 static constexpr int32_t CONDITION_VALUE = 3;
155 static constexpr int32_t UNIT = 4;
156 static constexpr int32_t RELATIONSHIP = 2;
157 static constexpr int32_t MEDIA_FEATURE = 1;
158
159 return CalculateExpression(
160 TransferValue(mediaFeature->GetDouble(matchResults[MEDIA_FEATURE], NOT_FOUND), matchResults[UNIT]),
161 matchResults[RELATIONSHIP], StringToDouble(matchResults[CONDITION_VALUE]), failReason);
162 },
163 5);
164
165 // condition such as: 1000 < width
166 const MediaQueryerRule CSS_LEVEL4_RIGHT(
167 std::regex("\\(([\\d\\.]+)(dpi|dppx|dpcm|px|vp)?(>=|<=|>|<)([^m][a-z-]+)\\)"),
__anonb32c47cc0402(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 168 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
169 static constexpr int32_t CONDITION_VALUE = 1;
170 static constexpr int32_t UNIT = 2;
171 static constexpr int32_t RELATIONSHIP = 3;
172 static constexpr int32_t MEDIA_FEATURE = 4;
173 return CalculateExpression(StringToDouble(matchResults[CONDITION_VALUE]), matchResults[RELATIONSHIP],
174 TransferValue(mediaFeature->GetDouble(matchResults[MEDIA_FEATURE], NOT_FOUND), matchResults[UNIT]),
175 failReason);
176 },
177 5);
178
179 // condition such as: min-width: 1000
180 const MediaQueryerRule CSS_LEVEL3_RULE(
181 std::regex("\\((min|max)-([a-z-]+):([\\d\\.]+)(dpi|dppx|dpcm|vp)?\\)"),
__anonb32c47cc0502(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 182 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
183 static constexpr int32_t RELATIONSHIP = 1;
184 static constexpr int32_t MEDIA_FEATURE = 2;
185 static constexpr int32_t CONDITION_VALUE = 3;
186 static constexpr int32_t UNIT = 4;
187 std::string relationship;
188 if (matchResults[RELATIONSHIP] == "max") {
189 relationship = RelationShip::LESS_OR_EQUAL;
190 } else if (matchResults[RELATIONSHIP] == "min") {
191 relationship = RelationShip::GREAT_OR_EQUAL;
192 } else {
193 return false;
194 }
195
196 return CalculateExpression(
197 TransferValue(mediaFeature->GetDouble(matchResults[MEDIA_FEATURE], NOT_FOUND), matchResults[UNIT]),
198 relationship, StringToDouble(matchResults[CONDITION_VALUE]), failReason);
199 },
200 5);
201
202 const MediaQueryerRule SCREEN_SHAPE_RULE(
203 std::regex("\\(round-screen:([a-z]+)\\)"),
__anonb32c47cc0602(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 204 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
205 static constexpr int32_t CONDITION_VALUE = 1;
206 return StringToBool(matchResults[CONDITION_VALUE]) == mediaFeature->GetBool("round-screen", false);
207 },
208 2);
209
210 const MediaQueryerRule ORIENTATION_RULE(
211 std::regex("\\(orientation:([a-z]+)\\)"),
__anonb32c47cc0702(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 212 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
213 static constexpr int32_t CONDITION_VALUE = 1;
214 return matchResults[CONDITION_VALUE] == mediaFeature->GetString("orientation", "");
215 },
216 2);
217
218 const MediaQueryerRule DEVICE_TYPE_RULE(
219 std::regex("\\(device-type:([a-z]+)\\)"),
__anonb32c47cc0802(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 220 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
221 static constexpr int32_t CONDITION_VALUE = 1;
222 auto matchDeviceType = mediaFeature->GetString("device-type", "");
223 if (matchResults[CONDITION_VALUE] == "default") {
224 return matchDeviceType == "phone";
225 } else {
226 return matchResults[CONDITION_VALUE] == matchDeviceType;
227 }
228 },
229 2);
230
231 const MediaQueryerRule DEVICE_BRAND_RULE(
232 std::regex("\\(device-brand:([A-Z]+)\\)"),
__anonb32c47cc0902(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 233 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
234 static constexpr int32_t CONDITION_VALUE = 1;
235 auto value = matchResults[CONDITION_VALUE] == mediaFeature->GetString("device-brand", "");
236 return value;
237 },
238 2);
239
240 const MediaQueryerRule DARK_MODE_RULE(
241 std::regex("\\(dark-mode:([a-z]+)\\)"),
__anonb32c47cc0a02(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 242 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
243 static constexpr int32_t CONDITION_VALUE = 1;
244 return StringToBool(matchResults[CONDITION_VALUE]) == mediaFeature->GetBool("dark-mode", false);
245 },
246 2);
247
248 const std::list<MediaQueryerRule> SINGLE_CONDITION_RULES = {
249 CSS_LEVEL4_MULTI,
250 CSS_LEVEL4_LEFT,
251 CSS_LEVEL4_RIGHT,
252 CSS_LEVEL3_RULE,
253 ORIENTATION_RULE,
254 DEVICE_TYPE_RULE,
255 DEVICE_BRAND_RULE,
256 SCREEN_SHAPE_RULE,
257 DARK_MODE_RULE,
258
259 };
260
ParseSingleCondition(const std::string & condition,const MediaFeature & mediaFeature,MediaError & failReason)261 bool ParseSingleCondition(const std::string& condition, const MediaFeature& mediaFeature, MediaError& failReason)
262 {
263 for (const auto& rule : SINGLE_CONDITION_RULES) {
264 std::smatch matchResults;
265 if (rule.Match(condition, matchResults)) {
266 return rule.ParseCondition(matchResults, mediaFeature, failReason);
267 }
268 }
269 LOGE("illegal condition");
270 failReason = MediaError::SYNTAX;
271 return false;
272 }
273
ParseAndCondition(const std::string & condition,const MediaFeature & mediaFeature,MediaError & failReason)274 bool ParseAndCondition(const std::string& condition, const MediaFeature& mediaFeature, MediaError& failReason)
275 {
276 auto noAnd = std::regex_replace(condition, std::regex("and[^a-z]"), ",(");
277 std::vector<std::string> conditionArr;
278 StringUtils::SplitStr(noAnd, ",", conditionArr);
279 if (conditionArr.empty()) {
280 failReason = MediaError::SYNTAX;
281 return false;
282 }
283
284 for (const auto& item : conditionArr) {
285 if (!ParseSingleCondition(item, mediaFeature, failReason)) {
286 return false;
287 }
288 }
289 return true;
290 }
291
DoMatchCondition(const std::string & condition,const MediaFeature & mediaFeature)292 bool DoMatchCondition(const std::string& condition, const MediaFeature& mediaFeature)
293 {
294 // remove space from condition string
295 std::string noSpace = std::regex_replace(condition, std::regex("\\s"), "");
296 bool inverse = false;
297 std::string noScreen;
298 if (CONDITION_WITH_SCREEN.Match(noSpace)) {
299 if (noSpace.find("notscreen") != std::string::npos) {
300 inverse = true;
301 }
302 MediaQueryerRule screenPatten(std::regex("screen[^and:]"));
303 if (screenPatten.Match(noSpace)) {
304 return !inverse;
305 }
306 noScreen = std::regex_replace(noSpace, std::regex("^(only|not)?screen(and)?"), "");
307 } else if (CONDITION_WITHOUT_SCREEN.Match(noSpace)) {
308 noScreen = noSpace;
309 } else {
310 LOGE("illegal condition");
311 return false;
312 }
313 MediaError failReason = MediaError::NONE;
314 // replace 'or' with comma ','
315 auto commaCondition = std::regex_replace(noScreen, std::regex("or[(]"), ",(");
316 // remove screen and modifier
317 std::vector<std::string> conditionArr;
318 StringUtils::SplitStr(commaCondition, ",", conditionArr);
319 int32_t len = static_cast<int32_t>(conditionArr.size());
320 for (int32_t i = 0; i < len; i++) {
321 if (CONDITION_WITH_AND.Match(conditionArr[i])) {
322 bool result = ParseAndCondition(conditionArr[i], mediaFeature, failReason);
323 if (failReason == MediaError::SYNTAX) {
324 return false;
325 }
326 if (i + 1 == len) {
327 return (inverse && !result) || (!inverse && result);
328 }
329 } else {
330 if (ParseSingleCondition(conditionArr[i], mediaFeature, failReason)) {
331 return !inverse;
332 }
333 if (failReason == MediaError::SYNTAX) {
334 return false;
335 }
336 }
337 }
338 return inverse;
339 }
340
341 } // namespace
342
MatchCondition(const std::string & condition,const MediaFeature & mediaFeature)343 bool MediaQueryer::MatchCondition(const std::string& condition, const MediaFeature& mediaFeature)
344 {
345 if (condition.empty()) {
346 return false;
347 }
348
349 // If width and height are not initialized, and the query condition includes "width" or "height",
350 // return false directly.
351 if (mediaFeature->GetInt("width", 0) == 0 &&
352 (condition.find("width") != std::string::npos || condition.find("height") != std::string::npos)) {
353 return false;
354 }
355
356 auto iter = queryHistories.find(condition);
357 if (iter != queryHistories.end()) {
358 auto queryHistory = iter->second;
359 if (queryHistory.mediaFeatureValue == mediaFeature->ToString()) {
360 return queryHistory.result;
361 }
362 }
363
364 auto result = DoMatchCondition(condition, mediaFeature);
365 queryHistories[condition] = { mediaFeature->ToString(), result };
366 return result;
367 }
368
369 /* card info */
GetMediaFeature() const370 std::unique_ptr<JsonValue> MediaQueryer::GetMediaFeature() const
371 {
372 auto json = MediaQueryInfo::GetMediaQueryJsonInfo();
373
374 /* cover the following aspects with card specified values */
375 double aspectRatio = (height_ != 0) ? (static_cast<double>(width_) / height_) : 1.0;
376 json->Replace("width", width_);
377 json->Replace("height", height_);
378 json->Replace("aspect-ratio", aspectRatio);
379 json->Replace("dark-mode", colorMode_ == ColorMode::DARK);
380 json->Put("device-brand", SystemProperties::GetBrand().c_str());
381 LOGD("current media info %{public}s", json->ToString().c_str());
382 return json;
383 }
384
385 } // namespace OHOS::Ace::Framework
386