Skip to content

Commit 7abc385

Browse files
authored
Merge pull request #2415 from dpalou/MOBILE-3401
Mobile 3401
2 parents a3f29ad + 52852c2 commit 7abc385

File tree

11 files changed

+520
-168
lines changed

11 files changed

+520
-168
lines changed

package-lock.json

Lines changed: 36 additions & 86 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"cordova-plugin-splashscreen": "5.0.3",
113113
"cordova-plugin-statusbar": "2.4.3",
114114
"cordova-plugin-whitelist": "1.3.4",
115+
"cordova-plugin-wkuserscript": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git",
115116
"cordova-plugin-wkwebview-cookies": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git",
116117
"cordova-plugin-zip": "3.1.0",
117118
"cordova-sqlite-storage": "4.0.0",
@@ -211,7 +212,8 @@
211212
},
212213
"cordova-plugin-wkwebview-cookies": {},
213214
"cordova-plugin-qrscanner": {},
214-
"cordova-plugin-chooser": {}
215+
"cordova-plugin-chooser": {},
216+
"cordova-plugin-wkuserscript": {}
215217
}
216218
},
217219
"main": "desktop/electron.js",

src/addon/mod/h5pactivity/components/index/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
407407
* @return Whether it's an XAPI post statement of the current activity.
408408
*/
409409
protected isCurrentXAPIPost(data: any): boolean {
410-
if (data.context != 'moodleapp' || data.action != 'xapi_post_statement' || !data.statements) {
410+
if (data.environment != 'moodleapp' || data.context != 'h5p' || data.action != 'xapi_post_statement' || !data.statements) {
411411
return false;
412412
}
413413

src/assets/js/iframe-recaptcha.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
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+
(function () {
16+
var url = location.href;
17+
18+
if (!url.match(/^https?:\/\//i) || !url.match(/\/webservice\/recaptcha\.php/i)) {
19+
// Not the recaptcha script, stop.
20+
return;
21+
}
22+
23+
// Define recaptcha callbacks.
24+
window.recaptchacallback = function(value) {
25+
window.parent.postMessage({
26+
environment: 'moodleapp',
27+
context: 'recaptcha',
28+
action: 'callback',
29+
frameUrl: location.href,
30+
value: value,
31+
}, '*');
32+
};
33+
34+
window.recaptchaexpiredcallback = function() {
35+
window.parent.postMessage({
36+
environment: 'moodleapp',
37+
context: 'recaptcha',
38+
action: 'expired',
39+
frameUrl: location.href,
40+
}, '*');
41+
};
42+
})();
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
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+
(function () {
16+
var url = location.href;
17+
18+
if (url.match(/^moodleappfs:\/\/localhost/i) || !url.match(/^[a-z0-9]+:\/\//i)) {
19+
// Same domain as the app, stop.
20+
return;
21+
}
22+
23+
// Redefine window.open.
24+
window.open = function(url, name, specs) {
25+
if (name == '_self') {
26+
// Link should be loaded in the same frame.
27+
location.href = toAbsolute(url);
28+
29+
return;
30+
}
31+
32+
getRootWindow(window).postMessage({
33+
environment: 'moodleapp',
34+
context: 'iframe',
35+
action: 'window_open',
36+
frameUrl: location.href,
37+
url: url,
38+
name: name,
39+
specs: specs,
40+
}, '*');
41+
};
42+
43+
// Handle link clicks.
44+
document.addEventListener('click', (event) => {
45+
if (event.defaultPrevented) {
46+
// Event already prevented by some other code.
47+
return;
48+
}
49+
50+
// Find the link being clicked.
51+
var el = event.target;
52+
while (el && el.tagName !== 'A') {
53+
el = el.parentElement;
54+
}
55+
56+
if (!el || el.treated) {
57+
return;
58+
}
59+
60+
// Add click listener to the link, this way if the iframe has added a listener to the link it will be executed first.
61+
el.treated = true;
62+
el.addEventListener('click', function(event) {
63+
linkClicked(el, event);
64+
});
65+
}, {
66+
capture: true // Use capture to fix this listener not called if the element clicked is too deep in the DOM.
67+
});
68+
69+
70+
71+
/**
72+
* Concatenate two paths, adding a slash between them if needed.
73+
*
74+
* @param leftPath Left path.
75+
* @param rightPath Right path.
76+
* @return Concatenated path.
77+
*/
78+
function concatenatePaths(leftPath, rightPath) {
79+
if (!leftPath) {
80+
return rightPath;
81+
} else if (!rightPath) {
82+
return leftPath;
83+
}
84+
85+
var lastCharLeft = leftPath.slice(-1);
86+
var firstCharRight = rightPath.charAt(0);
87+
88+
if (lastCharLeft === '/' && firstCharRight === '/') {
89+
return leftPath + rightPath.substr(1);
90+
} else if (lastCharLeft !== '/' && firstCharRight !== '/') {
91+
return leftPath + '/' + rightPath;
92+
} else {
93+
return leftPath + rightPath;
94+
}
95+
}
96+
97+
/**
98+
* Get the root window.
99+
*
100+
* @param win Current window to check.
101+
* @return Root window.
102+
*/
103+
function getRootWindow(win) {
104+
if (win.parent === win) {
105+
return win;
106+
}
107+
108+
return getRootWindow(win.parent);
109+
}
110+
111+
/**
112+
* Get the scheme from a URL.
113+
*
114+
* @param url URL to treat.
115+
* @return Scheme, undefined if no scheme found.
116+
*/
117+
function getUrlScheme(url) {
118+
if (!url) {
119+
return;
120+
}
121+
122+
var matches = url.match(/^([a-z][a-z0-9+\-.]*):/);
123+
if (matches && matches[1]) {
124+
return matches[1];
125+
}
126+
}
127+
128+
/**
129+
* Check if a URL is absolute.
130+
*
131+
* @param url URL to treat.
132+
* @return Whether it's absolute.
133+
*/
134+
function isAbsoluteUrl(url) {
135+
return /^[^:]{2,}:\/\//i.test(url);
136+
}
137+
138+
/**
139+
* Check whether a URL scheme belongs to a local file.
140+
*
141+
* @param scheme Scheme to check.
142+
* @return Whether the scheme belongs to a local file.
143+
*/
144+
function isLocalFileUrlScheme(scheme) {
145+
if (scheme) {
146+
scheme = scheme.toLowerCase();
147+
}
148+
149+
return scheme == 'cdvfile' ||
150+
scheme == 'file' ||
151+
scheme == 'filesystem' ||
152+
scheme == 'moodleappfs';
153+
}
154+
155+
/**
156+
* Handle a click on an anchor element.
157+
*
158+
* @param link Anchor element clicked.
159+
* @param event Click event.
160+
*/
161+
function linkClicked(link, event) {
162+
if (event.defaultPrevented) {
163+
// Event already prevented by some other code.
164+
return;
165+
}
166+
167+
var linkScheme = getUrlScheme(link.href);
168+
var pageScheme = getUrlScheme(location.href);
169+
var isTargetSelf = !link.target || link.target == '_self';
170+
171+
if (!link.href || linkScheme == 'javascript') {
172+
// Links with no URL and Javascript links are ignored.
173+
return;
174+
}
175+
176+
event.preventDefault();
177+
178+
if (isTargetSelf && (isLocalFileUrlScheme(linkScheme) || !isLocalFileUrlScheme(pageScheme))) {
179+
// Link should be loaded in the same frame. Don't do it if link is online and frame is local.
180+
location.href = toAbsolute(link.href);
181+
182+
return;
183+
}
184+
185+
getRootWindow(window).postMessage({
186+
environment: 'moodleapp',
187+
context: 'iframe',
188+
action: 'link_clicked',
189+
frameUrl: location.href,
190+
link: {href: link.href, target: link.target},
191+
}, '*');
192+
}
193+
194+
/**
195+
* Convert a URL to an absolute URL if needed using the frame src.
196+
*
197+
* @param url URL to convert.
198+
* @return Absolute URL.
199+
*/
200+
function toAbsolute(url) {
201+
if (isAbsoluteUrl(url)) {
202+
return url;
203+
}
204+
205+
// It's a relative URL, use the frame src to create the full URL.
206+
var pathToDir = location.href.substring(0, location.href.lastIndexOf('/'));
207+
208+
return concatenatePaths(pathToDir, url);
209+
}
210+
})();

src/components/iframe/core-iframe.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
<div [class.core-loading-container]="loading" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}">
2-
<iframe #iframe [hidden]="loading" class="core-iframe" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl" [attr.allowfullscreen]="allowFullscreen ? 'allowfullscreen' : null"></iframe>
1+
<div [class.core-loading-container]="loading || !safeUrl" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}">
2+
<!-- Don't add the iframe until the safeUrl is set, adding an iframe with null as src causes the iframe to load the whole app. -->
3+
<iframe #iframe *ngIf="safeUrl" [hidden]="loading" class="core-iframe" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl" [attr.allowfullscreen]="allowFullscreen ? 'allowfullscreen' : null"></iframe>
4+
35
<span class="core-loading-spinner">
46
<ion-spinner *ngIf="loading"></ion-spinner>
57
</span>

src/components/iframe/iframe.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
import {
16-
Component, Input, Output, OnInit, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange, Optional
16+
Component, Input, Output, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange, Optional
1717
} from '@angular/core';
1818
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
1919
import { NavController, Platform } from 'ionic-angular';
@@ -25,12 +25,13 @@ import { CoreIframeUtilsProvider } from '@providers/utils/iframe';
2525
import { CoreUtilsProvider } from '@providers/utils/utils';
2626
import { CoreSplitViewComponent } from '@components/split-view/split-view';
2727
import { CoreUrl } from '@singletons/url';
28+
import { WKWebViewCookiesWindow } from 'cordova-plugin-wkwebview-cookies';
2829

2930
@Component({
3031
selector: 'core-iframe',
3132
templateUrl: 'core-iframe.html'
3233
})
33-
export class CoreIframeComponent implements OnInit, OnChanges {
34+
export class CoreIframeComponent implements OnChanges {
3435

3536
@ViewChild('iframe') iframe: ElementRef;
3637
@Input() src: string;
@@ -43,6 +44,7 @@ export class CoreIframeComponent implements OnInit, OnChanges {
4344

4445
protected logger;
4546
protected IFRAME_TIMEOUT = 15000;
47+
protected initialized = false;
4648

4749
constructor(logger: CoreLoggerProvider,
4850
protected iframeUtils: CoreIframeUtilsProvider,
@@ -59,9 +61,15 @@ export class CoreIframeComponent implements OnInit, OnChanges {
5961
}
6062

6163
/**
62-
* Component being initialized.
64+
* Init the data.
6365
*/
64-
ngOnInit(): void {
66+
protected init(): void {
67+
if (this.initialized) {
68+
return;
69+
}
70+
71+
this.initialized = true;
72+
6573
const iframe: HTMLIFrameElement = this.iframe && this.iframe.nativeElement;
6674

6775
this.iframeWidth = this.domUtils.formatPixelsSize(this.iframeWidth) || '100%';
@@ -101,7 +109,7 @@ export class CoreIframeComponent implements OnInit, OnChanges {
101109
if (this.platform.is('ios') && !this.urlUtils.isLocalFileUrl(url)) {
102110
// Save a "fake" cookie for the iframe's domain to fix a bug in WKWebView.
103111
try {
104-
const win = <any> window;
112+
const win = <WKWebViewCookiesWindow> window;
105113
const urlParts = CoreUrl.parse(url);
106114

107115
await win.WKWebViewCookies.setCookie({
@@ -116,6 +124,11 @@ export class CoreIframeComponent implements OnInit, OnChanges {
116124
}
117125

118126
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(CoreFile.instance.convertFileSrc(url));
127+
128+
// Now that the URL has been set, initialize the iframe. Wait for the iframe to the added to the DOM.
129+
setTimeout(() => {
130+
this.init();
131+
});
119132
}
120133
}
121134
}

0 commit comments

Comments
 (0)