Proof-of-concept (WebView2 / Local) iv.

This commit is contained in:
John Preston 2023-08-24 18:04:32 +02:00
parent 672ad64e53
commit 125f856e67
25 changed files with 3164 additions and 4 deletions

View File

@ -30,8 +30,9 @@ include(cmake/lib_tgvoip.cmake)
include(cmake/lib_tgcalls.cmake)
include(cmake/lib_prisma.cmake)
include(cmake/td_export.cmake)
include(cmake/td_mtproto.cmake)
include(cmake/td_iv.cmake)
include(cmake/td_lang.cmake)
include(cmake/td_mtproto.cmake)
include(cmake/td_scheme.cmake)
include(cmake/td_ui.cmake)
include(cmake/generate_appdata_changelog.cmake)
@ -62,8 +63,9 @@ PRIVATE
desktop-app::external_minizip
tdesktop::td_export
tdesktop::td_mtproto
tdesktop::td_iv
tdesktop::td_lang
tdesktop::td_mtproto
tdesktop::td_scheme
tdesktop::td_ui
desktop-app::lib_webrtc
@ -995,6 +997,8 @@ PRIVATE
intro/intro_step.h
intro/intro_widget.cpp
intro/intro_widget.h
iv/iv_instance.cpp
iv/iv_instance.h
lang/lang_cloud_manager.cpp
lang/lang_cloud_manager.h
lang/lang_instance.cpp
@ -1567,6 +1571,7 @@ PRIVATE
qrc/emoji_preview.qrc
qrc/telegram/animations.qrc
qrc/telegram/export.qrc
qrc/telegram/iv.qrc
qrc/telegram/telegram.qrc
qrc/telegram/sounds.qrc
winrc/Telegram.rc

View File

