// (C) Copyright 2015 Martin Dougiamas // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { Injectable, NgZone } from '@angular/core'; import { Config, Platform } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { Network } from '@ionic-native/network'; import { CoreAppProvider } from '../app'; import { CoreFileProvider } from '../file'; import { CoreLoggerProvider } from '../logger'; import { CoreSitesProvider } from '../sites'; import { CoreDomUtilsProvider } from './dom'; import { CoreTextUtilsProvider } from './text'; import { CoreUrlUtilsProvider } from './url'; import { CoreUtilsProvider } from './utils'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; /* * "Utils" service with helper functions for iframes, embed and similar. */ @Injectable() export class CoreIframeUtilsProvider { static FRAME_TAGS = ['iframe', 'frame', 'object', 'embed']; protected logger; constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, private domUtils: CoreDomUtilsProvider, private platform: Platform, private appProvider: CoreAppProvider, private translate: TranslateService, private network: Network, private zone: NgZone, private config: Config, private contentLinksHelper: CoreContentLinksHelperProvider) { this.logger = logger.getInstance('CoreUtilsProvider'); } /** * Check if a frame uses an online URL but the app is offline. If it does, the iframe is hidden and a warning is shown. * * @param {any} element The frame to check (iframe, embed, ...). * @param {boolean} [isSubframe] Whether it's a frame inside another frame. * @return {boolean} True if frame is online and the app is offline, false otherwise. */ checkOnlineFrameInOffline(element: any, isSubframe?: boolean): boolean { const src = element.src || element.data; if (src && src.match(/^https?:\/\//i) && !this.appProvider.isOnline()) { if (element.classList.contains('core-iframe-offline-disabled')) { // Iframe already hidden, stop. return true; } // The frame has an online URL but the app is offline. Show a warning, or a link if the URL can be opened in the app. const div = document.createElement('div'); div.setAttribute('text-center', ''); div.setAttribute('padding', ''); div.classList.add('core-iframe-offline-warning'); const site = this.sitesProvider.getCurrentSite(); const username = site ? site.getInfo().username : undefined; this.contentLinksHelper.canHandleLink(src, undefined, username).then((canHandleLink) => { if (canHandleLink) { const link = document.createElement('a'); if (isSubframe) { // Ionic styles are not available in subframes, adding some minimal inline styles. link.style.display = 'block'; link.style.padding = '1em'; link.style.fontWeight = '500'; link.style.textAlign = 'center'; link.style.textTransform = 'uppercase'; link.style.cursor = 'pointer'; } else { const mode = this.config.get('mode'); link.setAttribute('ion-button', ''); link.classList.add('button', 'button-' + mode, 'button-default', 'button-default-' + mode, 'button-block', 'button-block-' + mode); } const message = this.translate.instant('core.viewembeddedcontent'); link.innerHTML = isSubframe ? message : '
'; link.onclick = (event: Event): void => { this.contentLinksHelper.handleLink(src, username); event.preventDefault(); }; div.appendChild(link); } else { div.innerHTML = (isSubframe ? '' : this.domUtils.getConnectionWarningIconHtml()) + '' + this.translate.instant('core.networkerroriframemsg') + '
'; } element.parentElement.insertBefore(div, element); }); // Add a class to specify that the iframe is hidden. element.classList.add('core-iframe-offline-disabled'); if (isSubframe) { // We cannot apply CSS styles in subframes, just hide the iframe. element.style.display = 'none'; } // If the network changes, check it again. const subscription = this.network.onConnect().subscribe(() => { // Execute the callback in the Angular zone, so change detection doesn't stop working. this.zone.run(() => { if (!this.checkOnlineFrameInOffline(element, isSubframe)) { // Now the app is online, no need to check connection again. subscription.unsubscribe(); } }); }); return true; } else if (element.classList.contains('core-iframe-offline-disabled')) { // Reload the frame. element.src = element.src; element.data = element.data; // Remove the warning and show the iframe this.domUtils.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); element.classList.remove('core-iframe-offline-disabled'); if (isSubframe) { element.style.display = ''; } } return false; } /** * Given an element, return the content window and document. * Please notice that the element should be an iframe, embed or similar. * * @param {any} element Element to treat (iframe, embed, ...). * @return {{ window: Window, document: Document }} Window and Document. */ getContentWindowAndDocument(element: any): { window: Window, document: Document } { let contentWindow: Window = element.contentWindow, contentDocument: Document; try { contentDocument = element.contentDocument || (contentWindow && contentWindow.document); } catch (ex) { // Ignore errors. } if (!contentWindow && contentDocument) { // It's probably an