// (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, NavController } 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 element The frame to check (iframe, embed, ...).
* @param isSubframe Whether it's a frame inside another frame.
* @return 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 : '' + message + '';
link.onclick = (event: Event): void => {
this.contentLinksHelper.handleLink(src, username);
event.preventDefault();
};
div.appendChild(link);
} else {
div.innerHTML = (isSubframe ? '' : this.domUtils.getConnectionWarningIconHtml()) +
'
';
}
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 element Element to treat (iframe, embed, ...).
* @return 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