@ -0,0 +1 @@
.hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,871 @@
body {
font-family: 'Helvetica Neue';
font-size: 17px;
line-height: 25px;
padding: 0;
margin: 0;
}
article {
padding-bottom: 12px;
overflow: hidden;
white-space: pre-wrap;
max-width: 732px;
margin: 0 auto;
}
article h1,
article h2 {
font-family: 'Georgia';
font-size: 28px;
line-height: 31px;
margin: 21px 18px 12px;
font-weight: normal;
min-height: 31px;
}
article h2 {
font-size: 24px;
line-height: 30px;
margin: -6px 18px 12px;
color: #777;
}
article h6.kicker {
font-family: 'Helvetica Neue';
font-size: 14px;
line-height: 17px;
margin: 21px 18px 12px;
font-weight: 500;
color: #79828B;
}
article h6.kicker + h1 {
margin-top: 12px;
}
article address {
font-size: 15px;
color: #79828B;
margin: 12px 18px 21px;
font-style: normal;
}
article.rtl address {
direction: ltr;
text-align: right;
}
article address figure {
width: 25px;
height: 25px;
float: right;
margin: 0 0 0 12px;
background: no-repeat center;
background-size: cover;
border-radius: 50%;
}
article address a,
article address a[href] {
color: #79828B;
}
article address a[href] {
text-decoration: underline;
}
article a[href] {
color: #007EE5;
text-decoration: none;
}
article span.reference {
border: dotted #ddd;
border-width: 1px 1px 1px 2px;
background: rgba(255, 255, 255, 0.7);
margin: 0 1px;
padding: 2px;
position: relative;
}
article.rtl span.reference {
border-width: 1px 0 1px 1px;
}
article code {
font-size: 0.94em;
background: #eee;
border-radius: 2px;
padding: 0 3px 1px;
}
article mark {
background: #fcf8e3;
border-radius: 2px;
padding: 0 3px 1px;
}
article sup,
article sub {
font-size: 0.75em;
line-height: 1;
}
article p {
margin: 0 18px 12px;
word-wrap: break-word;
}
article ul p,
article ol p {
margin: 0 0 6px;
}
article pre,
article pre.hljs {
font-family: Menlo;
margin: 14px 0;
padding: 7px 18px;
background: #F5F8FC;
font-size: 16px;
white-space: pre-wrap;
word-wrap: break-word;
overflow-x: visible;
position: relative;
}
article ul pre,
article ol pre,
article ul pre.hljs,
article ol pre.hljs {
margin: 6px 0 6px -18px;
}
article.rtl ul pre,
article.rtl ol pre,
article.rtl ul pre.hljs,
article.rtl ol pre.hljs {
margin-right: -18px;
margin-left: 0;
}
article pre + pre {
margin-top: -14px;
}
article h3,
article h4 {
font-family: 'Georgia';
font-size: 24px;
line-height: 30px;
margin: 18px 18px 9px;
font-weight: normal;
}
article h4 {
font-size: 19px;
margin: 18px 18px 7px;
}
article ul h3,
article ol h3 {
margin: 12px 0 7px;
}
article ul h4,
article ol h4 {
margin: 10px 0 5px;
}
article blockquote {
font-family: 'Georgia';
margin: 18px 18px 16px;
padding-left: 22px;
position: relative;
font-style: italic;
word-wrap: break-word;
}
article blockquote:before {
content: '';
position: absolute;
left: 1px;
top: 3px;
bottom: 3px;
width: 3px;
background-color: #000;
border-radius: 3px;
}
article.rtl blockquote {
padding-right: 22px;
padding-left: 0;
}
article.rtl blockquote:before {
right: 1px;
left: auto;
}
article aside {
font-family: 'Georgia';
margin: 18px 18px 16px;
padding: 0 18px;
text-align: center;
font-style: italic;
}
article ul blockquote,
article ol blockquote,
article ul aside,
article ol aside {
margin: 12px 0 10px;
}
article blockquote cite,
article aside cite,
article footer cite,
article .iv-pullquote cite {
font-family: 'Helvetica Neue';
font-size: 15px;
display: block;
color: #79828B;
line-height: 19px;
padding: 5px 0 2px;
font-style: normal;
}
article hr {
width: 50%;
margin: 36px auto 26px;
padding: 2px 0 10px;
border: none;
}
article ul hr,
article ol hr {
margin: 18px auto;
}
article hr:before {
content: '';
display: block;
border-top: 1px solid #c9cdd1;
padding: 0 0 2px;
}
article footer hr {
margin: 18px auto 6px;
}
article ul,
article ol {
margin: 0 18px 12px;
padding-left: 18px;
}
article.rtl ul,
article.rtl ol {
padding-right: 18px;
padding-left: 0;
}
/*article ul {
list-style: none;
}*/
article ul > li,
article ol > li {
padding-left: 4px;
}
article.rtl ul > li,
article.rtl ol > li {
padding-right: 4px;
padding-left: 0;
}
/*article ul > li {
position: relative;
}
article ul > li:before {
content: '\2022';
position: absolute;
display: block;
font-size: 163%;
left: -19px;
top: 1px;
}
article.rtl ul > li:before {
left: auto;
right: -19px;
}*/
article ul ul,
article ul ol,
article ol ul,
article ol ol {
margin: 0 0 12px;
}
article table {
width: 100%;
border-collapse: collapse;
}
article table.bordered,
article table.bordered td,
article table.bordered th {
border: 1px solid #ddd;
}
article table.striped tr:nth-child(odd) td {
background-color: #f7f7f7;
}
article table caption {
font-size: 15px;
line-height: 18px;
margin: 4px 0 7px;
text-align: left;
color: #79828B;
}
article.rtl table caption {
text-align: right;
}
article td,
article th {
font-size: 15px;
line-height: 21px;
padding: 6px 5px 5px;
background-color: #fff;
vertical-align: middle;
font-weight: normal;
text-align: left;
}
article th {
background-color: #efefef;
}
article.rtl table td,
article.rtl table th {
text-align: right;
}
article details {
position: relative;
margin: 0 0 12px;
padding: 0 0 1px;
}
article details:before {
content: '';
display: block;
border-bottom: 1px solid #c8c7cb;
position: absolute;
left: 18px;
right: 0;
bottom: 0;
}
article.rtl details:before {
right: 18px;
left: 0;
}
article details + details {
margin-top: -12px;
}
article details > details:last-child {
margin-bottom: -1px;
}
article summary {
padding: 10px 18px 10px 42px;
line-height: 25px;
min-height: 25px;
}
article.rtl summary {
padding-left: 18px;
padding-right: 42px;
}
article summary:hover {
cursor: pointer;
}
article summary:focus {
outline: none;
}
article summary::-webkit-details-marker {
display: none;
}
article summary::marker {
content: '';
}
article summary:before {
content: '';
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAICAYAAADN5B7xAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAH1JREFUeNqEjUEKgCAQRSfrNi1bdZFadJjsMC46SSAIHqjB5mcFqdFfhD3eUyKZtb6ln92O2janmXdvrRu+ZTfAgasu1jAHU4qiHAwc/Ff4oCQKsxxZ0NT33XrxUTjkWvgiXFf3TWkU6Vt+XihH515yFiQRpfLnEMUw3yHAABZNTT9emBrvAAAAAElFTkSuQmCC');
transition: all .2s ease;
display: inline-block;
position: absolute;
width: 12px;
height: 8px;
left: 18px;
top: 18px;
}
article.rtl summary:before {
right: 18px;
left: auto;
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
article summary:before {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAYAAAAMJL+VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPxJREFUeNq8lEESgiAUhgFbZ0epSW28gB2pZbrrSukBHDWto1TrwHih45AiaDOxesLP9w1PBlzXNfrLSNPqkGWV8ysHGMBqv4mAlyFC7MRPk+T51Z0Lh73AAJZgIoRFUR/bEMb4TggJPG9TTIUzxmIuWHWzOCLfQQgwRiedRMBpIsObFvn+NgSTLEE2bCiKm6eDQ0bAkS2v4AjYuPvJcqtEu9DDshaB665zFZzSV6yCfyr5JplLTOA9wZiEg/a+72Qic9nxubMOPijQSZraCK4UjEiezSVYmsBHBSrJAEIJ1wr0knG4kUAt0cONBX2JGXzGi1uG7SNmOt4CDADc4r+K4txg+wAAAABJRU5ErkJggg==');
background-size: 12px 8px;
}
}
article details[open] > summary:before {
/*transform: rotateZ(-180deg);*/
transform: scaleY(-1);
}
article li summary {
padding-left: 24px;
}
article li details:before,
article li summary:before {
left: 0;
}
img,
video,
iframe {
max-width: 100%;
max-height: 400px;
vertical-align: top;
}
video {
width: 100%;
}
audio {
width: 100%;
width: calc(100% - 36px);
margin: 0 18px;
vertical-align: top;
}
img {
font-size: 12px;
line-height: 14px;
color: #999;
}
img.pic {
max-height: none;
font-size: inherit;
vertical-align: middle;
position: relative;
top: -0.1em;
}
img.pic.optional {
opacity: 0.4;
}
body:hover img.pic.optional {
opacity: 1;
}
iframe.autosize {
max-height: none;
}
.iframe-wrap {
max-width: 100%;
vertical-align: top;
display: inline-block;
position: relative;
}
.iframe-wrap iframe {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
figure {
margin: 0 0 16px;
padding: 0;
text-align: center;
position: relative;
}
figure.nowide {
margin-left: 18px;
margin-right: 18px;
}
figure.nowide figcaption {
padding-left: 0;
padding-right: 0;
}
ul figure.nowide,
ol figure.nowide {
margin: 0 0 12px;
}
figure > figure {
margin: 0;
}
figcaption {
font-size: 15px;
color: #79828B;
padding: 6px 18px 0;
line-height: 19px;
text-align: left;
}
article.rtl figcaption {
text-align: right;
}
ul figcaption,
ol figcaption {
padding-left: 0;
padding-right: 0;
}
figcaption > cite {
font-family: 'Helvetica Neue';
font-size: 12px;
display: block;
line-height: 15px;
padding: 2px 0 0;
font-style: normal;
}
footer {
margin: 12px 18px;
color: #79828B;
}
figure.slideshow-wrap {
position: relative;
}
figure.slideshow {
position: relative;
white-space: nowrap;
width: 100%;
background: #000;
overflow: hidden;
}
figure.slideshow > figure {
position: static !important;
display: inline-block;
width: 100%;
vertical-align: middle;
transition: margin .3s;
}
figure.slideshow > figure figcaption {
box-sizing: border-box;
position: absolute;
bottom: 0;
width: 100%;
padding-bottom: 36px;
}
figure.slideshow > figure figcaption:after {
content: '';
display: block;
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: -75px;
background: -moz-linear-gradient(top,rgba(64,64,64,0),rgba(64,64,64,.55));
background: -webkit-gradient(linear,0 0,0 100%,from(rgba(64,64,64,0)),to(rgba(64,64,64,.55)));
background: -o-linear-gradient(rgba(64,64,64,0),rgba(64,64,64,.55));
pointer-events: none;
}
figure.slideshow > figure figcaption > span,
figure.slideshow > figure figcaption > cite {
position: relative;
color: #fff;
text-shadow: 0 1px rgba(0, 0, 0, .4);
z-index: 1;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
figure.slideshow > figure figcaption > span {
display: -webkit-box;
max-height: 3.8em;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
white-space: pre-wrap;
}
figure.slideshow > figure figcaption code {
text-shadow: none;
background: rgba(204, 204, 204, .7);
color: #fff;
}
figure.slideshow > figure figcaption mark {
text-shadow: none;
background: rgba(33, 123, 134, .7);
color: #fff;
}
figure.slideshow > figure figcaption a,
figure.slideshow > figure figcaption a:hover {
color: #66baff;
}
.slideshow-buttons {
position: absolute;
width: 100%;
bottom: 10px;
white-space: nowrap;
overflow: hidden;
z-index: 3;
}
.slideshow-buttons > fieldset {
padding: 0 10px 20px;
margin: 0 0 -20px;
border: none;
line-height: 0;
overflow: hidden;
overflow-x: auto;
min-width: auto;
}
.slideshow-buttons label {
display: inline-block;
padding: 7px;
cursor: pointer;
}
.slideshow-buttons input {
position: absolute;
left: -10000px;
}
.slideshow-buttons label i {
display: inline-block;
background: #fff;
box-shadow: 0 0 3px rgba(0, 0, 0, .4);
border-radius: 3.5px;
width: 7px;
height: 7px;
opacity: .6;
transition: opacity .3s;
}
.slideshow-buttons input:checked ~ i {
opacity: 1;
}
figure.collage {
margin: -2px 16px;
text-align: left;
}
figure.collage > figure {
display: inline-block;
vertical-align: top;
width: calc(25% - 4px);
margin: 2px;
box-sizing: border-box;
}
figure.collage > figure > i {
background: no-repeat center;
background-size: cover;
display: inline-block;
vertical-align: top;
width: 100%;
padding-top: 100%;
}
figure.table-wrap {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
figure.table {
display: table-cell;
padding: 0 18px;
}
article ol figure.table-wrap,
article ul figure.table-wrap {
margin-top: 7px;
}
article ol figure.table,
article ul figure.table {
padding: 0;
}
figure blockquote.embed-post {
text-align: left;
margin-bottom: 0;
}
article.rtl figure blockquote.embed-post {
text-align: right;
}
blockquote.embed-post address {
margin: 0;
padding: 5px 0 9px;
overflow: hidden;
}
blockquote.embed-post address figure {
width: 50px;
height: 50px;
float: left;
margin: 0 12px 0 0;
background: no-repeat center;
background-size: cover;
border-radius: 50%;
}
article.rtl blockquote.embed-post address figure {
float: right;
margin-left: 12px;
margin-right: 0;
}
blockquote.embed-post address a {
display: inline-block;
padding-top: 2px;
font-size: 17px;
font-weight: 600;
color: #000;
}
blockquote.embed-post address time {
display: block;
line-height: 19px;
}
blockquote.embed-post p,
blockquote.embed-post blockquote {
margin: 0 0 7px;
clear: left;
}
blockquote.embed-post figcaption {
padding-left: 0;
padding-right: 0;
}
blockquote.embed-post figure.collage {
margin-left: -2px;
margin-right: -2px;
}
blockquote.embed-post footer {
margin: 12px 0 0;
font-style: normal;
}
blockquote.embed-post footer hr {
display: none;
}
section.embed-post {
display: block;
width: auto;
height: auto;
background: #f7f7f7;
margin: 0 18px 12px;
padding: 24px 18px;
text-align: center;
}
section.embed-post strong {
font-size: 21px;
font-weight: normal;
display: block;
color: #777;
}
section.embed-post small {
display: block;
color: #777;
}
section.related {
margin: 7px 0 12px;
}
section.related h4 {
font-family: 'Helvetica Neue';
font-size: 17px;
line-height: 26px;
font-weight: 500;
display: block;
padding: 7px 18px;
background: #f7f7f7;
margin: 0;
color: #000;
}
section.related a.related-link {
display: block;
padding: 15px 18px 16px;
background: #fff;
position: relative;
overflow: hidden;
}
section.related a.related-link:after {
content: '';
display: block;
border-bottom: 1px solid #c8c7cb;
position: absolute;
left: 18px;
right: 0;
bottom: 0;
}
section.related .related-link-url {
display: block;
font-size: 15px;
line-height: 18px;
padding: 7px 0;
color: #999;
word-break: break-word;
}
section.related .related-link-thumb {
display: inline-block;
float: right;
width: 87px;
height: 87px;
border-radius: 4px;
background: no-repeat center;
background-size: cover;
margin-left: 15px;
}
section.related .related-link-content {
display: block;
margin: -3px 0;
}
section.related .related-link-title {
font-size: 15px;
font-weight: 500;
line-height: 18px;
display: block;
display: -webkit-box;
margin-bottom: 4px;
max-height: 36px;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre-wrap;
color: #000;
}
section.related .related-link-desc {
font-size: 14px;
line-height: 17px;
display: block;
display: -webkit-box;
max-height: 51px;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre-wrap;
color: #000;
}
section.related .related-link-source {
font-size: 13px;
line-height: 17px;
display: block;
overflow: hidden;
margin-top: 4px;
text-overflow: ellipsis;
white-space: nowrap;
color: #818181;
}
section.message {
position: absolute;
display: table;
width: 100%;
height: 100%;
}
section.message.static {
position: static;
min-height: 200px;
height: 100vh;
}
section.message > aside {
display: table-cell;
vertical-align: middle;
text-align: center;
color: #999;
font-size: 24px;
pointer-events: none;
}
section.message > aside > cite {
display: block;
font-size: 14px;
padding: 10px 0 0;
font-style: normal;
color: #ccc;
}
section.channel {
margin-top: -16px;
margin-bottom: -9px;
}
section.channel:first-child {
margin-top: 0;
}
section.channel > a {
display: block;
padding: 7px 18px;
background: #f7f7f7;
}
section.channel > a:before {
content: 'Join';
color: #3196e8;
font-weight: 500;
margin-left: 7px;
float: right;
}
section.channel > a > h4 {
font-family: 'Helvetica Neue';
font-size: 17px;
line-height: 26px;
font-weight: 500;
margin: 0;
color: #000;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.iv-pullquote {
text-align: center;
max-width: 420px;
font-size: 19px;
display: block;
margin: 0 auto;
}
.iv-photo-wrap {
width: 100%;
background-size: 100%;
margin: 0 auto;
}
.iv-photo {
background-size: 100%;
}

View File

@ -0,0 +1,86 @@
var IV = {
sendPostMessage: function(data) {
try {
window.parent.postMessage(JSON.stringify(data), window.parentOrigin);
} catch(e) {}
},
frameClickHandler: function(e) {
var target = e.target, href;
do {
if (target.tagName == 'SUMMARY') return;
if (target.tagName == 'DETAILS') return;
if (target.tagName == 'LABEL') return;
if (target.tagName == 'AUDIO') return;
if (target.tagName == 'A') break;
} while (target = target.parentNode);
if (target && target.hasAttribute('href')) {
var base_loc = document.createElement('A');
base_loc.href = window.currentUrl;
if (base_loc.origin != target.origin ||
base_loc.pathname != target.pathname ||
base_loc.search != target.search) {
IV.sendPostMessage({event: 'link_click', url: target.href});
}
}
e.preventDefault();
},
postMessageHandler: function(event) {
if (event.source !== window.parent ||
event.origin != window.parentOrigin) {
return;
}
try {
var data = JSON.parse(event.data);
} catch(e) {
var data = {};
}
},
slideshowSlide: function(el, next) {
var dir = window.getComputedStyle(el, null).direction || 'ltr';
var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft';
if (next) {
var s = el.previousSibling.s;
s.value = (+s.value + 1 == s.length) ? 0 : +s.value + 1;
s.forEach(function(el){ el.checked && el.parentNode.scrollIntoView && el.parentNode.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'}); });
el.firstChild.style[marginProp] = (-100 * s.value) + '%';
} else {
el.form.nextSibling.firstChild.style[marginProp] = (-100 * el.value) + '%';
}
return false;
},
initPreBlocks: function() {
if (!hljs) return;
var pres = document.getElementsByTagName('pre');
for (var i = 0; i < pres.length; i++) {
if (pres[i].hasAttribute('data-language')) {
hljs.highlightBlock(pres[i]);
}
}
},
initEmbedBlocks: function() {
var iframes = document.getElementsByTagName('iframe');
for (var i = 0; i < iframes.length; i++) {
(function(iframe) {
window.addEventListener('message', function(event) {
if (event.source !== iframe.contentWindow ||
event.origin != window.origin) {
return;
}
try {
var data = JSON.parse(event.data);
} catch(e) {
var data = {};
}
if (data.eventType == 'resize_frame') {
if (data.eventData.height) {
iframe.style.height = data.eventData.height + 'px';
}
}
}, false);
})(iframes[i]);
}
}
};
document.onclick = IV.frameClickHandler;
window.onmessage = IV.postMessageHandler;

View File

@ -0,0 +1,8 @@
<RCC>
<qresource prefix="/iv">
<file alias="page.css">../../iv_html/page.css</file>
<file alias="page.js">../../iv_html/page.js</file>
<file alias="highlight.css">../../iv_html/highlight.9.12.0.css</file>
<file alias="highlight.js">../../iv_html/highlight.9.12.0.js</file>
</qresource>
</RCC>

View File

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/boosts/info_boosts_widget.h"
#include "info/profile/info_profile_emoji_status_panel.h"
#include "info/info_memento.h"
#include "iv/iv_data.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "lottie/lottie_single_player.h"
@ -310,6 +311,7 @@ PreviewWrap::PreviewWrap(
nullptr, // photo
nullptr, // document
WebPageCollage(),
nullptr, // iv
0, // duration
QString(), // author
false, // hasLargeMedia

View File

@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_updates.h"
#include "calls/calls_instance.h"
#include "countries/countries_manager.h"
#include "iv/iv_instance.h"
#include "lang/lang_file_parser.h"
#include "lang/lang_translator.h"
#include "lang/lang_cloud_manager.h"
@ -162,6 +163,7 @@ Application::Application()
, _domain(std::make_unique<Main::Domain>(cDataFile()))
, _exportManager(std::make_unique<Export::Manager>())
, _calls(std::make_unique<Calls::Instance>())
, _iv(std::make_unique<Iv::Instance>())
, _langpack(std::make_unique<Lang::Instance>())
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
@ -218,6 +220,7 @@ Application::~Application() {
// Domain::finish() and there is a violation on Ensures(started()).
Payments::CheckoutProcess::ClearAll();
InlineBots::AttachWebView::ClearAll();
_iv->closeAll();
_domain->finish();
@ -1272,6 +1275,8 @@ bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
return false;
} else if (_calls->hasActivePanel(session)) {
return true;
} else if (_iv->hasActiveWindow(session)) {
return true;
} else if (const auto window = _lastActiveWindow) {
return (window->account().maybeSession() == session)
&& window->widget()->isActive();

View File

@ -45,6 +45,10 @@ class Account;
class Session;
} // namespace Main
namespace Iv {
class Instance;
} // namespace Iv
namespace Ui {
namespace Animations {
class Manager;
@ -280,6 +284,11 @@ public:
return *_calls;
}
// Iv.
Iv::Instance &iv() const {
return *_iv;
}
void logout(Main::Account *account = nullptr);
void logoutWithChecks(Main::Account *account);
void forceLogOut(
@ -409,6 +418,7 @@ private:
const std::unique_ptr<Main::Domain> _domain;
const std::unique_ptr<Export::Manager> _exportManager;
const std::unique_ptr<Calls::Instance> _calls;
const std::unique_ptr<Iv::Instance> _iv;
base::flat_map<
Main::Account*,
std::unique_ptr<Window::Controller>> _primaryWindows;

View File

@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio.h"
#include "boxes/abstract_box.h"
#include "passport/passport_form_controller.h"
#include "iv/iv_data.h"
#include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name
#include "data/business/data_business_chatbots.h"
#include "data/business/data_business_info.h"
@ -3365,6 +3366,7 @@ not_null<WebPageData*> Session::processWebpage(
nullptr,
nullptr,
WebPageCollage(),
nullptr,
0,
QString(),
false,
@ -3389,6 +3391,7 @@ not_null<WebPageData*> Session::webpage(
nullptr,
nullptr,
WebPageCollage(),
nullptr,
0,
QString(),
false,
@ -3406,6 +3409,7 @@ not_null<WebPageData*> Session::webpage(
PhotoData *photo,
DocumentData *document,
WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv,
int duration,
const QString &author,
bool hasLargeMedia,
@ -3423,6 +3427,7 @@ not_null<WebPageData*> Session::webpage(
photo,
document,
std::move(collage),
std::move(iv),
duration,
author,
hasLargeMedia,
@ -3503,6 +3508,14 @@ void Session::webpageApplyFields(
}, [](const auto &) {});
}
}
if (const auto page = data.vcached_page()) {
for (const auto photo : page->data().vphotos().v) {
processPhoto(photo);
}
for (const auto document : page->data().vdocuments().v) {
processDocument(document);
}
}
webpageApplyFields(
page,
(story ? WebPageType::Story : ParseWebPageType(data)),
@ -3523,6 +3536,9 @@ void Session::webpageApplyFields(
? processDocument(*document).get()
: lookupThemeDocument()),
WebPageCollage(this, data),
(data.vcached_page()
? std::make_unique<Iv::Data>(data, *data.vcached_page())
: nullptr),
data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()),
data.is_has_large_media(),
@ -3541,6 +3557,7 @@ void Session::webpageApplyFields(
PhotoData *photo,
DocumentData *document,
WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv,
int duration,
const QString &author,
bool hasLargeMedia,
@ -3557,6 +3574,7 @@ void Session::webpageApplyFields(
photo,
document,
std::move(collage),
std::move(iv),
duration,
author,
hasLargeMedia,

View File

@ -38,6 +38,10 @@ namespace Passport {
struct SavedCredentials;
} // namespace Passport
namespace Iv {
class Data;
} // namespace Iv
namespace Data {
class Folder;
@ -581,6 +585,7 @@ public:
PhotoData *photo,
DocumentData *document,
WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv,
int duration,
const QString &author,
bool hasLargeMedia,
@ -858,6 +863,7 @@ private:
PhotoData *photo,
DocumentData *document,
WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv,
int duration,
const QString &author,
bool hasLargeMedia,

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_document.h"
#include "lang/lang_keys.h"
#include "iv/iv_data.h"
#include "ui/image/image.h"
#include "ui/text/text_entity.h"
@ -206,6 +207,8 @@ WebPageData::WebPageData(not_null<Data::Session*> owner, const WebPageId &id)
, _owner(owner) {
}
WebPageData::~WebPageData() = default;
Data::Session &WebPageData::owner() const {
return *_owner;
}
@ -225,6 +228,7 @@ bool WebPageData::applyChanges(
PhotoData *newPhoto,
DocumentData *newDocument,
WebPageCollage &&newCollage,
std::unique_ptr<Iv::Data> newIv,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
@ -276,6 +280,7 @@ bool WebPageData::applyChanges(
&& photo == newPhoto
&& document == newDocument
&& collage.items == newCollage.items
&& (!iv == !newIv)
&& duration == newDuration
&& author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
@ -296,6 +301,7 @@ bool WebPageData::applyChanges(
photo = newPhoto;
document = newDocument;
collage = std::move(newCollage);
iv = std::move(newIv);
duration = newDuration;
author = resultAuthor;
pendingTill = newPendingTill;

View File

@ -17,6 +17,10 @@ namespace Data {
class Session;
} // namespace Data
namespace Iv {
class Data;
} // namespace Iv
enum class WebPageType : uint8 {
None,
@ -64,6 +68,7 @@ struct WebPageCollage {
struct WebPageData {
WebPageData(not_null<Data::Session*> owner, const WebPageId &id);
~WebPageData();
[[nodiscard]] Data::Session &owner() const;
[[nodiscard]] Main::Session &session() const;
@ -79,6 +84,7 @@ struct WebPageData {
PhotoData *newPhoto,
DocumentData *newDocument,
WebPageCollage &&newCollage,
std::unique_ptr<Iv::Data> newIv,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
@ -105,6 +111,7 @@ struct WebPageData {
PhotoData *photo = nullptr;
DocumentData *document = nullptr;
WebPageCollage collage;
std::unique_ptr<Iv::Data> iv;
int duration = 0;
TimeId pendingTill = 0;
uint32 version : 30 = 0;

View File

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/view/history_view_cursor_state.h"
#include "iv/iv_data.h"
#include "iv/iv_controller.h"
#include "lang/lang_keys.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
@ -18,6 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "core/application.h"
#include "iv/iv_instance.h"
namespace HistoryView {
namespace {

View File

@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_web_page.h"
#include "core/application.h"
#include "base/qt/qt_key_modifiers.h"
#include "window/window_session_controller.h"
#include "iv/iv_instance.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h"
#include "data/data_file_click_handler.h"
@ -82,7 +86,29 @@ constexpr auto kMaxOriginalEntryLines = 8192;
return result;
}
[[nodiscard]] ClickHandlerPtr IvClickHandler(not_null<WebPageData*> webpage) {
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
if (const auto iv = webpage->iv.get()) {
#ifdef _DEBUG
const auto local = base::IsCtrlPressed();
#else // _DEBUG
const auto local = false;
#endif // _DEBUG
Core::App().iv().show(controller->uiShow(), iv, local);
return;
} else {
HiddenUrlClickHandler::Open(webpage->url, context.other);
}
}
});
}
[[nodiscard]] QString PageToPhrase(not_null<WebPageData*> webpage) {
if (webpage->iv) {
return u"Instant View"_q;
}
const auto type = webpage->type;
return Ui::Text::Upper((type == WebPageType::Theme)
? tr::lng_view_button_theme(tr::now)
@ -118,7 +144,8 @@ constexpr auto kMaxOriginalEntryLines = 8192;
[[nodiscard]] bool HasButton(not_null<WebPageData*> webpage) {
const auto type = webpage->type;
return (type == WebPageType::Message)
return webpage->iv
|| (type == WebPageType::Message)
|| (type == WebPageType::Group)
|| (type == WebPageType::Channel)
|| (type == WebPageType::ChannelBoost)
@ -245,7 +272,7 @@ QSize WebPage::countOptimalSize() {
}
return true;
}();
_openl = (previewOfHiddenUrl
_openl = _data->iv ? IvClickHandler(_data) : (previewOfHiddenUrl
|| UrlClickHandler::IsSuspicious(_data->url))
? std::make_shared<HiddenUrlClickHandler>(_data->url)
: std::make_shared<UrlClickHandler>(_data->url, true);

View File

@ -0,0 +1,115 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "iv/iv_controller.h"
#include "iv/iv_data.h"
#include "ui/widgets/rp_window.h"
#include "webview/webview_data_stream_memory.h"
#include "webview/webview_embed.h"
#include "webview/webview_interface.h"
#include <QtCore/QRegularExpression>
#include <QtCore/QFile>
namespace Iv {
Controller::Controller() = default;
Controller::~Controller() {
_webview = nullptr;
_window = nullptr;
}
void Controller::show(const QString &dataPath, Prepared page) {
_window = std::make_unique<Ui::RpWindow>();
const auto window = _window.get();
window->setGeometry({ 200, 200, 800, 600 });
const auto container = Ui::CreateChild<Ui::RpWidget>(
window->body().get());
window->sizeValue() | rpl::start_with_next([=](QSize size) {
container->setGeometry(QRect(QPoint(), size));
}, container->lifetime());
container->show();
_webview = std::make_unique<Webview::Window>(
container,
Webview::WindowConfig{ .userDataPath = dataPath });
const auto raw = _webview.get();
window->lifetime().add([=] {
_webview = nullptr;
});
if (!raw->widget()) {
_webview = nullptr;
_window = nullptr;
return;
}
raw->widget()->show();
container->geometryValue(
) | rpl::start_with_next([=](QRect geometry) {
raw->widget()->setGeometry(geometry);
}, _lifetime);
raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) {
return true;
});
raw->setNavigationDoneHandler([=](bool success) {
});
raw->setDataRequestHandler([=](Webview::DataRequest request) {
if (!request.id.starts_with("iv/")) {
_dataRequests.fire(std::move(request));
return Webview::DataResult::Pending;
}
const auto finishWith = [&](QByteArray data, std::string mime) {
request.done({
.stream = std::make_unique<Webview::DataStreamFromMemory>(
std::move(data),
std::move(mime)),
});
return Webview::DataResult::Done;
};
const auto id = std::string_view(request.id).substr(3);
if (id == "page.html") {
return finishWith(page.html, "text/html");
}
const auto css = id.ends_with(".css");
const auto js = !css && id.ends_with(".js");
if (!css && !js) {
return Webview::DataResult::Failed;
}
const auto qstring = QString::fromUtf8(id.data(), id.size());
const auto pattern = u"^[a-zA-Z\\.\\-_0-9]+$"_q;
if (QRegularExpression(pattern).match(qstring).hasMatch()) {
auto file = QFile(u":/iv/"_q + qstring);
if (file.open(QIODevice::ReadOnly)) {
const auto mime = css ? "text/css" : "text/javascript";
return finishWith(file.readAll(), mime);
}
}
return Webview::DataResult::Failed;
});
raw->init(R"(
)");
raw->navigateToData("iv/page.html");
window->show();
}
rpl::producer<Webview::DataRequest> Controller::dataRequests() const {
return _dataRequests.events();
}
rpl::lifetime &Controller::lifetime() {
return _lifetime;
}
} // namespace Iv

View File

@ -0,0 +1,42 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Webview {
struct DataRequest;
class Window;
} // namespace Webview
namespace Ui {
class RpWindow;
} // namespace Ui
namespace Iv {
struct Prepared;
class Controller final {
public:
Controller();
~Controller();
void show(const QString &dataPath, Prepared page);
[[nodiscard]] rpl::producer<Webview::DataRequest> dataRequests() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
std::unique_ptr<Ui::RpWindow> _window;
std::unique_ptr<Webview::Window> _webview;
rpl::event_stream<Webview::DataRequest> _dataRequests;
rpl::lifetime _lifetime;
};
} // namespace Iv

View File

@ -0,0 +1,63 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "iv/iv_data.h"
#include "iv/iv_prepare.h"
namespace Iv {
QByteArray GeoPointId(Geo point) {
const auto lat = int(point.lat * 1000000);
const auto lon = int(point.lon * 1000000);
const auto combined = (std::uint64_t(std::uint32_t(lat)) << 32)
| std::uint64_t(std::uint32_t(lon));
return QByteArray::number(combined)
+ ','
+ QByteArray::number(point.access);
}
Geo GeoPointFromId(QByteArray data) {
const auto parts = data.split(',');
if (parts.size() != 2) {
return {};
}
const auto combined = parts[0].toULongLong();
const auto lat = int(std::uint32_t(combined >> 32));
const auto lon = int(std::uint32_t(combined & 0xFFFFFFFFULL));
return {
.lat = lat / 1000000.,
.lon = lon / 1000000.,
.access = parts[1].toULongLong(),
};
}
Data::Data(const MTPDwebPage &webpage, const MTPPage &page)
: _source(std::make_unique<Source>(Source{
.page = page,
.webpagePhoto = (webpage.vphoto()
? *webpage.vphoto()
: std::optional<MTPPhoto>()),
.webpageDocument = (webpage.vdocument()
? *webpage.vdocument()
: std::optional<MTPDocument>()),
})) {
}
QString Data::id() const {
return qs(_source->page.data().vurl());
}
Data::~Data() = default;
void Data::prepare(const Options &options, Fn<void(Prepared)> done) const {
crl::async([source = *_source, options, done = std::move(done)] {
done(Prepare(source, options));
});
}
} // namespace Iv

View File

@ -0,0 +1,47 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Iv {
struct Source;
struct Options {
QString saveToFolder;
};
struct Prepared {
QByteArray html;
std::vector<QByteArray> resources;
base::flat_map<QByteArray, QByteArray> embeds;
};
struct Geo {
float64 lat = 0.;
float64 lon = 0.;
uint64 access = 0;
};
[[nodiscard]] QByteArray GeoPointId(Geo point);
[[nodiscard]] Geo GeoPointFromId(QByteArray data);
class Data final {
public:
Data(const MTPDwebPage &webpage, const MTPPage &page);
~Data();
[[nodiscard]] QString id() const;
void prepare(const Options &options, Fn<void(Prepared)> done) const;
private:
const std::unique_ptr<Source> _source;
};
} // namespace Iv

View File

@ -0,0 +1,663 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "iv/iv_instance.h"
#include "core/file_utilities.h"
#include "data/data_cloud_file.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "iv/iv_controller.h"
#include "iv/iv_data.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "main/main_session.h"
#include "main/session/session_show.h"
#include "media/streaming/media_streaming_loader.h"
#include "storage/file_download.h"
#include "storage/storage_domain.h"
#include "ui/boxes/confirm_box.h"
#include "webview/webview_data_stream_memory.h"
#include "webview/webview_interface.h"
namespace Iv {
namespace {
constexpr auto kGeoPointScale = 1;
constexpr auto kGeoPointZoomMin = 13;
constexpr auto kMaxLoadParts = 3;
constexpr auto kKeepLoadingParts = 8;
[[nodiscard]] QString LookupLocalPath(
const std::shared_ptr<Main::SessionShow> show) {
const auto &domain = show->session().account().domain();
const auto &base = domain.local().webviewDataPath();
static auto counter = 0;
return base + u"/iv/"_q + QString::number(++counter);
}
[[nodiscard]] Storage::Cache::Key IvBaseCacheKey(
not_null<DocumentData*> document) {
auto big = document->bigFileBaseCacheKey();
big.low += 0x7FF;
return big;
}
} // namespace
class Shown final : public base::has_weak_ptr {
public:
Shown(
std::shared_ptr<Main::SessionShow> show,
not_null<Data*> data,
bool local);
[[nodiscard]] bool showing(
not_null<Main::Session*> session,
not_null<Data*> data) const;
[[nodiscard]] bool showingFrom(not_null<Main::Session*> session) const;
[[nodiscard]] bool activeFor(not_null<Main::Session*> session) const;
private:
struct MapPreview {
std::unique_ptr<::Data::CloudFile> file;
QByteArray bytes;
};
struct PartRequest {
Webview::DataRequest request;
QByteArray data;
std::vector<bool> loaded;
int64 offset = 0;
};
struct FileLoad {
not_null<DocumentData*> document;
std::unique_ptr<Media::Streaming::Loader> loader;
std::vector<PartRequest> requests;
std::string mime;
rpl::lifetime lifetime;
};
void showLocal(Prepared result);
void showWindowed(Prepared result);
// Local.
void showProgress(int index);
void loadResource(int index);
void finishLocal(const QString &path);
[[nodiscard]] QString localRoot() const;
void writeLocal(const QString &relative, const QByteArray &data);
void loadPhoto(QString id, PhotoId photoId);
void loadDocument(QString id, DocumentId documentId);
void loadPage(QString id, QString tag);
void loadMap(QString id, QString params);
void writeEmbed(QString id, QString hash);
// Windowed.
void streamPhoto(PhotoId photoId, Webview::DataRequest request);
void streamFile(DocumentId documentId, Webview::DataRequest request);
void streamFile(FileLoad &file, Webview::DataRequest request);
void processPartInFile(
FileLoad &file,
Media::Streaming::LoadedPart &&part);
bool finishRequestWithPart(
PartRequest &request,
const Media::Streaming::LoadedPart &part);
void streamMap(QString params, Webview::DataRequest request);
void sendEmbed(QByteArray hash, Webview::DataRequest request);
void requestDone(
Webview::DataRequest request,
QByteArray bytes,
std::string mime,
int64 offset = 0,
int64 total = 0);
void requestFail(Webview::DataRequest request);
const not_null<Main::Session*> _session;
std::shared_ptr<Main::SessionShow> _show;
QString _id;
std::unique_ptr<Controller> _controller;
base::flat_map<DocumentId, FileLoad> _files;
QString _localBase;
base::flat_map<QByteArray, QByteArray> _embeds;
base::flat_map<QString, MapPreview> _maps;
std::vector<QByteArray> _resources;
int _resource = -1;
rpl::lifetime _lifetime;
};
Shown::Shown(
std::shared_ptr<Main::SessionShow> show,
not_null<Data*> data,
bool local)
: _session(&show->session())
, _show(show)
, _id(data->id()) {
const auto weak = base::make_weak(this);
const auto base = local ? LookupLocalPath(show) : QString();
data->prepare({ .saveToFolder = base }, [=](Prepared result) {
crl::on_main(weak, [=, result = std::move(result)]() mutable {
_embeds = std::move(result.embeds);
if (!base.isEmpty()) {
_localBase = base;
showLocal(std::move(result));
} else {
showWindowed(std::move(result));
}
});
});
}
void Shown::showLocal(Prepared result) {
showProgress(0);
QDir(_localBase).removeRecursively();
QDir().mkpath(_localBase);
_resources = std::move(result.resources);
writeLocal(localRoot(), result.html);
}
void Shown::showProgress(int index) {
const auto count = int(_resources.size() + 1);
_show->showToast(u"Saving %1 / %2..."_q.arg(index + 1).arg(count));
}
void Shown::finishLocal(const QString &path) {
if (path.isEmpty()) {
_show->showToast(u"Failed!"_q);
} else {
_show->showToast(u"Done!"_q);
File::Launch(path);
}
_id = QString();
}
QString Shown::localRoot() const {
return u"page.html"_q;
}
void Shown::writeLocal(const QString &relative, const QByteArray &data) {
const auto path = _localBase + '/' + relative;
QFileInfo(path).absoluteDir().mkpath(".");
auto f = QFile(path);
if (!f.open(QIODevice::WriteOnly) || f.write(data) != data.size()) {
finishLocal({});
} else {
crl::on_main(this, [=] {
loadResource(_resource + 1);
});
}
}
void Shown::loadResource(int index) {
_resource = index;
if (_resource == _resources.size()) {
finishLocal(_localBase + '/' + localRoot());
return;
}
showProgress(_resource + 1);
const auto id = QString::fromUtf8(_resources[_resource]);
if (id.startsWith(u"photo/"_q)) {
loadPhoto(id, id.mid(6).toULongLong());
} else if (id.startsWith(u"document/"_q)) {
loadDocument(id, id.mid(9).toULongLong());
} else if (id.startsWith(u"iv/"_q)) {
loadPage(id, id.mid(3));
} else if (id.startsWith(u"map/"_q)) {
loadMap(id, id.mid(4));
} else if (id.startsWith(u"html/"_q)) {
writeEmbed(id, id.mid(5));
} else {
_show->show(
Ui::MakeInformBox(u"Skipping resource %1..."_q.arg(id)));
crl::on_main(this, [=] {
loadResource(index + 1);
});
}
}
void Shown::loadPhoto(QString id, PhotoId photoId) {
const auto photo = _session->data().photo(photoId);
const auto media = photo->createMediaView();
media->wanted(::Data::PhotoSize::Large, ::Data::FileOrigin());
const auto finish = [=](QByteArray bytes) {
writeLocal(id, bytes);
};
if (media->loaded()) {
finish(media->imageBytes(::Data::PhotoSize::Large));
} else {
photo->session().downloaderTaskFinished(
) | rpl::filter([=] {
return media->loaded();
}) | rpl::take(1) | rpl::start_with_next([=] {
finish(media->imageBytes(::Data::PhotoSize::Large));
}, _lifetime);
}
}
void Shown::loadDocument(QString id, DocumentId documentId) {
const auto path = _localBase + '/' + id;
QFileInfo(path).absoluteDir().mkpath(".");
const auto document = _session->data().document(documentId);
document->save(::Data::FileOrigin(), path);
if (!document->loading()) {
crl::on_main(this, [=] {
loadResource(_resource + 1);
});
}
document->session().downloaderTaskFinished(
) | rpl::filter([=] {
return !document->loading();
}) | rpl::take(1) | rpl::start_with_next([=] {
crl::on_main(this, [=] {
loadResource(_resource + 1);
});
}, _lifetime);
}
void Shown::loadPage(QString id, QString tag) {
if (!id.endsWith(u".css"_q) && !id.endsWith(u".js"_q)) {
finishLocal({});
return;
}
const auto pattern = u"^[a-zA-Z\\.\\-_0-9]+$"_q;
if (QRegularExpression(pattern).match(tag).hasMatch()) {
auto file = QFile(u":/iv/"_q + tag);
if (file.open(QIODevice::ReadOnly)) {
writeLocal(id, file.readAll());
return;
}
}
finishLocal({});
}
void Shown::loadMap(QString id, QString params) {
using namespace ::Data;
const auto i = _maps.find(params);
if (i != end(_maps)) {
writeLocal(id, i->second.bytes);
return;
}
const auto parts = params.split(u'&');
if (parts.size() != 3) {
finishLocal({});
return;
}
const auto point = GeoPointFromId(parts[0].toUtf8());
const auto size = parts[1].split(',');
const auto zoom = parts[2].toInt();
if (size.size() != 2) {
finishLocal({});
return;
}
const auto location = GeoPointLocation{
.lat = point.lat,
.lon = point.lon,
.access = point.access,
.width = size[0].toInt(),
.height = size[1].toInt(),
.zoom = std::max(zoom, kGeoPointZoomMin),
.scale = kGeoPointScale,
};
const auto prepared = ImageWithLocation{
.location = ImageLocation(
{ location },
location.width,
location.height)
};
auto &preview = _maps.emplace(params, MapPreview()).first->second;
preview.file = std::make_unique<CloudFile>();
UpdateCloudFile(
*preview.file,
prepared,
_session->data().cache(),
kImageCacheTag,
[=](FileOrigin origin) { /* restartLoader not used here */ });
const auto autoLoading = false;
const auto finalCheck = [=] { return true; };
const auto done = [=](QByteArray bytes) {
const auto i = _maps.find(params);
Assert(i != end(_maps));
i->second.bytes = std::move(bytes);
writeLocal(id, i->second.bytes);
};
LoadCloudFile(
_session,
*preview.file,
FileOrigin(),
LoadFromCloudOrLocal,
autoLoading,
kImageCacheTag,
finalCheck,
done,
[=](bool) { done("failed..."); });
}
void Shown::writeEmbed(QString id, QString hash) {
const auto i = _embeds.find(hash.toUtf8());
if (i != end(_embeds)) {
writeLocal(id, i->second);
} else {
finishLocal({});
}
}
void Shown::showWindowed(Prepared result) {
_controller = std::make_unique<Controller>();
_controller->dataRequests(
) | rpl::start_with_next([=](Webview::DataRequest request) {
const auto requested = QString::fromStdString(request.id);
const auto id = QStringView(requested);
if (id.startsWith(u"photo/")) {
streamPhoto(id.mid(6).toULongLong(), std::move(request));
} else if (id.startsWith(u"document/"_q)) {
streamFile(id.mid(9).toULongLong(), std::move(request));
} else if (id.startsWith(u"map/"_q)) {
streamMap(id.mid(4).toUtf8(), std::move(request));
} else if (id.startsWith(u"html/"_q)) {
sendEmbed(id.mid(5).toUtf8(), std::move(request));
}
}, _controller->lifetime());
const auto domain = &_session->domain();
_controller->show(domain->local().webviewDataPath(), std::move(result));
}
void Shown::streamPhoto(PhotoId photoId, Webview::DataRequest request) {
using namespace Data;
const auto photo = _session->data().photo(photoId);
if (photo->isNull()) {
requestFail(std::move(request));
return;
}
const auto media = photo->createMediaView();
media->wanted(PhotoSize::Large, FileOrigin());
const auto check = [=] {
if (!media->loaded() && !media->owner()->failed(PhotoSize::Large)) {
return false;
}
requestDone(
request,
media->imageBytes(PhotoSize::Large),
"image/jpeg");
return true;
};
if (!check()) {
photo->session().downloaderTaskFinished(
) | rpl::filter(
check
) | rpl::take(1) | rpl::start(_controller->lifetime());
}
}
void Shown::streamFile(
DocumentId documentId,
Webview::DataRequest request) {
using namespace Data;
const auto i = _files.find(documentId);
if (i != end(_files)) {
streamFile(i->second, std::move(request));
return;
}
const auto document = _session->data().document(documentId);
auto loader = document->createStreamingLoader(FileOrigin(), false);
if (!loader) {
requestFail(std::move(request));
return;
}
auto &file = _files.emplace(
documentId,
FileLoad{
.document = document,
.loader = std::move(loader),
.mime = document->mimeString().toStdString(),
}).first->second;
file.loader->parts(
) | rpl::start_with_next([=](Media::Streaming::LoadedPart &&part) {
const auto i = _files.find(documentId);
Assert(i != end(_files));
processPartInFile(i->second, std::move(part));
}, file.lifetime);
streamFile(file, std::move(request));
}
void Shown::streamFile(FileLoad &file, Webview::DataRequest request) {
constexpr auto kPart = Media::Streaming::Loader::kPartSize;
const auto size = file.document->size;
const auto last = int((size + kPart - 1) / kPart);
const auto from = int(std::min(request.offset, size) / kPart);
const auto till = (request.limit > 0)
? std::min(request.offset + request.limit, size)
: size;
const auto parts = std::min(
int((till + kPart - 1) / kPart) - from,
kMaxLoadParts);
//auto base = IvBaseCacheKey(document);
const auto length = std::min((from + parts) * kPart, size)
- from * kPart;
file.requests.push_back(PartRequest{
.request = std::move(request),
.data = QByteArray(length, 0),
.loaded = std::vector<bool>(parts, false),
.offset = from * kPart,
});
file.loader->resetPriorities();
const auto load = std::min(from + kKeepLoadingParts, last) - from;
for (auto i = 0; i != load; ++i) {
file.loader->load((from + i) * kPart);
}
}
void Shown::processPartInFile(
FileLoad &file,
Media::Streaming::LoadedPart &&part) {
for (auto i = begin(file.requests); i != end(file.requests);) {
if (finishRequestWithPart(*i, part)) {
auto done = base::take(*i);
i = file.requests.erase(i);
requestDone(
std::move(done.request),
done.data,
file.mime,
done.offset,
file.document->size);
} else {
++i;
}
}
}
bool Shown::finishRequestWithPart(
PartRequest &request,
const Media::Streaming::LoadedPart &part) {
const auto offset = part.offset;
if (offset == Media::Streaming::LoadedPart::kFailedOffset) {
request.data = QByteArray();
return true;
} else if (offset < request.offset
|| offset >= request.offset + request.data.size()) {
return false;
}
constexpr auto kPart = Media::Streaming::Loader::kPartSize;
const auto copy = std::min(
int(part.bytes.size()),
int(request.data.size() - (offset - request.offset)));
const auto index = (offset - request.offset) / kPart;
Assert(index < request.loaded.size());
if (request.loaded[index]) {
return false;
}
request.loaded[index] = true;
memcpy(
request.data.data() + index * kPart,
part.bytes.constData(),
copy);
return !ranges::contains(request.loaded, false);
}
void Shown::streamMap(QString params, Webview::DataRequest request) {
using namespace ::Data;
const auto parts = params.split(u'&');
if (parts.size() != 3) {
finishLocal({});
return;
}
const auto point = GeoPointFromId(parts[0].toUtf8());
const auto size = parts[1].split(',');
const auto zoom = parts[2].toInt();
if (size.size() != 2) {
finishLocal({});
return;
}
const auto location = GeoPointLocation{
.lat = point.lat,
.lon = point.lon,
.access = point.access,
.width = size[0].toInt(),
.height = size[1].toInt(),
.zoom = std::max(zoom, kGeoPointZoomMin),
.scale = kGeoPointScale,
};
const auto prepared = ImageWithLocation{
.location = ImageLocation(
{ location },
location.width,
location.height)
};
auto &preview = _maps.emplace(params, MapPreview()).first->second;
preview.file = std::make_unique<CloudFile>();
UpdateCloudFile(
*preview.file,
prepared,
_session->data().cache(),
kImageCacheTag,
[=](FileOrigin origin) { /* restartLoader not used here */ });
const auto autoLoading = false;
const auto finalCheck = [=] { return true; };
const auto done = [=](QByteArray bytes) {
const auto i = _maps.find(params);
Assert(i != end(_maps));
i->second.bytes = std::move(bytes);
requestDone(request, i->second.bytes, "image/png");
};
LoadCloudFile(
_session,
*preview.file,
FileOrigin(),
LoadFromCloudOrLocal,
autoLoading,
kImageCacheTag,
finalCheck,
done,
[=](bool) { done("failed..."); });
}
void Shown::sendEmbed(QByteArray hash, Webview::DataRequest request) {
const auto i = _embeds.find(hash);
if (i != end(_embeds)) {
requestDone(std::move(request), i->second, "text/html");
} else {
requestFail(std::move(request));
}
}
void Shown::requestDone(
Webview::DataRequest request,
QByteArray bytes,
std::string mime,
int64 offset,
int64 total) {
if (bytes.isEmpty() && mime.empty()) {
requestFail(std::move(request));
return;
}
crl::on_main([
done = std::move(request.done),
data = std::move(bytes),
mime = std::move(mime),
offset,
total
] {
using namespace Webview;
done({
.stream = std::make_unique<DataStreamFromMemory>(data, mime),
.streamOffset = offset,
.totalSize = total,
});
});
}
void Shown::requestFail(Webview::DataRequest request) {
crl::on_main([done = std::move(request.done)] {
done({});
});
}
bool Shown::showing(
not_null<Main::Session*> session,
not_null<Data*> data) const {
return showingFrom(session) && (_id == data->id());
}
bool Shown::showingFrom(not_null<Main::Session*> session) const {
return (_session == session);
}
bool Shown::activeFor(not_null<Main::Session*> session) const {
return showingFrom(session) && _controller;
}
Instance::Instance() = default;
Instance::~Instance() = default;
void Instance::show(
std::shared_ptr<Main::SessionShow> show,
not_null<Data*> data,
bool local) {
const auto session = &show->session();
if (_shown && _shown->showing(session, data)) {
return;
}
_shown = std::make_unique<Shown>(show, data, local);
if (!_tracking.contains(session)) {
_tracking.emplace(session);
session->lifetime().add([=] {
_tracking.remove(session);
if (_shown && _shown->showingFrom(session)) {
_shown = nullptr;
}
});
}
}
bool Instance::hasActiveWindow(not_null<Main::Session*> session) const {
return _shown && _shown->activeFor(session);
}
void Instance::closeAll() {
_shown = nullptr;
}
} // namespace Iv

View File

@ -0,0 +1,45 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Main {
class Session;
class SessionShow;
} // namespace Main
namespace Iv {
class Data;
class Shown;
class Instance final {
public:
Instance();
~Instance();
void show(
std::shared_ptr<Main::SessionShow> show,
not_null<Data*> data,
bool local);
[[nodiscard]] bool hasActiveWindow(
not_null<Main::Session*> session) const;
void closeAll();
[[nodiscard]] rpl::lifetime &lifetime();
private:
std::unique_ptr<Shown> _shown;
base::flat_set<not_null<Main::Session*>> _tracking;
rpl::lifetime _lifetime;
};
} // namespace Iv

View File

@ -0,0 +1,27 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include <QtCore/QString>
#include <QtCore/QByteArray>
#include <crl/crl.h>
#include <rpl/rpl.h>
#include <vector>
#include <map>
#include <set>
#include <deque>
#include <atomic>
#include <range/v3/all.hpp>
#include "base/flat_map.h"
#include "base/flat_set.h"
#include "scheme.h"
#include "logs.h"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Iv {
struct Options;
struct Prepared;
struct Source {
MTPPage page;
std::optional<MTPPhoto> webpagePhoto;
std::optional<MTPDocument> webpageDocument;
};
[[nodiscard]] Prepared Prepare(
const Source &source,
const Options &options);
} // namespace Iv

View File

@ -0,0 +1,35 @@
# This file is part of Telegram Desktop,
# the official desktop application for the Telegram messaging service.
#
# For license and copyright information please follow this link:
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
add_library(td_iv OBJECT)
init_non_host_target(td_iv)
add_library(tdesktop::td_iv ALIAS td_iv)
target_precompile_headers(td_iv PRIVATE ${src_loc}/iv/iv_pch.h)
nice_target_sources(td_iv ${src_loc}
PRIVATE
iv/iv_controller.cpp
iv/iv_controller.h
iv/iv_data.cpp
iv/iv_data.h
iv/iv_pch.h
iv/iv_prepare.cpp
iv/iv_prepare.h
)
target_include_directories(td_iv
PUBLIC
${src_loc}
)
target_link_libraries(td_iv
PUBLIC
desktop-app::lib_ui
tdesktop::td_scheme
PRIVATE
desktop-app::lib_webview
tdesktop::td_lang
)