Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MOBILE-4018 iframe: Make open iframe links more consistent #4276

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions src/assets/js/iframe-treat-links.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,19 @@
}

// Redefine window.open.
const originalWindowOpen = window.open;
window.open = function(url, name, specs) {
if (name == '_self') {
// Link should be loaded in the same frame.
location.href = toAbsolute(url);

return;
// Link will be opened in the same frame, no need to treat it.
return originalWindowOpen(url, name, specs);
}

getRootWindow(window).postMessage({
environment: 'moodleapp',
context: 'iframe',
action: 'window_open',
frameUrl: location.href,
url: url,
url: toAbsolute(url),
name: name,
specs: specs,
}, '*');
Expand Down Expand Up @@ -164,24 +163,19 @@
return;
}

const linkScheme = getUrlScheme(link.href);
const pageScheme = getUrlScheme(location.href);
const isTargetSelf = !link.target || link.target == '_self';
if (!link.target || link.target == '_self') {
// Link needs to be opened in the same iframe. This is already handled properly, we don't need to do anything else.
// Links opened in the same iframe won't be captured by the app.
return;
}

if (!link.href || linkScheme == 'javascript') {
if (!link.href || getUrlScheme(link.href) == 'javascript') {
// Links with no URL and Javascript links are ignored.
return;
}

event.preventDefault();

if (isTargetSelf && (isLocalFileUrlScheme(linkScheme) || !isLocalFileUrlScheme(pageScheme))) {
// Link should be loaded in the same frame. Don't do it if link is online and frame is local.
location.href = toAbsolute(link.href);

return;
}

getRootWindow(window).postMessage({
environment: 'moodleapp',
context: 'iframe',
Expand Down
105 changes: 24 additions & 81 deletions src/core/services/utils/iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { WKWebViewCookiesWindow } from 'cordova-plugin-wkwebview-cookies';

import { CoreNetwork } from '@services/network';
import { CoreFile } from '@services/file';
import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrl } from '@singletons/url';
Expand Down Expand Up @@ -313,7 +312,13 @@ export class CoreIframeUtilsProvider {
): void {
if (contentWindow) {
// Intercept window.open.
const originalWindowOpen = contentWindow.open;
contentWindow.open = (url: string, name: string) => {
if (name === '_self') {
// Link will be opened in the same frame, no need to treat it.
return originalWindowOpen(url, name);
}

this.windowOpen(url, name, element);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -410,7 +415,7 @@ export class CoreIframeUtilsProvider {

// Add click listener to the link, this way if the iframe has added a listener to the link it will be executed first.
link.treated = true;
link.addEventListener('click', event => this.linkClicked(link, element, event));
link.addEventListener('click', event => this.linkClicked(link, event));
}, {
capture: true, // Use capture to fix this listener not called if the element clicked is too deep in the DOM.
});
Expand Down Expand Up @@ -447,110 +452,48 @@ export class CoreIframeUtilsProvider {
}
}

if (name == '_self') {
// Link should be loaded in the same frame.
if (!element) {
this.logger.warn('Cannot load URL in iframe because the element was not supplied', url);

return;
}

if (element.tagName.toLowerCase() === 'object') {
element.setAttribute('data', url);
} else {
element.setAttribute('src', url);
}
} else {
try {
// It's an external link or a local file, check if it can be opened in the app.
await CoreWindow.open(url, name);
} catch (error) {
CoreDomUtils.showErrorModal(error);
}
try {
// It's an external link or a local file, check if it can be opened in the app.
await CoreWindow.open(url, name);
} catch (error) {
CoreDomUtils.showErrorModal(error);
}
}

/**
* A link inside a frame was clicked.
*
* @param link Link clicked, or data of the link clicked.
* @param element Frame element.
* @param event Click event.
* @returns Promise resolved when done.
*/
protected async linkClicked(
link: CoreIframeHTMLAnchorElement | {href: string; target?: string; originalHref?: string},
element?: CoreFrameElement,
event?: Event,
): Promise<void> {
if (event && event.defaultPrevented) {
// Event already prevented by some other code.
return;
}

if (!link.target || link.target === '_self') {
// Link needs to be opened in the same iframe. This is already handled properly, we don't need to do anything else.
// Links opened in the same iframe won't be captured by the app.
return;
}

const urlParts = CoreUrl.parse(link.href);
const originalHref = 'getAttribute' in link ? link.getAttribute('href') : link.originalHref;
if (!link.href || !originalHref || originalHref == '#' || !urlParts || urlParts.protocol === 'javascript') {
if (!link.href || !originalHref || originalHref === '#' || !urlParts || urlParts.protocol === 'javascript') {
// Links with no URL and Javascript links are ignored.
return;
}

if (urlParts.protocol && !CoreUrl.isLocalFileUrlScheme(urlParts.protocol, urlParts.domain || '')) {
// Scheme suggests it's an external resource.
event && event.preventDefault();

const frameSrc = element && ((<HTMLIFrameElement> element).src || (<HTMLObjectElement> element).data);

// If the frame is not local, check the target to identify how to treat the link.
if (
element &&
frameSrc &&
!CoreUrl.isLocalFileUrl(frameSrc) &&
(!link.target || link.target == '_self')
) {
// Load the link inside the frame itself.
if (element.tagName.toLowerCase() === 'object') {
element.setAttribute('data', link.href);
} else {
element.setAttribute('src', link.href);
}

return;
}

// The frame is local or the link needs to be opened in a new window. Open in browser.
if (!CoreSites.isLoggedIn()) {
CoreOpener.openInBrowser(link.href);
} else {
await CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(link.href);
}
} else if (link.target == '_parent' || link.target == '_top' || link.target == '_blank') {
// Opening links with _parent, _top or _blank can break the app. We'll open it in InAppBrowser.
event && event.preventDefault();

const filename = link.href.substring(link.href.lastIndexOf('/') + 1);

if (!CoreFileHelper.isOpenableInApp({ filename })) {
try {
await CoreFileHelper.showConfirmOpenUnsupportedFile(false, { filename });
} catch (error) {
return; // Cancelled, stop.
}
}

try {
await CoreOpener.openFile(link.href);
} catch (error) {
CoreDomUtils.showErrorModal(error);
}
} else if (CorePlatform.isIOS() && (!link.target || link.target == '_self') && element) {
// In cordova ios 4.1.0 links inside iframes stopped working. We'll manually treat them.
event && event.preventDefault();
if (element.tagName.toLowerCase() === 'object') {
element.setAttribute('data', link.href);
} else {
element.setAttribute('src', link.href);
}
try {
event?.preventDefault();
await CoreWindow.open(link.href, link.target);
} catch (error) {
CoreDomUtils.showErrorModal(error);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/singletons/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class CoreWindow {
} else {
let treated = false;

if (name != '_system') {
if (name !== '_system') {
// Check if it can be opened in the app.
treated = await CoreContentLinksHelper.handleLink(url, undefined, true, true);
}
Expand Down
Loading