-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathbasic-seamless-iframe.html
259 lines (220 loc) · 7.52 KB
/
basic-seamless-iframe.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
<!--
This component frames a page, potentially on a separate domain, and then
automatically communicates with a corresponding element on a framed page to
enable common cross-frame communication scenarios. For example, this component
can automatically adjust its height to match that of the framed page. (This
replicates the intent of the standard HTML "seamless" iframe attribute, which is
unfortunately implemented poorly on most browsers.)
This component can also be used to let you pass content back and forth between
the framed page and the outer page.
Such scenarios are normally blocked whenever an iframe hosts a page on a
different domain. Various web sources recommend that a frame and its contained
page communicate by means of window.postMessage(), but this is somewhat
cumbersome to set up. This component and its companion, basic-framed-content,
transparently set up and handle that postMessage-based communication for you.
If the framed page contains a basic-framed-content element, then:
1. The outer basic-seamless-iframe can set and inspect the content of the
inner basic-framed-content element.
2. The outer basic-seamless-iframe can adjust its height to exactly contain the
inner basic-framed-content element.
Note: This component does not currently support having two instances on the
same document that frame the same page.
@element basic-seamless-iframe
@demo ../packages/sample/basic-seamless-iframe/index.html
-->
<!--
TODO: Extend an HTML iframe element directly?
TODO: Give contained pages on the same domain more direct ways to cooperatively
communicate with this element.
TODO: Allow two instances of this component to frame the same page. This
requires coming up with unique way identify the content component; the code
currently uses frame src for that purpose, but that won't work if two frames
are showing the same page.
-->
<link rel="import" href="../polymer/polymer.html">
<dom-module id="basic-seamless-iframe">
<template>
<style>
:host {
display: block;
position: relative;
}
#iframe {
border: none;
display: block;
height: 100%;
position: absolute;
width: 100%;
}
</style>
<iframe id="iframe" src="{{src}}" scrollbars="no"></iframe>
</template>
</dom-module>
<script>
Polymer({
/**
* Fired when a basic-framed-content element within the framed page has had
* its content change.
*
* @event framedContentChanged
* @param Object detail The new content of the basic-framed-content element.
*/
/**
* Fired when a basic-framed-content element has completed loading its
* components and raised its polymer-ready event.
*
* @event framedWebComponentsReady
*/
is: 'basic-seamless-iframe',
properties: {
/**
* If true, the element will automatically adjust its height to match that
* of a contained basic-framed-content element on the framed page.
*
* @attribute autoSize
* @type boolean
* @default true
*/
autoSize: {
observer: '_autoSizeChanged',
type: Boolean,
value: true
},
/**
* The page to show in the iframe.
*
* For security purposes, this URL needs to exactly match the URL which will
* returned by the server. E.g., if the server will return a trailing slash,
* this URL should include a trailing slash to exactly match it.
*
* @attribute src
*/
src: {
observer: '_srcChanged',
type: String
}
},
ready: function() {
window.addEventListener('resize', function() {
if (this.autoSize && this.clientWidth !== this._clientWidth) {
// Ping framed content for its new -- potentially changed -- height.
this._postMessageToFrame('requestHeight');
this._clientWidth = this.clientWidth;
}
}.bind(this));
// Raise our own load event when the frame loads.
this.$.iframe.addEventListener('load', function() {
this.dispatchEvent(new CustomEvent('load'));
}.bind(this));
// Listen for messages posted by the framed page.
window.addEventListener('message', function(event) {
// For security reasons, only handle messages with the same origin as the
// framed page.
if (event.origin !== this._frameOrigin) {
return;
}
// We also check that the message (ostensibly) comes from the framed page.
// This can be spoofed, but isn't intended as a security measure. Rather,
// it's just a way that this component can indentify which messages are
// meant for it.
if (!event.data || event.data.source !== this.$.iframe.src) {
return;
}
switch (event.data && event.data.message) {
case 'framedContentResponse':
this._framedContentChanged(event.data.detail);
break;
case 'framedHeightResponse':
this._framedHeightChanged(event.data.detail);
break;
case 'framedWebComponentsReady':
this._framedWebComponentsReady();
break;
}
}.bind(this), false);
},
/**
* Reload the content of the frame.
*
* @method reload
*/
reload: function() {
// We'd like to just invoke this.$.iframe.contentWindow.location.reload().
// However, as of 11/3/2014, IE chokes on that. We workaround that by
// forcing the src on the frame again, which reloads the frame.
this.$.iframe.src = this.src;
},
/**
* Pass the indicated content to the basic-framed-content element within the
* framed page.
*
* @method setFramedContent
* @param content The content to pass to the basic-framed-content element.
*/
setFramedContent: function(content) {
this._pendingContent = content;
this._updateFramedContent();
},
_autoSizeChanged: function() {
if (this.autoSize) {
this._postMessageToFrame('requestHeight');
}
},
// Track the last known width of this element.
_clientWidth: null,
_framedContentChanged: function(framedContent) {
var event = new CustomEvent('framedContentChanged', {
detail: framedContent
});
this.dispatchEvent(event);
},
_framedHeightChanged: function(framedHeight) {
if (this.autoSize) {
this.style.minHeight = framedHeight + 'px';
}
},
_framedWebComponentsReady: function() {
this._frameReady = true;
var event = new CustomEvent('framedWebComponentsReady');
this.dispatchEvent(event);
this._updateFramedContent();
},
_frameReady: false,
// Content we want to write to the framed page when it's ready for it.
_pendingContent: null,
_srcChanged: function() {
// Extract the origin from the src using an anchor tag as a parser.
var a = document.createElement('a');
a.href = this.src;
var origin = a.origin;
if (origin == undefined) {
/* IE 10/11 links don't expose an origin property. Get it ourselves. */
var originRegex = /(.*\/\/[^\/]+)\//; /* Everything up to first slash in path. */
var match = originRegex.exec(a.href);
if (match) {
origin = match[1];
}
}
this._frameOrigin = origin;
},
_updateFramedContent: function() {
if (this._frameReady && this._pendingContent != null) {
this._postMessageToFrame('setContent', this._pendingContent);
this._pendingContent = null;
}
},
_postMessageToFrame: function(message, detail) {
if (!this._frameOrigin) {
// Not ready to post yet.
return;
}
var payload = {
message: message
};
if (detail != null) {
payload.detail = detail;
}
this.$.iframe.contentWindow.postMessage(payload, this._frameOrigin);
}
});
</script>