mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-11-30 02:03:14 +03:00
677d757a6c
Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
4832 lines
118 KiB
C
4832 lines
118 KiB
C
/*
|
|
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
|
|
* Copyright 2010 Daniel Silverstone <dsilvers@digital-scurf.org>
|
|
* Copyright 2010 Vincent Sanders <vince@netsurf-browser.org>
|
|
*
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
*
|
|
* NetSurf is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* NetSurf is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* Browser window creation and manipulation implementation.
|
|
*/
|
|
|
|
#include "utils/config.h"
|
|
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <math.h>
|
|
#include <nsutils/time.h>
|
|
#include <nsutils/base64.h>
|
|
|
|
#include "utils/corestrings.h"
|
|
#include "utils/log.h"
|
|
#include "utils/messages.h"
|
|
#include "utils/nsurl.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/utf8.h"
|
|
#include "utils/nsoption.h"
|
|
#include "netsurf/misc.h"
|
|
#include "netsurf/window.h"
|
|
#include "netsurf/content.h"
|
|
#include "netsurf/plotters.h"
|
|
#include "content/content_debug.h"
|
|
#include "content/fetch.h"
|
|
#include "content/hlcache.h"
|
|
#include "content/urldb.h"
|
|
#include "css/utils.h"
|
|
#include "html/form_internal.h"
|
|
#include "html/html.h"
|
|
#include "html/box.h"
|
|
#include "javascript/js.h"
|
|
|
|
#include "desktop/cookie_manager.h"
|
|
#include "desktop/browser_history.h"
|
|
#include "desktop/browser_private.h"
|
|
#include "desktop/download.h"
|
|
#include "desktop/frames.h"
|
|
#include "desktop/global_history.h"
|
|
#include "desktop/hotlist.h"
|
|
#include "desktop/knockout.h"
|
|
#include "desktop/scrollbar.h"
|
|
#include "desktop/selection.h"
|
|
#include "desktop/theme.h"
|
|
#include "desktop/gui_internal.h"
|
|
#include "desktop/textinput.h"
|
|
|
|
/**
|
|
* smallest scale that can be applied to a browser window
|
|
*/
|
|
#define SCALE_MINIMUM 0.2
|
|
|
|
/**
|
|
* largests scale that can be applied to a browser window
|
|
*/
|
|
#define SCALE_MAXIMUM 10.0
|
|
|
|
/**
|
|
* maximum frame depth
|
|
*/
|
|
#define FRAME_DEPTH 8
|
|
|
|
/* Have to forward declare browser_window_destroy_internal */
|
|
static void browser_window_destroy_internal(struct browser_window *bw);
|
|
|
|
/* Forward declare internal navigation function */
|
|
static nserror browser_window__navigate_internal(
|
|
struct browser_window *bw, struct browser_fetch_parameters *params);
|
|
|
|
|
|
/**
|
|
* Close and destroy all child browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
static void browser_window_destroy_children(struct browser_window *bw)
|
|
{
|
|
int i;
|
|
|
|
if (bw->children) {
|
|
for (i = 0; i < (bw->rows * bw->cols); i++) {
|
|
browser_window_destroy_internal(&bw->children[i]);
|
|
}
|
|
free(bw->children);
|
|
bw->children = NULL;
|
|
bw->rows = 0;
|
|
bw->cols = 0;
|
|
}
|
|
if (bw->iframes) {
|
|
for (i = 0; i < bw->iframe_count; i++) {
|
|
browser_window_destroy_internal(&bw->iframes[i]);
|
|
}
|
|
free(bw->iframes);
|
|
bw->iframes = NULL;
|
|
bw->iframe_count = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Free the stored fetch parameters
|
|
*
|
|
* \param bw The browser window
|
|
*/
|
|
static void
|
|
browser_window__free_fetch_parameters(struct browser_fetch_parameters *params)
|
|
{
|
|
if (params->url != NULL) {
|
|
nsurl_unref(params->url);
|
|
params->url = NULL;
|
|
}
|
|
if (params->referrer != NULL) {
|
|
nsurl_unref(params->referrer);
|
|
params->referrer = NULL;
|
|
}
|
|
if (params->post_urlenc != NULL) {
|
|
free(params->post_urlenc);
|
|
params->post_urlenc = NULL;
|
|
}
|
|
if (params->post_multipart != NULL) {
|
|
fetch_multipart_data_destroy(params->post_multipart);
|
|
params->post_multipart = NULL;
|
|
}
|
|
if (params->parent_charset != NULL) {
|
|
free(params->parent_charset);
|
|
params->parent_charset = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get position of scrollbar widget within browser window.
|
|
*
|
|
* \param bw The browser window
|
|
* \param horizontal Whether to get position of horizontal scrollbar
|
|
* \param x Updated to x-coord of top left of scrollbar widget
|
|
* \param y Updated to y-coord of top left of scrollbar widget
|
|
*/
|
|
static inline void
|
|
browser_window_get_scrollbar_pos(struct browser_window *bw,
|
|
bool horizontal,
|
|
int *x, int *y)
|
|
{
|
|
if (horizontal) {
|
|
*x = 0;
|
|
*y = bw->height - SCROLLBAR_WIDTH;
|
|
} else {
|
|
*x = bw->width - SCROLLBAR_WIDTH;
|
|
*y = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get browser window horizontal scrollbar widget length
|
|
*
|
|
* \param bw The browser window
|
|
* \return the scrollbar's length
|
|
*/
|
|
static inline int get_horz_scrollbar_len(struct browser_window *bw)
|
|
{
|
|
if (bw->scroll_y == NULL) {
|
|
return bw->width;
|
|
}
|
|
return bw->width - SCROLLBAR_WIDTH;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get browser window vertical scrollbar widget length
|
|
*
|
|
* \param bw The browser window
|
|
* \return the scrollbar's length
|
|
*/
|
|
static inline int get_vert_scrollbar_len(struct browser_window *bw)
|
|
{
|
|
return bw->height;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set or remove a selection.
|
|
*
|
|
* \param bw browser window with selection
|
|
* \param selection true if bw has a selection, false if removing selection
|
|
* \param read_only true iff selection is read only (e.g. can't cut it)
|
|
*/
|
|
static void
|
|
browser_window_set_selection(struct browser_window *bw,
|
|
bool selection,
|
|
bool read_only)
|
|
{
|
|
struct browser_window *top;
|
|
|
|
assert(bw != NULL);
|
|
|
|
top = browser_window_get_root(bw);
|
|
|
|
assert(top != NULL);
|
|
|
|
if (bw != top->selection.bw &&
|
|
top->selection.bw != NULL &&
|
|
top->selection.bw->current_content != NULL) {
|
|
/* clear old selection */
|
|
content_clear_selection(top->selection.bw->current_content);
|
|
}
|
|
|
|
if (selection) {
|
|
top->selection.bw = bw;
|
|
} else {
|
|
top->selection.bw = NULL;
|
|
}
|
|
|
|
top->selection.read_only = read_only;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the scroll position of a browser window.
|
|
*
|
|
* scrolls the viewport to ensure the specified rectangle of the
|
|
* content is shown.
|
|
*
|
|
* \param bw window to scroll
|
|
* \param rect The rectangle to ensure is shown.
|
|
* \return NSERROR_OK on success or apropriate error code.
|
|
*/
|
|
static nserror
|
|
browser_window_set_scroll(struct browser_window *bw, const struct rect *rect)
|
|
{
|
|
if (bw->window != NULL) {
|
|
return guit->window->set_scroll(bw->window, rect);
|
|
}
|
|
|
|
if (bw->scroll_x != NULL) {
|
|
scrollbar_set(bw->scroll_x, rect->x0, false);
|
|
}
|
|
if (bw->scroll_y != NULL) {
|
|
scrollbar_set(bw->scroll_y, rect->y0, false);
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Internal helper for getting the positional features
|
|
*
|
|
* \param[in] bw browser window to examine.
|
|
* \param[in] x x-coordinate of point of interest
|
|
* \param[in] y y-coordinate of point of interest
|
|
* \param[out] data Feature structure to update.
|
|
* \return NSERROR_OK or appropriate error code on faliure.
|
|
*/
|
|
static nserror
|
|
browser_window__get_contextual_content(struct browser_window *bw,
|
|
int x, int y,
|
|
struct browser_window_features *data)
|
|
{
|
|
nserror ret = NSERROR_OK;
|
|
|
|
/* Handle (i)frame scroll offset (core-managed browser windows only) */
|
|
x += scrollbar_get_offset(bw->scroll_x);
|
|
y += scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children, so pass request on to
|
|
* appropriate child.
|
|
*/
|
|
struct browser_window *bwc;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
/* Loop through all children of bw */
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
/* Set current child */
|
|
bwc = &bw->children[cur_child];
|
|
|
|
/* Skip this frame if (x, y) coord lies outside */
|
|
if ((x < bwc->x) ||
|
|
(bwc->x + bwc->width < x) ||
|
|
(y < bwc->y) ||
|
|
(bwc->y + bwc->height < y)) {
|
|
continue;
|
|
}
|
|
|
|
/* Pass request into this child */
|
|
return browser_window__get_contextual_content(bwc,
|
|
(x - bwc->x), (y - bwc->y), data);
|
|
}
|
|
|
|
/* Coordinate not contained by any frame */
|
|
|
|
} else if (bw->current_content != NULL) {
|
|
/* Pass request to content */
|
|
ret = content_get_contextual_content(bw->current_content,
|
|
x, y, data);
|
|
data->main = bw->current_content;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* implements the download operation of a window navigate
|
|
*/
|
|
static nserror
|
|
browser_window_download(struct browser_window *bw,
|
|
nsurl *url,
|
|
nsurl *nsref,
|
|
uint32_t fetch_flags,
|
|
bool fetch_is_post,
|
|
llcache_post_data *post)
|
|
{
|
|
llcache_handle *l;
|
|
struct browser_window *root;
|
|
nserror error;
|
|
|
|
root = browser_window_get_root(bw);
|
|
assert(root != NULL);
|
|
|
|
fetch_flags |= LLCACHE_RETRIEVE_FORCE_FETCH;
|
|
fetch_flags |= LLCACHE_RETRIEVE_STREAM_DATA;
|
|
|
|
error = llcache_handle_retrieve(url, fetch_flags, nsref,
|
|
fetch_is_post ? post : NULL,
|
|
NULL, NULL, &l);
|
|
if (error == NSERROR_NO_FETCH_HANDLER) {
|
|
/* no internal handler for this type, call out to frontend */
|
|
error = guit->misc->launch_url(url);
|
|
} else if (error != NSERROR_OK) {
|
|
NSLOG(netsurf, INFO, "Failed to fetch download: %d", error);
|
|
} else {
|
|
error = download_context_create(l, root->window);
|
|
if (error != NSERROR_OK) {
|
|
NSLOG(netsurf, INFO,
|
|
"Failed creating download context: %d", error);
|
|
llcache_handle_abort(l);
|
|
llcache_handle_release(l);
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
/**
|
|
* recursively check browser windows for activity
|
|
*
|
|
* \param bw browser window to start checking from.
|
|
*/
|
|
static bool browser_window_check_throbber(struct browser_window *bw)
|
|
{
|
|
int children, index;
|
|
|
|
if (bw->throbbing)
|
|
return true;
|
|
|
|
if (bw->children) {
|
|
children = bw->rows * bw->cols;
|
|
for (index = 0; index < children; index++) {
|
|
if (browser_window_check_throbber(&bw->children[index]))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (bw->iframes) {
|
|
for (index = 0; index < bw->iframe_count; index++) {
|
|
if (browser_window_check_throbber(&bw->iframes[index]))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Start the busy indicator.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
static nserror browser_window_start_throbber(struct browser_window *bw)
|
|
{
|
|
bw->throbbing = true;
|
|
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
|
|
return guit->window->event(bw->window, GW_EVENT_START_THROBBER);
|
|
}
|
|
|
|
|
|
/**
|
|
* Stop the busy indicator.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
static nserror browser_window_stop_throbber(struct browser_window *bw)
|
|
{
|
|
nserror res = NSERROR_OK;
|
|
|
|
bw->throbbing = false;
|
|
|
|
while (bw->parent) {
|
|
bw = bw->parent;
|
|
}
|
|
|
|
if (!browser_window_check_throbber(bw)) {
|
|
res = guit->window->event(bw->window, GW_EVENT_STOP_THROBBER);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for fetchcache() for browser window favicon fetches.
|
|
*
|
|
* \param c content handle of favicon
|
|
* \param event The event to process
|
|
* \param pw a context containing the browser window
|
|
* \return NSERROR_OK on success else appropriate error code.
|
|
*/
|
|
static nserror
|
|
browser_window_favicon_callback(hlcache_handle *c,
|
|
const hlcache_event *event,
|
|
void *pw)
|
|
{
|
|
struct browser_window *bw = pw;
|
|
|
|
switch (event->type) {
|
|
case CONTENT_MSG_DONE:
|
|
if (bw->favicon.current != NULL) {
|
|
content_close(bw->favicon.current);
|
|
hlcache_handle_release(bw->favicon.current);
|
|
}
|
|
|
|
bw->favicon.current = c;
|
|
bw->favicon.loading = NULL;
|
|
|
|
/* content_get_bitmap on the hlcache_handle should give
|
|
* the favicon bitmap at this point
|
|
*/
|
|
guit->window->set_icon(bw->window, c);
|
|
break;
|
|
|
|
case CONTENT_MSG_ERROR:
|
|
|
|
/* clean up after ourselves */
|
|
if (c == bw->favicon.loading) {
|
|
bw->favicon.loading = NULL;
|
|
} else if (c == bw->favicon.current) {
|
|
bw->favicon.current = NULL;
|
|
}
|
|
|
|
hlcache_handle_release(c);
|
|
|
|
if (bw->favicon.failed == false) {
|
|
nsurl *nsref = NULL;
|
|
nsurl *nsurl;
|
|
nserror error;
|
|
|
|
bw->favicon.failed = true;
|
|
|
|
error = nsurl_create("resource:favicon.ico", &nsurl);
|
|
if (error != NSERROR_OK) {
|
|
NSLOG(netsurf, INFO,
|
|
"Unable to create default location url");
|
|
} else {
|
|
hlcache_handle_retrieve(nsurl,
|
|
HLCACHE_RETRIEVE_SNIFF_TYPE,
|
|
nsref, NULL,
|
|
browser_window_favicon_callback,
|
|
bw, NULL, CONTENT_IMAGE,
|
|
&bw->favicon.loading);
|
|
|
|
nsurl_unref(nsurl);
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* update the favicon associated with the browser window
|
|
*
|
|
* \param c the page content handle.
|
|
* \param bw A top level browser window.
|
|
* \param link A link context or NULL to attempt fallback scanning.
|
|
*/
|
|
static nserror
|
|
browser_window_update_favicon(hlcache_handle *c,
|
|
struct browser_window *bw,
|
|
struct content_rfc5988_link *link)
|
|
{
|
|
nsurl *nsref = NULL;
|
|
nsurl *nsurl;
|
|
nserror res;
|
|
|
|
assert(c != NULL);
|
|
assert(bw !=NULL);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Not top-level browser window; not interested */
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/* already fetching the favicon - use that */
|
|
if (bw->favicon.loading != NULL) {
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
bw->favicon.failed = false;
|
|
|
|
if (link == NULL) {
|
|
/* Look for "icon" */
|
|
link = content_find_rfc5988_link(c, corestring_lwc_icon);
|
|
}
|
|
|
|
if (link == NULL) {
|
|
/* Look for "shortcut icon" */
|
|
link = content_find_rfc5988_link(c, corestring_lwc_shortcut_icon);
|
|
}
|
|
|
|
if (link == NULL) {
|
|
lwc_string *scheme;
|
|
bool speculative_default = false;
|
|
bool match;
|
|
|
|
nsurl = hlcache_handle_get_url(c);
|
|
|
|
scheme = nsurl_get_component(nsurl, NSURL_SCHEME);
|
|
|
|
/* If the document was fetched over http(s), then speculate
|
|
* that there's a favicon living at /favicon.ico */
|
|
if ((lwc_string_caseless_isequal(scheme,
|
|
corestring_lwc_http,
|
|
&match) == lwc_error_ok &&
|
|
match) ||
|
|
(lwc_string_caseless_isequal(scheme,
|
|
corestring_lwc_https,
|
|
&match) == lwc_error_ok &&
|
|
match)) {
|
|
speculative_default = true;
|
|
}
|
|
|
|
lwc_string_unref(scheme);
|
|
|
|
if (speculative_default) {
|
|
/* no favicon via link, try for the default location */
|
|
res = nsurl_join(nsurl, "/favicon.ico", &nsurl);
|
|
} else {
|
|
bw->favicon.failed = true;
|
|
res = nsurl_create("resource:favicon.ico", &nsurl);
|
|
}
|
|
if (res != NSERROR_OK) {
|
|
NSLOG(netsurf, INFO,
|
|
"Unable to create default location url");
|
|
return res;
|
|
}
|
|
} else {
|
|
nsurl = nsurl_ref(link->href);
|
|
}
|
|
|
|
if (link == NULL) {
|
|
NSLOG(netsurf, INFO,
|
|
"fetching general favicon from '%s'",
|
|
nsurl_access(nsurl));
|
|
} else {
|
|
NSLOG(netsurf, INFO,
|
|
"fetching favicon rel:%s '%s'",
|
|
lwc_string_data(link->rel),
|
|
nsurl_access(nsurl));
|
|
}
|
|
|
|
res = hlcache_handle_retrieve(nsurl,
|
|
HLCACHE_RETRIEVE_SNIFF_TYPE,
|
|
nsref,
|
|
NULL,
|
|
browser_window_favicon_callback,
|
|
bw,
|
|
NULL,
|
|
CONTENT_IMAGE,
|
|
&bw->favicon.loading);
|
|
|
|
nsurl_unref(nsurl);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle meta http-equiv refresh time elapsing by loading a new page.
|
|
*
|
|
* \param p browser window to refresh with new page
|
|
*/
|
|
static void browser_window_refresh(void *p)
|
|
{
|
|
struct browser_window *bw = p;
|
|
nsurl *url;
|
|
nsurl *refresh;
|
|
hlcache_handle *parent = NULL;
|
|
enum browser_window_nav_flags flags = BW_NAVIGATE_UNVERIFIABLE;
|
|
|
|
assert(bw->current_content != NULL &&
|
|
(content_get_status(bw->current_content) ==
|
|
CONTENT_STATUS_READY ||
|
|
content_get_status(bw->current_content) ==
|
|
CONTENT_STATUS_DONE));
|
|
|
|
/* Ignore if the refresh URL has gone
|
|
* (may happen if a fetch error occurred) */
|
|
refresh = content_get_refresh_url(bw->current_content);
|
|
if (refresh == NULL)
|
|
return;
|
|
|
|
/* mark this content as invalid so it gets flushed from the cache */
|
|
content_invalidate_reuse_data(bw->current_content);
|
|
|
|
url = hlcache_handle_get_url(bw->current_content);
|
|
if ((url == NULL) || (nsurl_compare(url, refresh, NSURL_COMPLETE))) {
|
|
flags |= BW_NAVIGATE_HISTORY;
|
|
}
|
|
|
|
/* Treat an (almost) immediate refresh in a top-level browser window as
|
|
* if it were an HTTP redirect, and thus make the resulting fetch
|
|
* verifiable.
|
|
*
|
|
* See fetchcache.c for why redirected fetches should be verifiable at
|
|
* all.
|
|
*/
|
|
if (bw->refresh_interval <= 100 && bw->parent == NULL) {
|
|
flags &= ~BW_NAVIGATE_UNVERIFIABLE;
|
|
} else {
|
|
parent = bw->current_content;
|
|
}
|
|
|
|
browser_window_navigate(bw,
|
|
refresh,
|
|
url,
|
|
flags,
|
|
NULL,
|
|
NULL,
|
|
parent);
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Transfer the loading_content to a new download window.
|
|
*/
|
|
static void
|
|
browser_window_convert_to_download(struct browser_window *bw,
|
|
llcache_handle *stream)
|
|
{
|
|
struct browser_window *root = browser_window_get_root(bw);
|
|
nserror error;
|
|
|
|
assert(root != NULL);
|
|
|
|
error = download_context_create(stream, root->window);
|
|
if (error != NSERROR_OK) {
|
|
llcache_handle_abort(stream);
|
|
llcache_handle_release(stream);
|
|
}
|
|
|
|
/* remove content from browser window */
|
|
hlcache_handle_release(bw->loading_content);
|
|
bw->loading_content = NULL;
|
|
|
|
browser_window_stop_throbber(bw);
|
|
}
|
|
|
|
|
|
/**
|
|
* handle message for content ready on browser window
|
|
*/
|
|
static nserror browser_window_content_ready(struct browser_window *bw)
|
|
{
|
|
int width, height;
|
|
nserror res = NSERROR_OK;
|
|
|
|
/* close and release the current window content */
|
|
if (bw->current_content != NULL) {
|
|
content_close(bw->current_content);
|
|
hlcache_handle_release(bw->current_content);
|
|
}
|
|
|
|
bw->current_content = bw->loading_content;
|
|
bw->loading_content = NULL;
|
|
|
|
if (!bw->internal_nav) {
|
|
/* Transfer the fetch parameters */
|
|
browser_window__free_fetch_parameters(&bw->current_parameters);
|
|
bw->current_parameters = bw->loading_parameters;
|
|
memset(&bw->loading_parameters, 0, sizeof(bw->loading_parameters));
|
|
/* Transfer the certificate chain */
|
|
cert_chain_free(bw->current_cert_chain);
|
|
bw->current_cert_chain = bw->loading_cert_chain;
|
|
bw->loading_cert_chain = NULL;
|
|
}
|
|
|
|
/* Format the new content to the correct dimensions */
|
|
browser_window_get_dimensions(bw, &width, &height);
|
|
width /= bw->scale;
|
|
height /= bw->scale;
|
|
content_reformat(bw->current_content, false, width, height);
|
|
|
|
/* history */
|
|
if (bw->history_add && bw->history && !bw->internal_nav) {
|
|
nsurl *url = hlcache_handle_get_url(bw->current_content);
|
|
|
|
if (urldb_add_url(url)) {
|
|
urldb_set_url_title(url, content_get_title(bw->current_content));
|
|
urldb_update_url_visit_data(url);
|
|
urldb_set_url_content_type(url,
|
|
content_get_type(bw->current_content));
|
|
|
|
/* This is safe as we've just added the URL */
|
|
global_history_add(urldb_get_url(url));
|
|
}
|
|
/**
|
|
* \todo Urldb / Thumbnails / Local history brokenness
|
|
*
|
|
* We add to local history after calling urldb_add_url rather
|
|
* than in the block above. If urldb_add_url fails (as it
|
|
* will for urls like "about:about", "about:config" etc),
|
|
* there would be no local history node, and later calls to
|
|
* history_update will either explode or overwrite the node
|
|
* for the previous URL.
|
|
*
|
|
* We call it after, rather than before urldb_add_url because
|
|
* history_add calls bitmap render, which tries to register
|
|
* the thumbnail with urldb. That thumbnail registration
|
|
* fails if the url doesn't exist in urldb already, and only
|
|
* urldb-registered thumbnails get freed. So if we called
|
|
* history_add before urldb_add_url we would leak thumbnails
|
|
* for all newly visited URLs. With the history_add call
|
|
* after, we only leak the thumbnails when urldb does not add
|
|
* the URL.
|
|
*
|
|
* Also, since browser_window_history_add can create a
|
|
* thumbnail (content_redraw), we need to do it after
|
|
* content_reformat.
|
|
*/
|
|
browser_window_history_add(bw, bw->current_content, bw->frag_id);
|
|
}
|
|
|
|
browser_window_remove_caret(bw, false);
|
|
|
|
if (bw->window != NULL) {
|
|
guit->window->event(bw->window, GW_EVENT_NEW_CONTENT);
|
|
|
|
browser_window_refresh_url_bar(bw);
|
|
}
|
|
|
|
/* new content; set scroll_to_top */
|
|
browser_window_update(bw, true);
|
|
content_open(bw->current_content, bw, 0, 0);
|
|
browser_window_set_status(bw, content_get_status_message(bw->current_content));
|
|
|
|
/* frames */
|
|
if ((content_get_type(bw->current_content) == CONTENT_HTML) &&
|
|
(html_get_frameset(bw->current_content) != NULL)) {
|
|
res = browser_window_create_frameset(bw, html_get_frameset(bw->current_content));
|
|
}
|
|
|
|
if (content_get_type(bw->current_content) == CONTENT_HTML &&
|
|
html_get_iframe(bw->current_content) != NULL) {
|
|
browser_window_create_iframes(bw, html_get_iframe(bw->current_content));
|
|
}
|
|
|
|
/* Indicate page status may have changed */
|
|
if (res == NSERROR_OK) {
|
|
struct browser_window *root = browser_window_get_root(bw);
|
|
res = guit->window->event(root->window, GW_EVENT_PAGE_INFO_CHANGE);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* handle message for content done on browser window
|
|
*/
|
|
static nserror
|
|
browser_window_content_done(struct browser_window *bw)
|
|
{
|
|
float sx, sy;
|
|
struct rect rect;
|
|
int scrollx;
|
|
int scrolly;
|
|
|
|
if (bw->window == NULL) {
|
|
/* Updated browser window's scrollbars. */
|
|
/**
|
|
* \todo update browser window scrollbars before CONTENT_MSG_DONE
|
|
*/
|
|
browser_window_reformat(bw, true, bw->width, bw->height);
|
|
browser_window_handle_scrollbars(bw);
|
|
}
|
|
|
|
browser_window_update(bw, false);
|
|
browser_window_set_status(bw, content_get_status_message(bw->current_content));
|
|
browser_window_stop_throbber(bw);
|
|
browser_window_update_favicon(bw->current_content, bw, NULL);
|
|
|
|
if (browser_window_history_get_scroll(bw, &sx, &sy) == NSERROR_OK) {
|
|
scrollx = (int)((float)content_get_width(bw->current_content) * sx);
|
|
scrolly = (int)((float)content_get_height(bw->current_content) * sy);
|
|
rect.x0 = rect.x1 = scrollx;
|
|
rect.y0 = rect.y1 = scrolly;
|
|
if (browser_window_set_scroll(bw, &rect) != NSERROR_OK) {
|
|
NSLOG(netsurf, WARNING,
|
|
"Unable to set browser scroll offsets to %d by %d",
|
|
scrollx, scrolly);
|
|
}
|
|
}
|
|
|
|
if (!bw->internal_nav) {
|
|
browser_window_history_update(bw, bw->current_content);
|
|
hotlist_update_url(hlcache_handle_get_url(bw->current_content));
|
|
}
|
|
|
|
if (bw->refresh_interval != -1) {
|
|
guit->misc->schedule(bw->refresh_interval * 10,
|
|
browser_window_refresh, bw);
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle query responses from SSL requests
|
|
*/
|
|
static nserror
|
|
browser_window__handle_ssl_query_response(bool proceed, void *pw)
|
|
{
|
|
struct browser_window *bw = (struct browser_window *)pw;
|
|
|
|
/* If we're in the process of loading, stop the load */
|
|
if (bw->loading_content != NULL) {
|
|
/* We had a loading content (maybe auth page?) */
|
|
browser_window_stop(bw);
|
|
browser_window_remove_caret(bw, false);
|
|
browser_window_destroy_children(bw);
|
|
}
|
|
|
|
if (!proceed) {
|
|
/* We're processing a "back to safety", do a rough-and-ready
|
|
* nav to the old 'current' parameters, with any post data
|
|
* stripped away
|
|
*/
|
|
return browser_window__reload_current_parameters(bw);
|
|
}
|
|
|
|
/* We're processing a "proceed" attempt from the form */
|
|
/* First, we permit the SSL */
|
|
urldb_set_cert_permissions(bw->loading_parameters.url, true);
|
|
|
|
/* And then we navigate to the original loading parameters */
|
|
bw->internal_nav = false;
|
|
|
|
return browser_window__navigate_internal(bw, &bw->loading_parameters);
|
|
}
|
|
|
|
|
|
/**
|
|
* Unpack a "username:password" to components.
|
|
*
|
|
* \param[in] userpass The input string to split.
|
|
* \param[in] username_out Returns username on success. Owned by caller.
|
|
* \param[out] password_out Returns password on success. Owned by caller.
|
|
* \return NSERROR_OK, or appropriate error code.
|
|
*/
|
|
static nserror
|
|
browser_window__unpack_userpass(const char *userpass,
|
|
char **username_out,
|
|
char **password_out)
|
|
{
|
|
const char *tmp;
|
|
char *username;
|
|
char *password;
|
|
size_t len;
|
|
|
|
if (userpass == NULL) {
|
|
username = malloc(1);
|
|
password = malloc(1);
|
|
if (username == NULL || password == NULL) {
|
|
free(username);
|
|
free(password);
|
|
return NSERROR_NOMEM;
|
|
}
|
|
username[0] = '\0';
|
|
password[0] = '\0';
|
|
|
|
*username_out = username;
|
|
*password_out = password;
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
tmp = strchr(userpass, ':');
|
|
if (tmp == NULL) {
|
|
return NSERROR_BAD_PARAMETER;
|
|
} else {
|
|
size_t len2;
|
|
len = tmp - userpass;
|
|
len2 = strlen(++tmp);
|
|
|
|
username = malloc(len + 1);
|
|
password = malloc(len2 + 1);
|
|
if (username == NULL || password == NULL) {
|
|
free(username);
|
|
free(password);
|
|
return NSERROR_NOMEM;
|
|
}
|
|
memcpy(username, userpass, len);
|
|
username[len] = '\0';
|
|
memcpy(password, tmp, len2 + 1);
|
|
}
|
|
|
|
*username_out = username;
|
|
*password_out = password;
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Build a "username:password" from components.
|
|
*
|
|
* \param[in] username The username component.
|
|
* \param[in] password The password component.
|
|
* \param[out] userpass_out Returns combined string on success.
|
|
* Owned by caller.
|
|
* \return NSERROR_OK, or appropriate error code.
|
|
*/
|
|
static nserror
|
|
browser_window__build_userpass(const char *username,
|
|
const char *password,
|
|
char **userpass_out)
|
|
{
|
|
char *userpass;
|
|
size_t len;
|
|
|
|
len = strlen(username) + 1 + strlen(password) + 1;
|
|
|
|
userpass = malloc(len);
|
|
if (userpass == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
snprintf(userpass, len, "%s:%s", username, password);
|
|
|
|
*userpass_out = userpass;
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle a response from the UI when prompted for credentials
|
|
*/
|
|
static nserror
|
|
browser_window__handle_userpass_response(nsurl *url,
|
|
const char *realm,
|
|
const char *username,
|
|
const char *password,
|
|
void *pw)
|
|
{
|
|
struct browser_window *bw = (struct browser_window *)pw;
|
|
char *userpass;
|
|
nserror err;
|
|
|
|
err = browser_window__build_userpass(username, password, &userpass);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
urldb_set_auth_details(url, realm, userpass);
|
|
|
|
free(userpass);
|
|
|
|
/**
|
|
* \todo QUERY - Eventually this should fill out the form *NOT* nav
|
|
* to the original location
|
|
*/
|
|
/* Finally navigate to the original loading parameters */
|
|
if (bw->loading_content != NULL) {
|
|
/* We had a loading content (maybe auth page?) */
|
|
browser_window_stop(bw);
|
|
browser_window_remove_caret(bw, false);
|
|
browser_window_destroy_children(bw);
|
|
}
|
|
bw->internal_nav = false;
|
|
return browser_window__navigate_internal(bw, &bw->loading_parameters);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle login request (BAD_AUTH) during fetch
|
|
*
|
|
*/
|
|
static nserror
|
|
browser_window__handle_login(struct browser_window *bw,
|
|
const char *realm,
|
|
nsurl *url) {
|
|
char *username = NULL, *password = NULL;
|
|
nserror err = NSERROR_OK;
|
|
struct browser_fetch_parameters params;
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
/* Step one, retrieve what we have */
|
|
err = browser_window__unpack_userpass(
|
|
urldb_get_auth_details(url, realm),
|
|
&username, &password);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
/* Step two, construct our fetch parameters */
|
|
params.url = nsurl_ref(corestring_nsurl_about_query_auth);
|
|
params.referrer = nsurl_ref(url);
|
|
params.flags = BW_NAVIGATE_HISTORY | BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL;
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"siteurl",
|
|
nsurl_access(url));
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"realm",
|
|
realm);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"username",
|
|
username);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"password",
|
|
password);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
/* Now we issue the fetch */
|
|
bw->internal_nav = true;
|
|
err = browser_window__navigate_internal(bw, ¶ms);
|
|
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
err = guit->misc->login(url, realm, username, password,
|
|
browser_window__handle_userpass_response, bw);
|
|
|
|
if (err == NSERROR_NOT_IMPLEMENTED) {
|
|
err = NSERROR_OK;
|
|
}
|
|
out:
|
|
if (username != NULL) {
|
|
free(username);
|
|
}
|
|
if (password != NULL) {
|
|
free(password);
|
|
}
|
|
browser_window__free_fetch_parameters(¶ms);
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle a certificate verification request (BAD_CERTS) during a fetch
|
|
*/
|
|
static nserror
|
|
browser_window__handle_bad_certs(struct browser_window *bw,
|
|
nsurl *url)
|
|
{
|
|
struct browser_fetch_parameters params;
|
|
nserror err;
|
|
/* Initially we don't know WHY the SSL cert was bad */
|
|
const char *reason = messages_get_sslcode(SSL_CERT_ERR_UNKNOWN);
|
|
size_t depth;
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
params.url = nsurl_ref(corestring_nsurl_about_query_ssl);
|
|
params.referrer = nsurl_ref(url);
|
|
params.flags = BW_NAVIGATE_HISTORY | BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL;
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"siteurl",
|
|
nsurl_access(url));
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
if (bw->loading_cert_chain != NULL) {
|
|
for (depth = 0; depth < bw->loading_cert_chain->depth; ++depth) {
|
|
size_t idx = bw->loading_cert_chain->depth - (depth + 1);
|
|
ssl_cert_err err = bw->loading_cert_chain->certs[idx].err;
|
|
if (err != SSL_CERT_ERR_OK) {
|
|
reason = messages_get_sslcode(err);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"reason",
|
|
reason);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
/* Now we issue the fetch */
|
|
bw->internal_nav = true;
|
|
err = browser_window__navigate_internal(bw, ¶ms);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
err = guit->misc->cert_verify(url,
|
|
bw->loading_cert_chain,
|
|
browser_window__handle_ssl_query_response,
|
|
bw);
|
|
|
|
if (err == NSERROR_NOT_IMPLEMENTED) {
|
|
err = NSERROR_OK;
|
|
}
|
|
out:
|
|
browser_window__free_fetch_parameters(¶ms);
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle a timeout during a fetch
|
|
*/
|
|
static nserror
|
|
browser_window__handle_timeout(struct browser_window *bw, nsurl *url)
|
|
{
|
|
struct browser_fetch_parameters params;
|
|
nserror err;
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
params.url = nsurl_ref(corestring_nsurl_about_query_timeout);
|
|
params.referrer = nsurl_ref(url);
|
|
params.flags = BW_NAVIGATE_HISTORY | BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL;
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"siteurl",
|
|
nsurl_access(url));
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
/* Now we issue the fetch */
|
|
bw->internal_nav = true;
|
|
err = browser_window__navigate_internal(bw, ¶ms);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
browser_window__free_fetch_parameters(¶ms);
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle non specific errors during a fetch
|
|
*/
|
|
static nserror
|
|
browser_window__handle_fetcherror(struct browser_window *bw,
|
|
const char *reason,
|
|
nsurl *url)
|
|
{
|
|
struct browser_fetch_parameters params;
|
|
nserror err;
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
params.url = nsurl_ref(corestring_nsurl_about_query_fetcherror);
|
|
params.referrer = nsurl_ref(url);
|
|
params.flags = BW_NAVIGATE_HISTORY | BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL;
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"siteurl",
|
|
nsurl_access(url));
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
err = fetch_multipart_data_new_kv(¶ms.post_multipart,
|
|
"reason",
|
|
reason);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
/* Now we issue the fetch */
|
|
bw->internal_nav = true;
|
|
err = browser_window__navigate_internal(bw, ¶ms);
|
|
if (err != NSERROR_OK) {
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
browser_window__free_fetch_parameters(¶ms);
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle errors during content fetch
|
|
*/
|
|
static nserror
|
|
browser_window__handle_error(struct browser_window *bw,
|
|
hlcache_handle *c,
|
|
const hlcache_event *event)
|
|
{
|
|
const char *message = event->data.errordata.errormsg;
|
|
nserror code = event->data.errordata.errorcode;
|
|
nserror res;
|
|
nsurl *url = hlcache_handle_get_url(c);
|
|
|
|
/* Unexpected OK? */
|
|
assert(code != NSERROR_OK);
|
|
|
|
if (message == NULL) {
|
|
message = messages_get_errorcode(code);
|
|
} else {
|
|
message = messages_get(message);
|
|
}
|
|
|
|
if (c == bw->loading_content) {
|
|
bw->loading_content = NULL;
|
|
} else if (c == bw->current_content) {
|
|
bw->current_content = NULL;
|
|
browser_window_remove_caret(bw, false);
|
|
}
|
|
|
|
hlcache_handle_release(c);
|
|
|
|
switch (code) {
|
|
case NSERROR_BAD_AUTH:
|
|
res = browser_window__handle_login(bw, message, url);
|
|
break;
|
|
|
|
case NSERROR_BAD_CERTS:
|
|
res = browser_window__handle_bad_certs(bw, url);
|
|
break;
|
|
|
|
case NSERROR_TIMEOUT:
|
|
res = browser_window__handle_timeout(bw, url);
|
|
break;
|
|
|
|
default:
|
|
res = browser_window__handle_fetcherror(bw, message, url);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Update URL bar for a given browser window to given URL
|
|
*
|
|
* \param bw Browser window to update URL bar for.
|
|
* \param url URL for content displayed by bw including any fragment.
|
|
*/
|
|
static inline nserror
|
|
browser_window_refresh_url_bar_internal(struct browser_window *bw, nsurl *url)
|
|
{
|
|
assert(bw);
|
|
assert(url);
|
|
|
|
if ((bw->parent != NULL) || (bw->window == NULL)) {
|
|
/* Not root window or no gui window so do not set a URL */
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
return guit->window->set_url(bw->window, url);
|
|
}
|
|
|
|
|
|
/**
|
|
* Browser window content event callback handler.
|
|
*/
|
|
static nserror
|
|
browser_window_callback(hlcache_handle *c, const hlcache_event *event, void *pw)
|
|
{
|
|
struct browser_window *bw = pw;
|
|
nserror res = NSERROR_OK;
|
|
|
|
switch (event->type) {
|
|
case CONTENT_MSG_SSL_CERTS:
|
|
/* SSL certificate information has arrived, store it */
|
|
cert_chain_free(bw->loading_cert_chain);
|
|
cert_chain_dup(event->data.chain, &bw->loading_cert_chain);
|
|
break;
|
|
|
|
case CONTENT_MSG_LOG:
|
|
browser_window_console_log(bw,
|
|
event->data.log.src,
|
|
event->data.log.msg,
|
|
event->data.log.msglen,
|
|
event->data.log.flags);
|
|
break;
|
|
|
|
case CONTENT_MSG_DOWNLOAD:
|
|
assert(bw->loading_content == c);
|
|
|
|
browser_window_convert_to_download(bw, event->data.download);
|
|
|
|
if (bw->current_content != NULL) {
|
|
browser_window_refresh_url_bar(bw);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_LOADING:
|
|
assert(bw->loading_content == c);
|
|
|
|
#ifdef WITH_THEME_INSTALL
|
|
if (content_get_type(c) == CONTENT_THEME) {
|
|
theme_install_start(c);
|
|
bw->loading_content = NULL;
|
|
browser_window_stop_throbber(bw);
|
|
} else
|
|
#endif
|
|
{
|
|
bw->refresh_interval = -1;
|
|
browser_window_set_status(bw,
|
|
content_get_status_message(c));
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_READY:
|
|
assert(bw->loading_content == c);
|
|
|
|
res = browser_window_content_ready(bw);
|
|
break;
|
|
|
|
case CONTENT_MSG_DONE:
|
|
assert(bw->current_content == c);
|
|
|
|
res = browser_window_content_done(bw);
|
|
break;
|
|
|
|
case CONTENT_MSG_ERROR:
|
|
res = browser_window__handle_error(bw, c, event);
|
|
break;
|
|
|
|
case CONTENT_MSG_REDIRECT:
|
|
if (urldb_add_url(event->data.redirect.from)) {
|
|
urldb_update_url_visit_data(event->data.redirect.from);
|
|
}
|
|
browser_window_refresh_url_bar_internal(bw, event->data.redirect.to);
|
|
break;
|
|
|
|
case CONTENT_MSG_STATUS:
|
|
if (event->data.explicit_status_text == NULL) {
|
|
/* Object content's status text updated */
|
|
const char *status = NULL;
|
|
if (bw->loading_content != NULL) {
|
|
/* Give preference to any loading content */
|
|
status = content_get_status_message(
|
|
bw->loading_content);
|
|
}
|
|
|
|
if (status == NULL) {
|
|
status = content_get_status_message(c);
|
|
}
|
|
|
|
if (status != NULL) {
|
|
browser_window_set_status(bw, status);
|
|
}
|
|
} else {
|
|
/* Object content wants to set explicit message */
|
|
browser_window_set_status(bw,
|
|
event->data.explicit_status_text);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_REFORMAT:
|
|
if (c == bw->current_content &&
|
|
content_get_type(c) == CONTENT_HTML) {
|
|
/* reposition frames */
|
|
if (html_get_frameset(c) != NULL)
|
|
browser_window_recalculate_frameset(bw);
|
|
/* reflow iframe positions */
|
|
if (html_get_iframe(c) != NULL)
|
|
browser_window_recalculate_iframes(bw);
|
|
}
|
|
|
|
/* Hide any caret, but don't remove it */
|
|
browser_window_remove_caret(bw, true);
|
|
|
|
if (!(event->data.background)) {
|
|
/* Reformatted content should be redrawn */
|
|
browser_window_update(bw, false);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_REDRAW:
|
|
{
|
|
struct rect rect = {
|
|
.x0 = event->data.redraw.x,
|
|
.y0 = event->data.redraw.y,
|
|
.x1 = event->data.redraw.x + event->data.redraw.width,
|
|
.y1 = event->data.redraw.y + event->data.redraw.height
|
|
};
|
|
|
|
browser_window_update_box(bw, &rect);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_REFRESH:
|
|
bw->refresh_interval = event->data.delay * 100;
|
|
break;
|
|
|
|
case CONTENT_MSG_LINK: /* content has an rfc5988 link element */
|
|
{
|
|
bool match;
|
|
|
|
/* Handle "icon" and "shortcut icon" */
|
|
if ((lwc_string_caseless_isequal(
|
|
event->data.rfc5988_link->rel,
|
|
corestring_lwc_icon,
|
|
&match) == lwc_error_ok && match) ||
|
|
(lwc_string_caseless_isequal(
|
|
event->data.rfc5988_link->rel,
|
|
corestring_lwc_shortcut_icon,
|
|
&match) == lwc_error_ok && match)) {
|
|
/* it's a favicon perhaps start a fetch for it */
|
|
browser_window_update_favicon(c, bw,
|
|
event->data.rfc5988_link);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_GETTHREAD:
|
|
{
|
|
/* only the content object created by the browser
|
|
* window requires a new javascript thread object
|
|
*/
|
|
jsthread *thread;
|
|
assert(bw->loading_content == c);
|
|
|
|
if (js_newthread(bw->jsheap,
|
|
bw,
|
|
hlcache_handle_get_content(c),
|
|
&thread) == NSERROR_OK) {
|
|
/* The content which is requesting the thread
|
|
* is required to keep hold of it and
|
|
* to destroy it when it is finished with it.
|
|
*/
|
|
*(event->data.jsthread) = thread;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_GETDIMS:
|
|
{
|
|
int width;
|
|
int height;
|
|
|
|
browser_window_get_dimensions(bw, &width, &height);
|
|
|
|
*(event->data.getdims.viewport_width) = width / bw->scale;
|
|
*(event->data.getdims.viewport_height) = height / bw->scale;
|
|
break;
|
|
}
|
|
|
|
case CONTENT_MSG_SCROLL:
|
|
{
|
|
struct rect rect = {
|
|
.x0 = event->data.scroll.x0,
|
|
.y0 = event->data.scroll.y0,
|
|
};
|
|
|
|
/* Content wants to be scrolled */
|
|
if (bw->current_content != c) {
|
|
break;
|
|
}
|
|
|
|
if (event->data.scroll.area) {
|
|
rect.x1 = event->data.scroll.x1;
|
|
rect.y1 = event->data.scroll.y1;
|
|
} else {
|
|
rect.x1 = event->data.scroll.x0;
|
|
rect.y1 = event->data.scroll.y0;
|
|
}
|
|
browser_window_set_scroll(bw, &rect);
|
|
|
|
break;
|
|
}
|
|
|
|
case CONTENT_MSG_DRAGSAVE:
|
|
{
|
|
/* Content wants drag save of a content */
|
|
struct browser_window *root = browser_window_get_root(bw);
|
|
hlcache_handle *save = event->data.dragsave.content;
|
|
|
|
if (save == NULL) {
|
|
save = c;
|
|
}
|
|
|
|
switch(event->data.dragsave.type) {
|
|
case CONTENT_SAVE_ORIG:
|
|
guit->window->drag_save_object(root->window,
|
|
save,
|
|
GUI_SAVE_OBJECT_ORIG);
|
|
break;
|
|
|
|
case CONTENT_SAVE_NATIVE:
|
|
guit->window->drag_save_object(root->window,
|
|
save,
|
|
GUI_SAVE_OBJECT_NATIVE);
|
|
break;
|
|
|
|
case CONTENT_SAVE_COMPLETE:
|
|
guit->window->drag_save_object(root->window,
|
|
save,
|
|
GUI_SAVE_COMPLETE);
|
|
break;
|
|
|
|
case CONTENT_SAVE_SOURCE:
|
|
guit->window->drag_save_object(root->window,
|
|
save,
|
|
GUI_SAVE_SOURCE);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_SAVELINK:
|
|
{
|
|
/* Content wants a link to be saved */
|
|
struct browser_window *root = browser_window_get_root(bw);
|
|
guit->window->save_link(root->window,
|
|
event->data.savelink.url,
|
|
event->data.savelink.title);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_POINTER:
|
|
/* Content wants to have specific mouse pointer */
|
|
browser_window_set_pointer(bw, event->data.pointer);
|
|
break;
|
|
|
|
case CONTENT_MSG_DRAG:
|
|
{
|
|
browser_drag_type bdt = DRAGGING_NONE;
|
|
|
|
switch (event->data.drag.type) {
|
|
case CONTENT_DRAG_NONE:
|
|
bdt = DRAGGING_NONE;
|
|
break;
|
|
case CONTENT_DRAG_SCROLL:
|
|
bdt = DRAGGING_CONTENT_SCROLLBAR;
|
|
break;
|
|
case CONTENT_DRAG_SELECTION:
|
|
bdt = DRAGGING_SELECTION;
|
|
break;
|
|
}
|
|
browser_window_set_drag_type(bw, bdt, event->data.drag.rect);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_CARET:
|
|
switch (event->data.caret.type) {
|
|
case CONTENT_CARET_REMOVE:
|
|
browser_window_remove_caret(bw, false);
|
|
break;
|
|
case CONTENT_CARET_HIDE:
|
|
browser_window_remove_caret(bw, true);
|
|
break;
|
|
case CONTENT_CARET_SET_POS:
|
|
browser_window_place_caret(bw,
|
|
event->data.caret.pos.x,
|
|
event->data.caret.pos.y,
|
|
event->data.caret.pos.height,
|
|
event->data.caret.pos.clip);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_SELECTION:
|
|
browser_window_set_selection(bw,
|
|
event->data.selection.selection,
|
|
event->data.selection.read_only);
|
|
break;
|
|
|
|
case CONTENT_MSG_SELECTMENU:
|
|
if (event->data.select_menu.gadget->type == GADGET_SELECT) {
|
|
struct browser_window *root =
|
|
browser_window_get_root(bw);
|
|
guit->window->create_form_select_menu(root->window,
|
|
event->data.select_menu.gadget);
|
|
}
|
|
|
|
break;
|
|
|
|
case CONTENT_MSG_GADGETCLICK:
|
|
if (event->data.gadget_click.gadget->type == GADGET_FILE) {
|
|
struct browser_window *root =
|
|
browser_window_get_root(bw);
|
|
guit->window->file_gadget_open(root->window, c,
|
|
event->data.gadget_click.gadget);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* internal scheduled reformat callback.
|
|
*
|
|
* scheduled reformat callback to allow reformats from unthreaded context.
|
|
*
|
|
* \param vbw The browser window to be reformatted
|
|
*/
|
|
static void scheduled_reformat(void *vbw)
|
|
{
|
|
struct browser_window *bw = vbw;
|
|
int width;
|
|
int height;
|
|
nserror res;
|
|
|
|
res = guit->window->get_dimensions(bw->window, &width, &height);
|
|
if (res == NSERROR_OK) {
|
|
browser_window_reformat(bw, false, width, height);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Release all memory associated with a browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
static void browser_window_destroy_internal(struct browser_window *bw)
|
|
{
|
|
assert(bw);
|
|
|
|
NSLOG(netsurf, INFO, "Destroying window");
|
|
|
|
if (bw->children != NULL || bw->iframes != NULL) {
|
|
browser_window_destroy_children(bw);
|
|
}
|
|
|
|
/* Destroy scrollbars */
|
|
if (bw->scroll_x != NULL) {
|
|
scrollbar_destroy(bw->scroll_x);
|
|
}
|
|
|
|
if (bw->scroll_y != NULL) {
|
|
scrollbar_destroy(bw->scroll_y);
|
|
}
|
|
|
|
/* clear any pending callbacks */
|
|
guit->misc->schedule(-1, browser_window_refresh, bw);
|
|
NSLOG(netsurf, INFO,
|
|
"Clearing reformat schedule for browser window %p", bw);
|
|
guit->misc->schedule(-1, scheduled_reformat, bw);
|
|
|
|
/* If this brower window is not the root window, and has focus, unset
|
|
* the root browser window's focus pointer. */
|
|
if (!bw->window) {
|
|
struct browser_window *top = browser_window_get_root(bw);
|
|
|
|
if (top->focus == bw)
|
|
top->focus = top;
|
|
|
|
if (top->selection.bw == bw) {
|
|
browser_window_set_selection(top, false, false);
|
|
}
|
|
}
|
|
|
|
/* Destruction order is important: we must ensure that the frontend
|
|
* destroys any window(s) associated with this browser window before
|
|
* we attempt any destructive cleanup.
|
|
*/
|
|
|
|
if (bw->window) {
|
|
/* Only the root window has a GUI window */
|
|
guit->window->destroy(bw->window);
|
|
}
|
|
|
|
if (bw->loading_content != NULL) {
|
|
hlcache_handle_abort(bw->loading_content);
|
|
hlcache_handle_release(bw->loading_content);
|
|
bw->loading_content = NULL;
|
|
}
|
|
|
|
if (bw->current_content != NULL) {
|
|
content_close(bw->current_content);
|
|
hlcache_handle_release(bw->current_content);
|
|
bw->current_content = NULL;
|
|
}
|
|
|
|
if (bw->favicon.loading != NULL) {
|
|
hlcache_handle_abort(bw->favicon.loading);
|
|
hlcache_handle_release(bw->favicon.loading);
|
|
bw->favicon.loading = NULL;
|
|
}
|
|
|
|
if (bw->favicon.current != NULL) {
|
|
content_close(bw->favicon.current);
|
|
hlcache_handle_release(bw->favicon.current);
|
|
bw->favicon.current = NULL;
|
|
}
|
|
|
|
if (bw->box != NULL) {
|
|
bw->box->iframe = NULL;
|
|
bw->box = NULL;
|
|
}
|
|
|
|
if (bw->jsheap != NULL) {
|
|
js_destroyheap(bw->jsheap);
|
|
bw->jsheap = NULL;
|
|
}
|
|
|
|
/* These simply free memory, so are safe here */
|
|
|
|
if (bw->frag_id != NULL) {
|
|
lwc_string_unref(bw->frag_id);
|
|
}
|
|
|
|
browser_window_history_destroy(bw);
|
|
|
|
cert_chain_free(bw->current_cert_chain);
|
|
cert_chain_free(bw->loading_cert_chain);
|
|
bw->current_cert_chain = NULL;
|
|
bw->loading_cert_chain = NULL;
|
|
|
|
free(bw->name);
|
|
free(bw->status.text);
|
|
bw->status.text = NULL;
|
|
browser_window__free_fetch_parameters(&bw->current_parameters);
|
|
browser_window__free_fetch_parameters(&bw->loading_parameters);
|
|
NSLOG(netsurf, INFO, "Status text cache match:miss %d:%d",
|
|
bw->status.match, bw->status.miss);
|
|
}
|
|
|
|
|
|
/**
|
|
* scroll to a fragment if present
|
|
*
|
|
* \param bw browser window
|
|
* \return true if the scroll was sucessful
|
|
*/
|
|
static bool frag_scroll(struct browser_window *bw)
|
|
{
|
|
struct rect rect;
|
|
|
|
if (bw->frag_id == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (!html_get_id_offset(bw->current_content,
|
|
bw->frag_id,
|
|
&rect.x0,
|
|
&rect.y0)) {
|
|
return false;
|
|
}
|
|
|
|
rect.x1 = rect.x0;
|
|
rect.y1 = rect.y0;
|
|
if (browser_window_set_scroll(bw, &rect) == NSERROR_OK) {
|
|
if (bw->current_content != NULL &&
|
|
bw->history != NULL &&
|
|
bw->history->current != NULL) {
|
|
browser_window_history_update(bw, bw->current_content);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set browser window scale.
|
|
*
|
|
* \param bw Browser window.
|
|
* \param absolute scale value.
|
|
* \return NSERROR_OK on success else error code
|
|
*/
|
|
static nserror
|
|
browser_window_set_scale_internal(struct browser_window *bw, float scale)
|
|
{
|
|
int i;
|
|
nserror res = NSERROR_OK;
|
|
|
|
/* do not apply tiny changes in scale */
|
|
if (fabs(bw->scale - scale) < 0.0001)
|
|
return res;
|
|
|
|
bw->scale = scale;
|
|
|
|
if (bw->current_content != NULL) {
|
|
if (content_can_reformat(bw->current_content) == false) {
|
|
browser_window_update(bw, false);
|
|
} else {
|
|
res = browser_window_schedule_reformat(bw);
|
|
}
|
|
}
|
|
|
|
/* scale frames */
|
|
for (i = 0; i < (bw->cols * bw->rows); i++) {
|
|
res = browser_window_set_scale_internal(&bw->children[i], scale);
|
|
}
|
|
|
|
/* sale iframes */
|
|
for (i = 0; i < bw->iframe_count; i++) {
|
|
res = browser_window_set_scale_internal(&bw->iframes[i], scale);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find browser window.
|
|
*
|
|
* \param bw Browser window.
|
|
* \param target Name of target.
|
|
* \param depth Depth to scan.
|
|
* \param page The browser window page.
|
|
* \param rdepth The rdepth.
|
|
* \param bw_target the output browser window.
|
|
*/
|
|
static void
|
|
browser_window_find_target_internal(struct browser_window *bw,
|
|
const char *target,
|
|
int depth,
|
|
struct browser_window *page,
|
|
int *rdepth,
|
|
struct browser_window **bw_target)
|
|
{
|
|
int i;
|
|
|
|
if ((bw->name) && (!strcasecmp(bw->name, target))) {
|
|
if ((bw == page) || (depth > *rdepth)) {
|
|
*rdepth = depth;
|
|
*bw_target = bw;
|
|
}
|
|
}
|
|
|
|
if ((!bw->children) && (!bw->iframes))
|
|
return;
|
|
|
|
depth++;
|
|
|
|
if (bw->children != NULL) {
|
|
for (i = 0; i < (bw->cols * bw->rows); i++) {
|
|
if ((bw->children[i].name) &&
|
|
(!strcasecmp(bw->children[i].name,
|
|
target))) {
|
|
if ((page == &bw->children[i]) ||
|
|
(depth > *rdepth)) {
|
|
*rdepth = depth;
|
|
*bw_target = &bw->children[i];
|
|
}
|
|
}
|
|
if (bw->children[i].children)
|
|
browser_window_find_target_internal(
|
|
&bw->children[i],
|
|
target, depth, page,
|
|
rdepth, bw_target);
|
|
}
|
|
}
|
|
|
|
if (bw->iframes != NULL) {
|
|
for (i = 0; i < bw->iframe_count; i++) {
|
|
browser_window_find_target_internal(&bw->iframes[i],
|
|
target,
|
|
depth,
|
|
page,
|
|
rdepth,
|
|
bw_target);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles the end of a drag operation in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*
|
|
* \todo Remove this function, once these things are associated with content,
|
|
* rather than bw.
|
|
*/
|
|
static void
|
|
browser_window_mouse_drag_end(struct browser_window *bw,
|
|
browser_mouse_state mouse,
|
|
int x, int y)
|
|
{
|
|
int scr_x, scr_y;
|
|
|
|
switch (bw->drag.type) {
|
|
case DRAGGING_SELECTION:
|
|
case DRAGGING_OTHER:
|
|
case DRAGGING_CONTENT_SCROLLBAR:
|
|
/* Drag handled by content handler */
|
|
break;
|
|
|
|
case DRAGGING_SCR_X:
|
|
|
|
browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y);
|
|
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
scrollbar_mouse_drag_end(bw->scroll_x, mouse, scr_x, scr_y);
|
|
|
|
bw->drag.type = DRAGGING_NONE;
|
|
break;
|
|
|
|
case DRAGGING_SCR_Y:
|
|
|
|
browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y);
|
|
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
scrollbar_mouse_drag_end(bw->scroll_y, mouse, scr_x, scr_y);
|
|
|
|
bw->drag.type = DRAGGING_NONE;
|
|
break;
|
|
|
|
default:
|
|
browser_window_set_drag_type(bw, DRAGGING_NONE, NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process mouse click event
|
|
*
|
|
* \param bw The browsing context receiving the event
|
|
* \param mouse The mouse event state
|
|
* \param x The scaled x co-ordinate of the event
|
|
* \param y The scaled y co-ordinate of the event
|
|
*/
|
|
static void
|
|
browser_window_mouse_click_internal(struct browser_window *bw,
|
|
browser_mouse_state mouse,
|
|
int x, int y)
|
|
{
|
|
hlcache_handle *c = bw->current_content;
|
|
const char *status = NULL;
|
|
browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT;
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children (frames) */
|
|
struct browser_window *child;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
|
|
child = &bw->children[cur_child];
|
|
|
|
if ((x < child->x) ||
|
|
(y < child->y) ||
|
|
(child->x + child->width < x) ||
|
|
(child->y + child->height < y)) {
|
|
/* Click not in this child */
|
|
continue;
|
|
}
|
|
|
|
/* It's this child that contains the click; pass it
|
|
* on to child. */
|
|
browser_window_mouse_click_internal(
|
|
child,
|
|
mouse,
|
|
x - child->x + scrollbar_get_offset(child->scroll_x),
|
|
y - child->y + scrollbar_get_offset(child->scroll_y));
|
|
|
|
/* Mouse action was for this child, we're done */
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
if (bw->scroll_x != NULL) {
|
|
int scr_x, scr_y;
|
|
browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y);
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (scr_x > 0 && scr_x < get_horz_scrollbar_len(bw) &&
|
|
scr_y > 0 && scr_y < SCROLLBAR_WIDTH) {
|
|
status = scrollbar_mouse_status_to_message(
|
|
scrollbar_mouse_action(
|
|
bw->scroll_x, mouse,
|
|
scr_x, scr_y));
|
|
pointer = BROWSER_POINTER_DEFAULT;
|
|
|
|
if (status != NULL)
|
|
browser_window_set_status(bw, status);
|
|
|
|
browser_window_set_pointer(bw, pointer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (bw->scroll_y != NULL) {
|
|
int scr_x, scr_y;
|
|
browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y);
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (scr_y > 0 && scr_y < get_vert_scrollbar_len(bw) &&
|
|
scr_x > 0 && scr_x < SCROLLBAR_WIDTH) {
|
|
status = scrollbar_mouse_status_to_message(
|
|
scrollbar_mouse_action(
|
|
bw->scroll_y,
|
|
mouse,
|
|
scr_x,
|
|
scr_y));
|
|
pointer = BROWSER_POINTER_DEFAULT;
|
|
|
|
if (status != NULL) {
|
|
browser_window_set_status(bw, status);
|
|
}
|
|
|
|
browser_window_set_pointer(bw, pointer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (content_get_type(c)) {
|
|
case CONTENT_HTML:
|
|
case CONTENT_TEXTPLAIN:
|
|
{
|
|
/* Give bw focus */
|
|
struct browser_window *root_bw = browser_window_get_root(bw);
|
|
if (bw != root_bw->focus) {
|
|
browser_window_remove_caret(bw, false);
|
|
browser_window_set_selection(bw, false, true);
|
|
root_bw->focus = bw;
|
|
}
|
|
|
|
/* Pass mouse action to content */
|
|
content_mouse_action(c, bw, mouse, x, y);
|
|
}
|
|
break;
|
|
default:
|
|
if (mouse & BROWSER_MOUSE_MOD_2) {
|
|
if (mouse & BROWSER_MOUSE_DRAG_2) {
|
|
guit->window->drag_save_object(bw->window, c,
|
|
GUI_SAVE_OBJECT_NATIVE);
|
|
} else if (mouse & BROWSER_MOUSE_DRAG_1) {
|
|
guit->window->drag_save_object(bw->window, c,
|
|
GUI_SAVE_OBJECT_ORIG);
|
|
}
|
|
} else if (mouse & (BROWSER_MOUSE_DRAG_1 |
|
|
BROWSER_MOUSE_DRAG_2)) {
|
|
browser_window_page_drag_start(bw, x, y);
|
|
browser_window_set_pointer(bw, BROWSER_POINTER_MOVE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Process mouse movement event
|
|
*
|
|
* \param bw The browsing context receiving the event
|
|
* \param mouse The mouse event state
|
|
* \param x The scaled x co-ordinate of the event
|
|
* \param y The scaled y co-ordinate of the event
|
|
*/
|
|
static void
|
|
browser_window_mouse_track_internal(struct browser_window *bw,
|
|
browser_mouse_state mouse,
|
|
int x, int y)
|
|
{
|
|
hlcache_handle *c = bw->current_content;
|
|
const char *status = NULL;
|
|
browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT;
|
|
|
|
if (bw->window != NULL && bw->drag.window && bw != bw->drag.window) {
|
|
/* This is the root browser window and there's an active drag
|
|
* in a sub window.
|
|
* Pass the mouse action straight on to that bw. */
|
|
struct browser_window *drag_bw = bw->drag.window;
|
|
int off_x = 0;
|
|
int off_y = 0;
|
|
|
|
browser_window_get_position(drag_bw, true, &off_x, &off_y);
|
|
|
|
if (drag_bw->browser_window_type == BROWSER_WINDOW_FRAME) {
|
|
browser_window_mouse_track_internal(drag_bw,
|
|
mouse,
|
|
x - off_x,
|
|
y - off_y);
|
|
|
|
} else if (drag_bw->browser_window_type == BROWSER_WINDOW_IFRAME) {
|
|
browser_window_mouse_track_internal(drag_bw, mouse,
|
|
x - off_x / bw->scale,
|
|
y - off_y / bw->scale);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children (frames) */
|
|
struct browser_window *child;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
|
|
child = &bw->children[cur_child];
|
|
|
|
if ((x < child->x) ||
|
|
(y < child->y) ||
|
|
(child->x + child->width < x) ||
|
|
(child->y + child->height < y)) {
|
|
/* Click not in this child */
|
|
continue;
|
|
}
|
|
|
|
/* It's this child that contains the mouse; pass
|
|
* mouse action on to child */
|
|
browser_window_mouse_track_internal(
|
|
child,
|
|
mouse,
|
|
x - child->x + scrollbar_get_offset(child->scroll_x),
|
|
y - child->y + scrollbar_get_offset(child->scroll_y));
|
|
|
|
/* Mouse action was for this child, we're done */
|
|
return;
|
|
}
|
|
|
|
/* Odd if we reached here, but nothing else can use the click
|
|
* when there are children. */
|
|
return;
|
|
}
|
|
|
|
if (c == NULL && bw->drag.type != DRAGGING_FRAME) {
|
|
return;
|
|
}
|
|
|
|
if (bw->drag.type != DRAGGING_NONE && !mouse) {
|
|
browser_window_mouse_drag_end(bw, mouse, x, y);
|
|
}
|
|
|
|
/* Browser window's horizontal scrollbar */
|
|
if (bw->scroll_x != NULL && bw->drag.type != DRAGGING_SCR_Y) {
|
|
int scr_x, scr_y;
|
|
browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y);
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if ((bw->drag.type == DRAGGING_SCR_X) ||
|
|
(scr_x > 0 &&
|
|
scr_x < get_horz_scrollbar_len(bw) &&
|
|
scr_y > 0 &&
|
|
scr_y < SCROLLBAR_WIDTH &&
|
|
bw->drag.type == DRAGGING_NONE)) {
|
|
/* Start a scrollbar drag, or continue existing drag */
|
|
status = scrollbar_mouse_status_to_message(
|
|
scrollbar_mouse_action(bw->scroll_x,
|
|
mouse,
|
|
scr_x,
|
|
scr_y));
|
|
pointer = BROWSER_POINTER_DEFAULT;
|
|
|
|
if (status != NULL) {
|
|
browser_window_set_status(bw, status);
|
|
}
|
|
|
|
browser_window_set_pointer(bw, pointer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Browser window's vertical scrollbar */
|
|
if (bw->scroll_y != NULL) {
|
|
int scr_x, scr_y;
|
|
browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y);
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if ((bw->drag.type == DRAGGING_SCR_Y) ||
|
|
(scr_y > 0 &&
|
|
scr_y < get_vert_scrollbar_len(bw) &&
|
|
scr_x > 0 &&
|
|
scr_x < SCROLLBAR_WIDTH &&
|
|
bw->drag.type == DRAGGING_NONE)) {
|
|
/* Start a scrollbar drag, or continue existing drag */
|
|
status = scrollbar_mouse_status_to_message(
|
|
scrollbar_mouse_action(bw->scroll_y,
|
|
mouse,
|
|
scr_x,
|
|
scr_y));
|
|
pointer = BROWSER_POINTER_DEFAULT;
|
|
|
|
if (status != NULL) {
|
|
browser_window_set_status(bw, status);
|
|
}
|
|
|
|
browser_window_set_pointer(bw, pointer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (bw->drag.type == DRAGGING_FRAME) {
|
|
browser_window_resize_frame(bw, bw->x + x, bw->y + y);
|
|
} else if (bw->drag.type == DRAGGING_PAGE_SCROLL) {
|
|
/* mouse movement since drag started */
|
|
struct rect rect;
|
|
|
|
rect.x0 = bw->drag.start_x - x;
|
|
rect.y0 = bw->drag.start_y - y;
|
|
|
|
/* new scroll offsets */
|
|
rect.x0 += bw->drag.start_scroll_x;
|
|
rect.y0 += bw->drag.start_scroll_y;
|
|
|
|
bw->drag.start_scroll_x = rect.x1 = rect.x0;
|
|
bw->drag.start_scroll_y = rect.y1 = rect.y0;
|
|
|
|
browser_window_set_scroll(bw, &rect);
|
|
} else {
|
|
assert(c != NULL);
|
|
content_mouse_track(c, bw, mouse, x, y);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* perform a scroll operation at a given coordinate
|
|
*
|
|
* \param bw The browsing context receiving the event
|
|
* \param x The scaled x co-ordinate of the event
|
|
* \param y The scaled y co-ordinate of the event
|
|
*/
|
|
static bool
|
|
browser_window_scroll_at_point_internal(struct browser_window *bw,
|
|
int x, int y,
|
|
int scrx, int scry)
|
|
{
|
|
bool handled_scroll = false;
|
|
assert(bw != NULL);
|
|
|
|
/* Handle (i)frame scroll offset (core-managed browser windows only) */
|
|
x += scrollbar_get_offset(bw->scroll_x);
|
|
y += scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children, so pass request on to
|
|
* appropriate child */
|
|
struct browser_window *bwc;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
/* Loop through all children of bw */
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
/* Set current child */
|
|
bwc = &bw->children[cur_child];
|
|
|
|
/* Skip this frame if (x, y) coord lies outside */
|
|
if (x < bwc->x || bwc->x + bwc->width < x ||
|
|
y < bwc->y || bwc->y + bwc->height < y)
|
|
continue;
|
|
|
|
/* Pass request into this child */
|
|
return browser_window_scroll_at_point_internal(
|
|
bwc,
|
|
(x - bwc->x),
|
|
(y - bwc->y),
|
|
scrx, scry);
|
|
}
|
|
}
|
|
|
|
/* Try to scroll any current content */
|
|
if (bw->current_content != NULL &&
|
|
content_scroll_at_point(bw->current_content, x, y, scrx, scry) == true) {
|
|
/* Scroll handled by current content */
|
|
return true;
|
|
}
|
|
|
|
/* Try to scroll this window, if scroll not already handled */
|
|
if (handled_scroll == false) {
|
|
if (bw->scroll_y && scrollbar_scroll(bw->scroll_y, scry)) {
|
|
handled_scroll = true;
|
|
}
|
|
|
|
if (bw->scroll_x && scrollbar_scroll(bw->scroll_x, scrx)) {
|
|
handled_scroll = true;
|
|
}
|
|
}
|
|
|
|
return handled_scroll;
|
|
}
|
|
|
|
|
|
/**
|
|
* allows a dragged file to be dropped into a browser window at a position
|
|
*
|
|
* \param bw The browsing context receiving the event
|
|
* \param x The scaled x co-ordinate of the event
|
|
* \param y The scaled y co-ordinate of the event
|
|
* \param file filename to be put in the widget
|
|
*/
|
|
static bool
|
|
browser_window_drop_file_at_point_internal(struct browser_window *bw,
|
|
int x, int y,
|
|
char *file)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
/* Handle (i)frame scroll offset (core-managed browser windows only) */
|
|
x += scrollbar_get_offset(bw->scroll_x);
|
|
y += scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children, so pass request on to
|
|
* appropriate child */
|
|
struct browser_window *bwc;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
/* Loop through all children of bw */
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
/* Set current child */
|
|
bwc = &bw->children[cur_child];
|
|
|
|
/* Skip this frame if (x, y) coord lies outside */
|
|
if (x < bwc->x || bwc->x + bwc->width < x ||
|
|
y < bwc->y || bwc->y + bwc->height < y)
|
|
continue;
|
|
|
|
/* Pass request into this child */
|
|
return browser_window_drop_file_at_point_internal(
|
|
bwc,
|
|
(x - bwc->x),
|
|
(y - bwc->y),
|
|
file);
|
|
}
|
|
}
|
|
|
|
/* Pass file drop on to any content */
|
|
if (bw->current_content != NULL) {
|
|
return content_drop_file_at_point(bw->current_content,
|
|
x, y, file);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if this is an internal navigation URL.
|
|
*
|
|
* This safely checks if the given url is an internal navigation even
|
|
* for urls with no scheme or path.
|
|
*
|
|
* \param url The URL to check
|
|
* \return true if an internal navigation url else false
|
|
*/
|
|
static bool
|
|
is_internal_navigate_url(nsurl *url)
|
|
{
|
|
bool is_internal = false;
|
|
lwc_string *scheme, *path;
|
|
|
|
scheme = nsurl_get_component(url, NSURL_SCHEME);
|
|
if (scheme != NULL) {
|
|
path = nsurl_get_component(url, NSURL_PATH);
|
|
if (path != NULL) {
|
|
if (scheme == corestring_lwc_about) {
|
|
if (path == corestring_lwc_query_auth) {
|
|
is_internal = true;
|
|
} else if (path == corestring_lwc_query_ssl) {
|
|
is_internal = true;
|
|
} else if (path == corestring_lwc_query_timeout) {
|
|
is_internal = true;
|
|
} else if (path == corestring_lwc_query_fetcherror) {
|
|
is_internal = true;
|
|
}
|
|
}
|
|
lwc_string_unref(path);
|
|
}
|
|
lwc_string_unref(scheme);
|
|
}
|
|
return is_internal;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_get_name(struct browser_window *bw, const char **out_name)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
*out_name = bw->name;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_set_name(struct browser_window *bw, const char *name)
|
|
{
|
|
char *nname = NULL;
|
|
|
|
assert(bw != NULL);
|
|
|
|
if (name != NULL) {
|
|
nname = strdup(name);
|
|
if (nname == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
}
|
|
|
|
if (bw->name != NULL) {
|
|
free(bw->name);
|
|
}
|
|
|
|
bw->name = nname;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
bool
|
|
browser_window_redraw(struct browser_window *bw,
|
|
int x, int y,
|
|
const struct rect *clip,
|
|
const struct redraw_context *ctx)
|
|
{
|
|
struct redraw_context new_ctx = *ctx;
|
|
int width = 0;
|
|
int height = 0;
|
|
bool plot_ok = true;
|
|
content_type content_type;
|
|
struct content_redraw_data data;
|
|
struct rect content_clip;
|
|
nserror res;
|
|
|
|
x /= bw->scale;
|
|
y /= bw->scale;
|
|
|
|
if (bw == NULL) {
|
|
NSLOG(netsurf, INFO, "NULL browser window");
|
|
return false;
|
|
}
|
|
|
|
if ((bw->current_content == NULL) &&
|
|
(bw->children == NULL)) {
|
|
/* Browser window has no content, render blank fill */
|
|
ctx->plot->clip(ctx, clip);
|
|
return (ctx->plot->rectangle(ctx, plot_style_fill_white, clip) == NSERROR_OK);
|
|
}
|
|
|
|
/* Browser window has content OR children (frames) */
|
|
if ((bw->window != NULL) &&
|
|
(ctx->plot->option_knockout)) {
|
|
/* Root browser window: start knockout */
|
|
knockout_plot_start(ctx, &new_ctx);
|
|
}
|
|
|
|
new_ctx.plot->clip(ctx, clip);
|
|
|
|
/* Handle redraw of any browser window children */
|
|
if (bw->children) {
|
|
struct browser_window *child;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
if (bw->window != NULL) {
|
|
/* Root browser window; start with blank fill */
|
|
plot_ok &= (new_ctx.plot->rectangle(ctx,
|
|
plot_style_fill_white,
|
|
clip) == NSERROR_OK);
|
|
}
|
|
|
|
/* Loop through all children of bw */
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
/* Set current child */
|
|
child = &bw->children[cur_child];
|
|
|
|
/* Get frame edge box in global coordinates */
|
|
content_clip.x0 = (x + child->x) * child->scale;
|
|
content_clip.y0 = (y + child->y) * child->scale;
|
|
content_clip.x1 = content_clip.x0 +
|
|
child->width * child->scale;
|
|
content_clip.y1 = content_clip.y0 +
|
|
child->height * child->scale;
|
|
|
|
/* Intersect it with clip rectangle */
|
|
if (content_clip.x0 < clip->x0)
|
|
content_clip.x0 = clip->x0;
|
|
if (content_clip.y0 < clip->y0)
|
|
content_clip.y0 = clip->y0;
|
|
if (clip->x1 < content_clip.x1)
|
|
content_clip.x1 = clip->x1;
|
|
if (clip->y1 < content_clip.y1)
|
|
content_clip.y1 = clip->y1;
|
|
|
|
/* Skip this frame if it lies outside clip rectangle */
|
|
if (content_clip.x0 >= content_clip.x1 ||
|
|
content_clip.y0 >= content_clip.y1)
|
|
continue;
|
|
|
|
/* Redraw frame */
|
|
plot_ok &= browser_window_redraw(child,
|
|
x + child->x,
|
|
y + child->y,
|
|
&content_clip,
|
|
&new_ctx);
|
|
}
|
|
|
|
/* Nothing else to redraw for browser windows with children;
|
|
* cleanup and return
|
|
*/
|
|
if (bw->window != NULL && ctx->plot->option_knockout) {
|
|
/* Root browser window: knockout end */
|
|
knockout_plot_end(ctx);
|
|
}
|
|
|
|
return plot_ok;
|
|
}
|
|
|
|
/* Handle browser windows with content to redraw */
|
|
|
|
content_type = content_get_type(bw->current_content);
|
|
if (content_type != CONTENT_HTML && content_type != CONTENT_TEXTPLAIN) {
|
|
/* Set render area according to scale */
|
|
width = content_get_width(bw->current_content) * bw->scale;
|
|
height = content_get_height(bw->current_content) * bw->scale;
|
|
|
|
/* Non-HTML may not fill viewport to extents, so plot white
|
|
* background fill */
|
|
plot_ok &= (new_ctx.plot->rectangle(&new_ctx,
|
|
plot_style_fill_white,
|
|
clip) == NSERROR_OK);
|
|
}
|
|
|
|
/* Set up content redraw data */
|
|
data.x = x - scrollbar_get_offset(bw->scroll_x);
|
|
data.y = y - scrollbar_get_offset(bw->scroll_y);
|
|
data.width = width;
|
|
data.height = height;
|
|
|
|
data.background_colour = 0xFFFFFF;
|
|
data.scale = bw->scale;
|
|
data.repeat_x = false;
|
|
data.repeat_y = false;
|
|
|
|
content_clip = *clip;
|
|
|
|
if (!bw->window) {
|
|
int x0 = x * bw->scale;
|
|
int y0 = y * bw->scale;
|
|
int x1 = (x + bw->width - ((bw->scroll_y != NULL) ?
|
|
SCROLLBAR_WIDTH : 0)) * bw->scale;
|
|
int y1 = (y + bw->height - ((bw->scroll_x != NULL) ?
|
|
SCROLLBAR_WIDTH : 0)) * bw->scale;
|
|
|
|
if (content_clip.x0 < x0) content_clip.x0 = x0;
|
|
if (content_clip.y0 < y0) content_clip.y0 = y0;
|
|
if (x1 < content_clip.x1) content_clip.x1 = x1;
|
|
if (y1 < content_clip.y1) content_clip.y1 = y1;
|
|
}
|
|
|
|
/* Render the content */
|
|
plot_ok &= content_redraw(bw->current_content, &data,
|
|
&content_clip, &new_ctx);
|
|
|
|
/* Back to full clip rect */
|
|
new_ctx.plot->clip(&new_ctx, clip);
|
|
|
|
if (!bw->window) {
|
|
/* Render scrollbars */
|
|
int off_x, off_y;
|
|
if (bw->scroll_x != NULL) {
|
|
browser_window_get_scrollbar_pos(bw, true,
|
|
&off_x, &off_y);
|
|
res = scrollbar_redraw(bw->scroll_x,
|
|
x + off_x, y + off_y, clip,
|
|
bw->scale, &new_ctx);
|
|
if (res != NSERROR_OK) {
|
|
plot_ok = false;
|
|
}
|
|
}
|
|
if (bw->scroll_y != NULL) {
|
|
browser_window_get_scrollbar_pos(bw, false,
|
|
&off_x, &off_y);
|
|
res = scrollbar_redraw(bw->scroll_y,
|
|
x + off_x, y + off_y, clip,
|
|
bw->scale, &new_ctx);
|
|
if (res != NSERROR_OK) {
|
|
plot_ok = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bw->window != NULL && ctx->plot->option_knockout) {
|
|
/* Root browser window: end knockout */
|
|
knockout_plot_end(ctx);
|
|
}
|
|
|
|
return plot_ok;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
bool browser_window_redraw_ready(struct browser_window *bw)
|
|
{
|
|
if (bw == NULL) {
|
|
NSLOG(netsurf, INFO, "NULL browser window");
|
|
return false;
|
|
} else if (bw->current_content != NULL) {
|
|
/* Can't render locked contents */
|
|
return !content_is_locked(bw->current_content);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in browser_private.h */
|
|
void browser_window_update_extent(struct browser_window *bw)
|
|
{
|
|
if (bw->window != NULL) {
|
|
/* Front end window */
|
|
guit->window->event(bw->window, GW_EVENT_UPDATE_EXTENT);
|
|
} else {
|
|
/* Core-managed browser window */
|
|
browser_window_handle_scrollbars(bw);
|
|
}
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
void
|
|
browser_window_get_position(struct browser_window *bw,
|
|
bool root,
|
|
int *pos_x,
|
|
int *pos_y)
|
|
{
|
|
*pos_x = 0;
|
|
*pos_y = 0;
|
|
|
|
assert(bw != NULL);
|
|
|
|
while (bw) {
|
|
switch (bw->browser_window_type) {
|
|
|
|
case BROWSER_WINDOW_FRAMESET:
|
|
*pos_x += bw->x * bw->scale;
|
|
*pos_y += bw->y * bw->scale;
|
|
break;
|
|
|
|
case BROWSER_WINDOW_NORMAL:
|
|
/* There is no offset to the root browser window */
|
|
break;
|
|
|
|
case BROWSER_WINDOW_FRAME:
|
|
/* Iframe and Frame handling is identical;
|
|
* fall though */
|
|
case BROWSER_WINDOW_IFRAME:
|
|
*pos_x += (bw->x - scrollbar_get_offset(bw->scroll_x)) *
|
|
bw->scale;
|
|
*pos_y += (bw->y - scrollbar_get_offset(bw->scroll_y)) *
|
|
bw->scale;
|
|
break;
|
|
}
|
|
|
|
bw = bw->parent;
|
|
|
|
if (!root) {
|
|
/* return if we just wanted the position in the parent
|
|
* browser window. */
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
void browser_window_set_position(struct browser_window *bw, int x, int y)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window */
|
|
bw->x = x;
|
|
bw->y = y;
|
|
} else {
|
|
NSLOG(netsurf, INFO,
|
|
"Asked to set position of front end window.");
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
void
|
|
browser_window_set_drag_type(struct browser_window *bw,
|
|
browser_drag_type type,
|
|
const struct rect *rect)
|
|
{
|
|
struct browser_window *top_bw = browser_window_get_root(bw);
|
|
gui_drag_type gtype;
|
|
|
|
bw->drag.type = type;
|
|
|
|
if (type == DRAGGING_NONE) {
|
|
top_bw->drag.window = NULL;
|
|
} else {
|
|
top_bw->drag.window = bw;
|
|
|
|
switch (type) {
|
|
case DRAGGING_SELECTION:
|
|
/** \todo tell front end */
|
|
return;
|
|
case DRAGGING_SCR_X:
|
|
case DRAGGING_SCR_Y:
|
|
case DRAGGING_CONTENT_SCROLLBAR:
|
|
gtype = GDRAGGING_SCROLLBAR;
|
|
break;
|
|
default:
|
|
gtype = GDRAGGING_OTHER;
|
|
break;
|
|
}
|
|
|
|
guit->window->drag_start(top_bw->window, gtype, rect);
|
|
}
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
browser_drag_type browser_window_get_drag_type(struct browser_window *bw)
|
|
{
|
|
return bw->drag.type;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
struct browser_window * browser_window_get_root(struct browser_window *bw)
|
|
{
|
|
while (bw && bw->parent) {
|
|
bw = bw->parent;
|
|
}
|
|
return bw;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
browser_editor_flags browser_window_get_editor_flags(struct browser_window *bw)
|
|
{
|
|
browser_editor_flags ed_flags = BW_EDITOR_NONE;
|
|
assert(bw->window);
|
|
assert(bw->parent == NULL);
|
|
|
|
if (bw->selection.bw != NULL) {
|
|
ed_flags |= BW_EDITOR_CAN_COPY;
|
|
|
|
if (!bw->selection.read_only)
|
|
ed_flags |= BW_EDITOR_CAN_CUT;
|
|
}
|
|
|
|
if (bw->can_edit)
|
|
ed_flags |= BW_EDITOR_CAN_PASTE;
|
|
|
|
return ed_flags;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
bool browser_window_can_select(struct browser_window *bw)
|
|
{
|
|
if (bw == NULL || bw->current_content == NULL)
|
|
return false;
|
|
|
|
/* TODO: We shouldn't have to know about specific content types
|
|
* here. There should be a content_is_selectable() call. */
|
|
if (content_get_type(bw->current_content) != CONTENT_HTML &&
|
|
content_get_type(bw->current_content) !=
|
|
CONTENT_TEXTPLAIN)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
char * browser_window_get_selection(struct browser_window *bw)
|
|
{
|
|
assert(bw->window);
|
|
assert(bw->parent == NULL);
|
|
|
|
if (bw->selection.bw == NULL ||
|
|
bw->selection.bw->current_content == NULL)
|
|
return NULL;
|
|
|
|
return content_get_selection(bw->selection.bw->current_content);
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
bool browser_window_can_search(struct browser_window *bw)
|
|
{
|
|
if (bw == NULL || bw->current_content == NULL)
|
|
return false;
|
|
|
|
/** \todo We shouldn't have to know about specific content
|
|
* types here. There should be a content_is_searchable() call.
|
|
*/
|
|
if ((content_get_type(bw->current_content) != CONTENT_HTML) &&
|
|
(content_get_type(bw->current_content) != CONTENT_TEXTPLAIN)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
bool browser_window_is_frameset(struct browser_window *bw)
|
|
{
|
|
return (bw->children != NULL);
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_get_scrollbar_type(struct browser_window *bw,
|
|
browser_scrolling *h,
|
|
browser_scrolling *v)
|
|
{
|
|
*h = bw->scrolling;
|
|
*v = bw->scrolling;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_get_features(struct browser_window *bw,
|
|
int x, int y,
|
|
struct browser_window_features *data)
|
|
{
|
|
/* clear the features structure to empty values */
|
|
data->link = NULL;
|
|
data->object = NULL;
|
|
data->main = NULL;
|
|
data->form_features = CTX_FORM_NONE;
|
|
|
|
return browser_window__get_contextual_content(bw,
|
|
x / bw->scale,
|
|
y / bw->scale,
|
|
data);
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
bool
|
|
browser_window_scroll_at_point(struct browser_window *bw,
|
|
int x, int y,
|
|
int scrx, int scry)
|
|
{
|
|
return browser_window_scroll_at_point_internal(bw,
|
|
x / bw->scale,
|
|
y / bw->scale,
|
|
scrx,
|
|
scry);
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
bool
|
|
browser_window_drop_file_at_point(struct browser_window *bw,
|
|
int x, int y,
|
|
char *file)
|
|
{
|
|
return browser_window_drop_file_at_point_internal(bw,
|
|
x / bw->scale,
|
|
y / bw->scale,
|
|
file);
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
void
|
|
browser_window_set_gadget_filename(struct browser_window *bw,
|
|
struct form_control *gadget,
|
|
const char *fn)
|
|
{
|
|
html_set_file_gadget_filename(bw->current_content, gadget, fn);
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_debug_dump(struct browser_window *bw,
|
|
FILE *f,
|
|
enum content_debug op)
|
|
{
|
|
if (bw->current_content != NULL) {
|
|
return content_debug_dump(bw->current_content, f, op);
|
|
}
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
nserror browser_window_debug(struct browser_window *bw, enum content_debug op)
|
|
{
|
|
if (bw->current_content != NULL) {
|
|
return content_debug(bw->current_content, op);
|
|
}
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_create(enum browser_window_create_flags flags,
|
|
nsurl *url,
|
|
nsurl *referrer,
|
|
struct browser_window *existing,
|
|
struct browser_window **bw)
|
|
{
|
|
gui_window_create_flags gw_flags = GW_CREATE_NONE;
|
|
struct browser_window *ret;
|
|
nserror err;
|
|
|
|
/* Check parameters */
|
|
if (flags & BW_CREATE_CLONE) {
|
|
if (existing == NULL) {
|
|
assert(0 && "Failed: No existing window provided.");
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
}
|
|
|
|
if (!(flags & BW_CREATE_HISTORY)) {
|
|
if (!(flags & BW_CREATE_CLONE) || existing == NULL) {
|
|
assert(0 && "Failed: Must have existing for history.");
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
}
|
|
|
|
ret = calloc(1, sizeof(struct browser_window));
|
|
if (ret == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
/* Initialise common parts */
|
|
err = browser_window_initialise_common(flags, ret, existing);
|
|
if (err != NSERROR_OK) {
|
|
browser_window_destroy(ret);
|
|
return err;
|
|
}
|
|
|
|
/* window characteristics */
|
|
ret->browser_window_type = BROWSER_WINDOW_NORMAL;
|
|
ret->scrolling = BW_SCROLLING_YES;
|
|
ret->border = true;
|
|
ret->no_resize = true;
|
|
ret->focus = ret;
|
|
|
|
/* initialise last action with creation time */
|
|
nsu_getmonotonic_ms(&ret->last_action);
|
|
|
|
/* The existing gui_window is on the top-level existing
|
|
* browser_window. */
|
|
existing = browser_window_get_root(existing);
|
|
|
|
/* Set up gui_window creation flags */
|
|
if (flags & BW_CREATE_TAB)
|
|
gw_flags |= GW_CREATE_TAB;
|
|
if (flags & BW_CREATE_CLONE)
|
|
gw_flags |= GW_CREATE_CLONE;
|
|
if (flags & BW_CREATE_FOREGROUND)
|
|
gw_flags |= GW_CREATE_FOREGROUND;
|
|
if (flags & BW_CREATE_FOCUS_LOCATION)
|
|
gw_flags |= GW_CREATE_FOCUS_LOCATION;
|
|
|
|
ret->window = guit->window->create(ret,
|
|
(existing != NULL) ? existing->window : NULL,
|
|
gw_flags);
|
|
|
|
if (ret->window == NULL) {
|
|
browser_window_destroy(ret);
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
|
|
if (url != NULL) {
|
|
enum browser_window_nav_flags nav_flags;
|
|
nav_flags = BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE;
|
|
if (flags & BW_CREATE_UNVERIFIABLE) {
|
|
nav_flags |= BW_NAVIGATE_UNVERIFIABLE;
|
|
}
|
|
if (flags & BW_CREATE_HISTORY) {
|
|
nav_flags |= BW_NAVIGATE_HISTORY;
|
|
}
|
|
browser_window_navigate(ret,
|
|
url,
|
|
referrer,
|
|
nav_flags,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
if (bw != NULL) {
|
|
*bw = ret;
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported internal interface, documented in desktop/browser_private.h */
|
|
nserror
|
|
browser_window_initialise_common(enum browser_window_create_flags flags,
|
|
struct browser_window *bw,
|
|
const struct browser_window *existing)
|
|
{
|
|
nserror err;
|
|
assert(bw);
|
|
|
|
/* new javascript context for each window/(i)frame */
|
|
err = js_newheap(nsoption_int(script_timeout), &bw->jsheap);
|
|
if (err != NSERROR_OK)
|
|
return err;
|
|
|
|
if (flags & BW_CREATE_CLONE) {
|
|
assert(existing != NULL);
|
|
|
|
/* clone history */
|
|
err = browser_window_history_clone(existing, bw);
|
|
|
|
/* copy the scale */
|
|
bw->scale = existing->scale;
|
|
} else {
|
|
/* create history */
|
|
err = browser_window_history_create(bw);
|
|
|
|
/* default scale */
|
|
bw->scale = (float) nsoption_int(scale) / 100.0;
|
|
}
|
|
|
|
if (err != NSERROR_OK)
|
|
return err;
|
|
|
|
/* window characteristics */
|
|
bw->refresh_interval = -1;
|
|
|
|
bw->drag.type = DRAGGING_NONE;
|
|
|
|
bw->scroll_x = NULL;
|
|
bw->scroll_y = NULL;
|
|
|
|
bw->focus = NULL;
|
|
|
|
/* initialise status text cache */
|
|
bw->status.text = NULL;
|
|
bw->status.text_len = 0;
|
|
bw->status.match = 0;
|
|
bw->status.miss = 0;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
void browser_window_destroy(struct browser_window *bw)
|
|
{
|
|
/* can't destoy child windows on their own */
|
|
assert(!bw->parent);
|
|
|
|
/* destroy */
|
|
browser_window_destroy_internal(bw);
|
|
free(bw);
|
|
}
|
|
|
|
|
|
/* exported interface, documented in netsurf/browser_window.h */
|
|
nserror browser_window_refresh_url_bar(struct browser_window *bw)
|
|
{
|
|
nserror ret;
|
|
nsurl *display_url, *url;
|
|
|
|
assert(bw);
|
|
|
|
if (bw->parent != NULL) {
|
|
/* Not root window; don't set a URL in GUI URL bar */
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
if (bw->current_content == NULL) {
|
|
/* no content so return about:blank */
|
|
ret = browser_window_refresh_url_bar_internal(bw,
|
|
corestring_nsurl_about_blank);
|
|
} else if (bw->throbbing && bw->loading_parameters.url != NULL) {
|
|
/* Throbbing and we have loading parameters, use those */
|
|
url = bw->loading_parameters.url;
|
|
ret = browser_window_refresh_url_bar_internal(bw, url);
|
|
} else if (bw->frag_id == NULL) {
|
|
if (bw->internal_nav) {
|
|
url = bw->loading_parameters.url;
|
|
} else {
|
|
url = hlcache_handle_get_url(bw->current_content);
|
|
}
|
|
ret = browser_window_refresh_url_bar_internal(bw, url);
|
|
} else {
|
|
/* Combine URL and Fragment */
|
|
if (bw->internal_nav) {
|
|
url = bw->loading_parameters.url;
|
|
} else {
|
|
url = hlcache_handle_get_url(bw->current_content);
|
|
}
|
|
ret = nsurl_refragment(
|
|
url,
|
|
bw->frag_id, &display_url);
|
|
if (ret == NSERROR_OK) {
|
|
ret = browser_window_refresh_url_bar_internal(bw,
|
|
display_url);
|
|
nsurl_unref(display_url);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_navigate(struct browser_window *bw,
|
|
nsurl *url,
|
|
nsurl *referrer,
|
|
enum browser_window_nav_flags flags,
|
|
char *post_urlenc,
|
|
struct fetch_multipart_data *post_multipart,
|
|
hlcache_handle *parent)
|
|
{
|
|
int depth = 0;
|
|
struct browser_window *cur;
|
|
uint32_t fetch_flags = 0;
|
|
bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL);
|
|
llcache_post_data post;
|
|
hlcache_child_context child;
|
|
nserror error;
|
|
bool is_internal = false;
|
|
struct browser_fetch_parameters params, *pass_params = NULL;
|
|
|
|
assert(bw);
|
|
assert(url);
|
|
|
|
NSLOG(netsurf, INFO, "bw %p, url %s", bw, nsurl_access(url));
|
|
|
|
/*
|
|
* determine if navigation is internal url, if so, we do not
|
|
* do certain things during the load.
|
|
*/
|
|
is_internal = is_internal_navigate_url(url);
|
|
|
|
if (is_internal &&
|
|
!(flags & BW_NAVIGATE_INTERNAL)) {
|
|
/* Internal navigation detected, but flag not set, only allow
|
|
* this is there's a fetch multipart
|
|
*/
|
|
if (post_multipart == NULL) {
|
|
return NSERROR_NEED_DATA;
|
|
}
|
|
/* It *is* internal, set it as such */
|
|
flags |= BW_NAVIGATE_INTERNAL | BW_NAVIGATE_HISTORY;
|
|
/* If we were previously internal, don't update again */
|
|
if (bw->internal_nav) {
|
|
flags |= BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE;
|
|
}
|
|
}
|
|
|
|
/* If we're navigating and we have a history entry and a content
|
|
* then update the history entry before we navigate to save our
|
|
* current state. However since history navigation pre-moves
|
|
* the history state, we ensure that we only do this if we've not
|
|
* been suppressed. In the suppressed case, the history code
|
|
* updates the history itself before navigating.
|
|
*/
|
|
if (bw->current_content != NULL &&
|
|
bw->history != NULL &&
|
|
bw->history->current != NULL &&
|
|
!is_internal &&
|
|
!(flags & BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE)) {
|
|
browser_window_history_update(bw, bw->current_content);
|
|
}
|
|
|
|
/* don't allow massively nested framesets */
|
|
for (cur = bw; cur->parent; cur = cur->parent) {
|
|
depth++;
|
|
}
|
|
if (depth > FRAME_DEPTH) {
|
|
NSLOG(netsurf, INFO, "frame depth too high.");
|
|
return NSERROR_FRAME_DEPTH;
|
|
}
|
|
|
|
/* Set up retrieval parameters */
|
|
if (!(flags & BW_NAVIGATE_UNVERIFIABLE)) {
|
|
fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE;
|
|
}
|
|
|
|
if (post_multipart != NULL) {
|
|
post.type = LLCACHE_POST_MULTIPART;
|
|
post.data.multipart = post_multipart;
|
|
} else if (post_urlenc != NULL) {
|
|
post.type = LLCACHE_POST_URL_ENCODED;
|
|
post.data.urlenc = post_urlenc;
|
|
}
|
|
|
|
child.charset = content_get_encoding(parent, CONTENT_ENCODING_NORMAL);
|
|
if ((parent != NULL) && (content_get_type(parent) == CONTENT_HTML)) {
|
|
child.quirks = content_get_quirks(parent);
|
|
} else {
|
|
child.quirks = false;
|
|
}
|
|
|
|
url = nsurl_ref(url);
|
|
|
|
if (referrer != NULL) {
|
|
referrer = nsurl_ref(referrer);
|
|
}
|
|
|
|
/* Get download out of the way */
|
|
if ((flags & BW_NAVIGATE_DOWNLOAD) != 0) {
|
|
error = browser_window_download(bw,
|
|
url,
|
|
referrer,
|
|
fetch_flags,
|
|
fetch_is_post,
|
|
&post);
|
|
nsurl_unref(url);
|
|
if (referrer != NULL) {
|
|
nsurl_unref(referrer);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
if (bw->frag_id != NULL) {
|
|
lwc_string_unref(bw->frag_id);
|
|
}
|
|
bw->frag_id = NULL;
|
|
|
|
if (nsurl_has_component(url, NSURL_FRAGMENT)) {
|
|
bool same_url = false;
|
|
|
|
bw->frag_id = nsurl_get_component(url, NSURL_FRAGMENT);
|
|
|
|
/* Compare new URL with existing one (ignoring fragments) */
|
|
if ((bw->current_content != NULL) &&
|
|
(hlcache_handle_get_url(bw->current_content) != NULL)) {
|
|
same_url = nsurl_compare(
|
|
url,
|
|
hlcache_handle_get_url(bw->current_content),
|
|
NSURL_COMPLETE);
|
|
}
|
|
|
|
/* if we're simply moving to another ID on the same page,
|
|
* don't bother to fetch, just update the window.
|
|
*/
|
|
if ((same_url) &&
|
|
(fetch_is_post == false) &&
|
|
(nsurl_has_component(url, NSURL_QUERY) == false)) {
|
|
nsurl_unref(url);
|
|
|
|
if (referrer != NULL) {
|
|
nsurl_unref(referrer);
|
|
}
|
|
|
|
if ((flags & BW_NAVIGATE_HISTORY) != 0) {
|
|
browser_window_history_add(bw,
|
|
bw->current_content,
|
|
bw->frag_id);
|
|
}
|
|
|
|
browser_window_update(bw, false);
|
|
|
|
if (bw->current_content != NULL) {
|
|
browser_window_refresh_url_bar(bw);
|
|
}
|
|
return NSERROR_OK;
|
|
}
|
|
}
|
|
|
|
browser_window_stop(bw);
|
|
browser_window_remove_caret(bw, false);
|
|
browser_window_destroy_children(bw);
|
|
|
|
/* Set up the fetch parameters */
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
params.url = nsurl_ref(url);
|
|
|
|
if (referrer != NULL) {
|
|
params.referrer = nsurl_ref(referrer);
|
|
}
|
|
|
|
params.flags = flags;
|
|
|
|
if (post_urlenc != NULL) {
|
|
params.post_urlenc = strdup(post_urlenc);
|
|
}
|
|
|
|
if (post_multipart != NULL) {
|
|
params.post_multipart = fetch_multipart_data_clone(post_multipart);
|
|
}
|
|
|
|
if (parent != NULL) {
|
|
params.parent_charset = strdup(child.charset);
|
|
params.parent_quirks = child.quirks;
|
|
}
|
|
|
|
bw->internal_nav = is_internal;
|
|
|
|
if (is_internal) {
|
|
pass_params = ¶ms;
|
|
} else {
|
|
/* At this point, we're navigating, so store the fetch parameters */
|
|
browser_window__free_fetch_parameters(&bw->loading_parameters);
|
|
memcpy(&bw->loading_parameters, ¶ms, sizeof(params));
|
|
memset(¶ms, 0, sizeof(params));
|
|
pass_params = &bw->loading_parameters;
|
|
}
|
|
|
|
error = browser_window__navigate_internal(bw, pass_params);
|
|
|
|
nsurl_unref(url);
|
|
|
|
if (referrer != NULL) {
|
|
nsurl_unref(referrer);
|
|
}
|
|
|
|
if (is_internal) {
|
|
browser_window__free_fetch_parameters(¶ms);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
/**
|
|
* Internal navigation handler for normal fetches
|
|
*/
|
|
static nserror
|
|
navigate_internal_real(struct browser_window *bw,
|
|
struct browser_fetch_parameters *params)
|
|
{
|
|
uint32_t fetch_flags = 0;
|
|
bool fetch_is_post;
|
|
llcache_post_data post;
|
|
hlcache_child_context child;
|
|
nserror res;
|
|
hlcache_handle *c;
|
|
|
|
NSLOG(netsurf, INFO, "Loading '%s'", nsurl_access(params->url));
|
|
|
|
fetch_is_post = (params->post_urlenc != NULL || params->post_multipart != NULL);
|
|
|
|
/* Clear SSL info for load */
|
|
cert_chain_free(bw->loading_cert_chain);
|
|
bw->loading_cert_chain = NULL;
|
|
|
|
/* Set up retrieval parameters */
|
|
if (!(params->flags & BW_NAVIGATE_UNVERIFIABLE)) {
|
|
fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE;
|
|
}
|
|
|
|
if (params->post_multipart != NULL) {
|
|
post.type = LLCACHE_POST_MULTIPART;
|
|
post.data.multipart = params->post_multipart;
|
|
} else if (params->post_urlenc != NULL) {
|
|
post.type = LLCACHE_POST_URL_ENCODED;
|
|
post.data.urlenc = params->post_urlenc;
|
|
}
|
|
|
|
if (params->parent_charset != NULL) {
|
|
child.charset = params->parent_charset;
|
|
child.quirks = params->parent_quirks;
|
|
}
|
|
|
|
browser_window_set_status(bw, messages_get("Loading"));
|
|
bw->history_add = (params->flags & BW_NAVIGATE_HISTORY);
|
|
|
|
/* Verifiable fetches may trigger a download */
|
|
if (!(params->flags & BW_NAVIGATE_UNVERIFIABLE)) {
|
|
fetch_flags |= HLCACHE_RETRIEVE_MAY_DOWNLOAD;
|
|
}
|
|
|
|
res = hlcache_handle_retrieve(params->url,
|
|
fetch_flags | HLCACHE_RETRIEVE_SNIFF_TYPE,
|
|
params->referrer,
|
|
fetch_is_post ? &post : NULL,
|
|
browser_window_callback,
|
|
bw,
|
|
params->parent_charset != NULL ? &child : NULL,
|
|
CONTENT_ANY,
|
|
&c);
|
|
|
|
switch (res) {
|
|
case NSERROR_OK:
|
|
bw->loading_content = c;
|
|
browser_window_start_throbber(bw);
|
|
if (bw->window != NULL) {
|
|
guit->window->set_icon(bw->window, NULL);
|
|
}
|
|
if (bw->internal_nav == false) {
|
|
res = browser_window_refresh_url_bar_internal(bw,
|
|
params->url);
|
|
}
|
|
break;
|
|
|
|
case NSERROR_NO_FETCH_HANDLER: /* no handler for this type */
|
|
/** \todo does this always try and download even
|
|
* unverifiable content?
|
|
*/
|
|
res = guit->misc->launch_url(params->url);
|
|
break;
|
|
|
|
default: /* report error to user */
|
|
browser_window_set_status(bw, messages_get_errorcode(res));
|
|
break;
|
|
|
|
}
|
|
|
|
/* Record time */
|
|
nsu_getmonotonic_ms(&bw->last_action);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Internal navigation handler for the authentication query handler
|
|
*
|
|
* If the parameters indicate we're processing a *response* from the handler
|
|
* then we deal with that, otherwise we pass it on to the about: handler
|
|
*/
|
|
static nserror
|
|
navigate_internal_query_auth(struct browser_window *bw,
|
|
struct browser_fetch_parameters *params)
|
|
{
|
|
char *userpass = NULL;
|
|
const char *username, *password, *realm, *siteurl;
|
|
nsurl *sitensurl;
|
|
nserror res;
|
|
bool is_login = false, is_cancel = false;
|
|
|
|
assert(params->post_multipart != NULL);
|
|
|
|
is_login = fetch_multipart_data_find(params->post_multipart, "login") != NULL;
|
|
is_cancel = fetch_multipart_data_find(params->post_multipart, "cancel") != NULL;
|
|
|
|
if (!(is_login || is_cancel)) {
|
|
/* This is a request, so pass it on */
|
|
return navigate_internal_real(bw, params);
|
|
}
|
|
|
|
if (is_cancel) {
|
|
/* We're processing a cancel, do a rough-and-ready nav to
|
|
* about:blank
|
|
*/
|
|
browser_window__free_fetch_parameters(&bw->loading_parameters);
|
|
bw->loading_parameters.url = nsurl_ref(corestring_nsurl_about_blank);
|
|
bw->loading_parameters.flags = BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL;
|
|
bw->internal_nav = true;
|
|
return browser_window__navigate_internal(bw, &bw->loading_parameters);
|
|
}
|
|
|
|
/* We're processing a "login" attempt from the form */
|
|
|
|
/* Retrieve the data */
|
|
username = fetch_multipart_data_find(params->post_multipart, "username");
|
|
password = fetch_multipart_data_find(params->post_multipart, "password");
|
|
realm = fetch_multipart_data_find(params->post_multipart, "realm");
|
|
siteurl = fetch_multipart_data_find(params->post_multipart, "siteurl");
|
|
|
|
if (username == NULL || password == NULL ||
|
|
realm == NULL || siteurl == NULL) {
|
|
/* Bad inputs, simply fail */
|
|
return NSERROR_INVALID;
|
|
}
|
|
|
|
/* Parse the URL */
|
|
res = nsurl_create(siteurl, &sitensurl);
|
|
if (res != NSERROR_OK) {
|
|
return res;
|
|
}
|
|
|
|
/* Construct the username/password */
|
|
res = browser_window__build_userpass(username, password, &userpass);
|
|
if (res != NSERROR_OK) {
|
|
nsurl_unref(sitensurl);
|
|
return res;
|
|
}
|
|
|
|
/* And let urldb know */
|
|
urldb_set_auth_details(sitensurl, realm, userpass);
|
|
|
|
/* Clean up */
|
|
free(userpass);
|
|
nsurl_unref(sitensurl);
|
|
|
|
/* Finally navigate to the original loading parameters */
|
|
bw->internal_nav = false;
|
|
return navigate_internal_real(bw, &bw->loading_parameters);
|
|
}
|
|
|
|
|
|
/**
|
|
* Internal navigation handler for the SSL/privacy query page.
|
|
*
|
|
* If the parameters indicate we're processing a *response* from the handler
|
|
* then we deal with that, otherwise we pass it on to the about: handler
|
|
*/
|
|
static nserror
|
|
navigate_internal_query_ssl(struct browser_window *bw,
|
|
struct browser_fetch_parameters *params)
|
|
{
|
|
bool is_proceed = false, is_back = false;
|
|
const char *siteurl = NULL;
|
|
nsurl *siteurl_ns;
|
|
|
|
assert(params->post_multipart != NULL);
|
|
|
|
is_proceed = fetch_multipart_data_find(params->post_multipart, "proceed") != NULL;
|
|
is_back = fetch_multipart_data_find(params->post_multipart, "back") != NULL;
|
|
siteurl = fetch_multipart_data_find(params->post_multipart, "siteurl");
|
|
|
|
if (!(is_proceed || is_back) || siteurl == NULL) {
|
|
/* This is a request, so pass it on */
|
|
return navigate_internal_real(bw, params);
|
|
}
|
|
|
|
if (nsurl_create(siteurl, &siteurl_ns) != NSERROR_OK) {
|
|
NSLOG(netsurf, ERROR, "Unable to reset ssl loading parameters");
|
|
} else {
|
|
/* In order that we may proceed, replace the loading parameters */
|
|
nsurl_unref(bw->loading_parameters.url);
|
|
bw->loading_parameters.url = siteurl_ns;
|
|
}
|
|
|
|
return browser_window__handle_ssl_query_response(is_proceed, bw);
|
|
}
|
|
|
|
|
|
/**
|
|
* Internal navigation handler for the timeout query page.
|
|
*
|
|
* If the parameters indicate we're processing a *response* from the handler
|
|
* then we deal with that, otherwise we pass it on to the about: handler
|
|
*/
|
|
static nserror
|
|
navigate_internal_query_timeout(struct browser_window *bw,
|
|
struct browser_fetch_parameters *params)
|
|
{
|
|
bool is_retry = false, is_back = false;
|
|
|
|
NSLOG(netsurf, INFO, "bw:%p params:%p", bw, params);
|
|
|
|
assert(params->post_multipart != NULL);
|
|
|
|
is_retry = fetch_multipart_data_find(params->post_multipart, "retry") != NULL;
|
|
is_back = fetch_multipart_data_find(params->post_multipart, "back") != NULL;
|
|
|
|
if (is_back) {
|
|
/* do a rough-and-ready nav to the old 'current'
|
|
* parameters, with any post data stripped away
|
|
*/
|
|
return browser_window__reload_current_parameters(bw);
|
|
}
|
|
|
|
if (is_retry) {
|
|
/* Finally navigate to the original loading parameters */
|
|
bw->internal_nav = false;
|
|
return navigate_internal_real(bw, &bw->loading_parameters);
|
|
}
|
|
|
|
return navigate_internal_real(bw, params);
|
|
}
|
|
|
|
|
|
/**
|
|
* Internal navigation handler for the fetch error query page.
|
|
*
|
|
* If the parameters indicate we're processing a *response* from the handler
|
|
* then we deal with that, otherwise we pass it on to the about: handler
|
|
*/
|
|
static nserror
|
|
navigate_internal_query_fetcherror(struct browser_window *bw,
|
|
struct browser_fetch_parameters *params)
|
|
{
|
|
bool is_retry = false, is_back = false;
|
|
|
|
NSLOG(netsurf, INFO, "bw:%p params:%p", bw, params);
|
|
|
|
assert(params->post_multipart != NULL);
|
|
|
|
is_retry = fetch_multipart_data_find(params->post_multipart, "retry") != NULL;
|
|
is_back = fetch_multipart_data_find(params->post_multipart, "back") != NULL;
|
|
|
|
if (is_back) {
|
|
/* do a rough-and-ready nav to the old 'current'
|
|
* parameters, with any post data stripped away
|
|
*/
|
|
return browser_window__reload_current_parameters(bw);
|
|
}
|
|
|
|
if (is_retry) {
|
|
/* Finally navigate to the original loading parameters */
|
|
bw->internal_nav = false;
|
|
return navigate_internal_real(bw, &bw->loading_parameters);
|
|
}
|
|
|
|
return navigate_internal_real(bw, params);
|
|
}
|
|
|
|
|
|
/**
|
|
* dispatch to internal query handlers or normal navigation
|
|
*
|
|
* Here we determine if we're navigating to an internal query URI and
|
|
* if so, what we need to do about it.
|
|
*
|
|
* \note these check must match those in is_internal_navigate_url()
|
|
*
|
|
* If we're not, then we just move on to the real navigate.
|
|
*/
|
|
nserror
|
|
browser_window__navigate_internal(struct browser_window *bw,
|
|
struct browser_fetch_parameters *params)
|
|
{
|
|
lwc_string *scheme, *path;
|
|
|
|
/* All our special URIs are in the about: scheme */
|
|
scheme = nsurl_get_component(params->url, NSURL_SCHEME);
|
|
if (scheme != corestring_lwc_about) {
|
|
lwc_string_unref(scheme);
|
|
goto normal_fetch;
|
|
}
|
|
lwc_string_unref(scheme);
|
|
|
|
/* Is it the auth query handler? */
|
|
path = nsurl_get_component(params->url, NSURL_PATH);
|
|
if (path == corestring_lwc_query_auth) {
|
|
lwc_string_unref(path);
|
|
return navigate_internal_query_auth(bw, params);
|
|
}
|
|
if (path == corestring_lwc_query_ssl) {
|
|
lwc_string_unref(path);
|
|
return navigate_internal_query_ssl(bw, params);
|
|
}
|
|
if (path == corestring_lwc_query_timeout) {
|
|
lwc_string_unref(path);
|
|
return navigate_internal_query_timeout(bw, params);
|
|
}
|
|
if (path == corestring_lwc_query_fetcherror) {
|
|
lwc_string_unref(path);
|
|
return navigate_internal_query_fetcherror(bw, params);
|
|
}
|
|
lwc_string_unref(path);
|
|
|
|
/* Fall through to a normal about: fetch */
|
|
|
|
normal_fetch:
|
|
return navigate_internal_real(bw, params);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
bool browser_window_up_available(struct browser_window *bw)
|
|
{
|
|
bool result = false;
|
|
|
|
if (bw != NULL && bw->current_content != NULL) {
|
|
nsurl *parent;
|
|
nserror err;
|
|
err = nsurl_parent(hlcache_handle_get_url(bw->current_content),
|
|
&parent);
|
|
if (err == NSERROR_OK) {
|
|
result = nsurl_compare(hlcache_handle_get_url(
|
|
bw->current_content),
|
|
parent,
|
|
NSURL_COMPLETE) == false;
|
|
nsurl_unref(parent);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
nserror browser_window_navigate_up(struct browser_window *bw, bool new_window)
|
|
{
|
|
nsurl *current, *parent;
|
|
nserror err;
|
|
|
|
if (bw == NULL)
|
|
return NSERROR_BAD_PARAMETER;
|
|
|
|
current = browser_window_access_url(bw);
|
|
|
|
err = nsurl_parent(current, &parent);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
|
|
if (nsurl_compare(current, parent, NSURL_COMPLETE) == true) {
|
|
/* Can't go up to parent from here */
|
|
nsurl_unref(parent);
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
if (new_window) {
|
|
err = browser_window_create(BW_CREATE_CLONE,
|
|
parent, NULL, bw, NULL);
|
|
} else {
|
|
err = browser_window_navigate(bw, parent, NULL,
|
|
BW_NAVIGATE_HISTORY,
|
|
NULL, NULL, NULL);
|
|
}
|
|
|
|
nsurl_unref(parent);
|
|
return err;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in include/netsurf/browser_window.h */
|
|
nsurl* browser_window_access_url(const struct browser_window *bw)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->current_content != NULL) {
|
|
return hlcache_handle_get_url(bw->current_content);
|
|
|
|
} else if (bw->loading_content != NULL) {
|
|
/* TODO: should we return this? */
|
|
return hlcache_handle_get_url(bw->loading_content);
|
|
}
|
|
|
|
return corestring_nsurl_about_blank;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in include/netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_get_url(struct browser_window *bw, bool fragment,nsurl** url_out)
|
|
{
|
|
nserror err;
|
|
nsurl *url;
|
|
|
|
assert(bw != NULL);
|
|
|
|
if (!fragment || bw->frag_id == NULL || bw->loading_content != NULL) {
|
|
/* If there's a loading content, then the bw->frag_id will have
|
|
* been trampled, possibly with a new frag_id, but we will
|
|
* still be returning the current URL, so in this edge case
|
|
* we just drop any fragment. */
|
|
url = nsurl_ref(browser_window_access_url(bw));
|
|
|
|
} else {
|
|
err = nsurl_refragment(browser_window_access_url(bw),
|
|
bw->frag_id, &url);
|
|
if (err != NSERROR_OK) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
*url_out = url;
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
const char* browser_window_get_title(struct browser_window *bw)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->current_content != NULL) {
|
|
return content_get_title(bw->current_content);
|
|
}
|
|
|
|
/* no content so return about:blank */
|
|
return nsurl_access(corestring_nsurl_about_blank);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
struct history * browser_window_get_history(struct browser_window *bw)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
return bw->history;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
bool browser_window_has_content(struct browser_window *bw)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->current_content == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
struct hlcache_handle *browser_window_get_content(struct browser_window *bw)
|
|
{
|
|
return bw->current_content;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
nserror browser_window_get_extents(struct browser_window *bw, bool scaled,
|
|
int *width, int *height)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->current_content == NULL) {
|
|
*width = 0;
|
|
*height = 0;
|
|
return NSERROR_BAD_CONTENT;
|
|
}
|
|
|
|
*width = content_get_width(bw->current_content);
|
|
*height = content_get_height(bw->current_content);
|
|
|
|
if (scaled) {
|
|
*width *= bw->scale;
|
|
*height *= bw->scale;
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* exported internal interface, documented in desktop/browser_private.h */
|
|
nserror
|
|
browser_window_get_dimensions(struct browser_window *bw,
|
|
int *width,
|
|
int *height)
|
|
{
|
|
nserror res;
|
|
assert(bw);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window */
|
|
*width = bw->width;
|
|
*height = bw->height;
|
|
res = NSERROR_OK;
|
|
} else {
|
|
/* Front end window */
|
|
res = guit->window->get_dimensions(bw->window, width, height);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
void
|
|
browser_window_set_dimensions(struct browser_window *bw, int width, int height)
|
|
{
|
|
assert(bw);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window */
|
|
bw->width = width;
|
|
bw->height = height;
|
|
} else {
|
|
NSLOG(netsurf, INFO,
|
|
"Asked to set dimensions of front end window.");
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
void browser_window_update(struct browser_window *bw, bool scroll_to_top)
|
|
{
|
|
static const struct rect zrect = {
|
|
.x0 = 0,
|
|
.y0 = 0,
|
|
.x1 = 0,
|
|
.y1 = 0
|
|
};
|
|
|
|
if (bw->current_content == NULL) {
|
|
return;
|
|
}
|
|
|
|
switch (bw->browser_window_type) {
|
|
|
|
case BROWSER_WINDOW_NORMAL:
|
|
/* Root browser window, constituting a front end window/tab */
|
|
guit->window->set_title(bw->window,
|
|
content_get_title(bw->current_content));
|
|
|
|
browser_window_update_extent(bw);
|
|
|
|
/* if frag_id exists, then try to scroll to it */
|
|
/** @todo don't do this if the user has scrolled */
|
|
if (!frag_scroll(bw)) {
|
|
if (scroll_to_top) {
|
|
browser_window_set_scroll(bw, &zrect);
|
|
}
|
|
}
|
|
|
|
guit->window->invalidate(bw->window, NULL);
|
|
|
|
break;
|
|
|
|
case BROWSER_WINDOW_IFRAME:
|
|
/* Internal iframe browser window */
|
|
assert(bw->parent != NULL);
|
|
assert(bw->parent->current_content != NULL);
|
|
|
|
browser_window_update_extent(bw);
|
|
|
|
if (scroll_to_top) {
|
|
browser_window_set_scroll(bw, &zrect);
|
|
}
|
|
|
|
/* if frag_id exists, then try to scroll to it */
|
|
/** @todo don't do this if the user has scrolled */
|
|
frag_scroll(bw);
|
|
|
|
html_redraw_a_box(bw->parent->current_content, bw->box);
|
|
break;
|
|
|
|
case BROWSER_WINDOW_FRAME:
|
|
{
|
|
struct rect rect;
|
|
browser_window_update_extent(bw);
|
|
|
|
if (scroll_to_top) {
|
|
browser_window_set_scroll(bw, &zrect);
|
|
}
|
|
|
|
/* if frag_id exists, then try to scroll to it */
|
|
/** @todo don't do this if the user has scrolled */
|
|
frag_scroll(bw);
|
|
|
|
rect.x0 = scrollbar_get_offset(bw->scroll_x);
|
|
rect.y0 = scrollbar_get_offset(bw->scroll_y);
|
|
rect.x1 = rect.x0 + bw->width;
|
|
rect.y1 = rect.y0 + bw->height;
|
|
|
|
browser_window_update_box(bw, &rect);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
case BROWSER_WINDOW_FRAMESET:
|
|
/* Nothing to do */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
void browser_window_update_box(struct browser_window *bw, struct rect *rect)
|
|
{
|
|
int pos_x;
|
|
int pos_y;
|
|
struct browser_window *top = bw;
|
|
|
|
assert(bw);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window */
|
|
browser_window_get_position(bw, true, &pos_x, &pos_y);
|
|
|
|
top = browser_window_get_root(bw);
|
|
|
|
rect->x0 += pos_x / bw->scale;
|
|
rect->y0 += pos_y / bw->scale;
|
|
rect->x1 += pos_x / bw->scale;
|
|
rect->y1 += pos_y / bw->scale;
|
|
}
|
|
|
|
rect->x0 *= top->scale;
|
|
rect->y0 *= top->scale;
|
|
rect->x1 *= top->scale;
|
|
rect->y1 *= top->scale;
|
|
|
|
guit->window->invalidate(top->window, rect);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
void browser_window_stop(struct browser_window *bw)
|
|
{
|
|
int children, index;
|
|
|
|
if (bw->loading_content != NULL) {
|
|
hlcache_handle_abort(bw->loading_content);
|
|
hlcache_handle_release(bw->loading_content);
|
|
bw->loading_content = NULL;
|
|
}
|
|
|
|
if (bw->current_content != NULL &&
|
|
content_get_status(bw->current_content) != CONTENT_STATUS_DONE) {
|
|
nserror error;
|
|
assert(content_get_status(bw->current_content) ==
|
|
CONTENT_STATUS_READY);
|
|
error = hlcache_handle_abort(bw->current_content);
|
|
assert(error == NSERROR_OK);
|
|
}
|
|
|
|
guit->misc->schedule(-1, browser_window_refresh, bw);
|
|
|
|
if (bw->children) {
|
|
children = bw->rows * bw->cols;
|
|
for (index = 0; index < children; index++)
|
|
browser_window_stop(&bw->children[index]);
|
|
}
|
|
if (bw->iframes) {
|
|
children = bw->iframe_count;
|
|
for (index = 0; index < children; index++)
|
|
browser_window_stop(&bw->iframes[index]);
|
|
}
|
|
|
|
if (bw->current_content != NULL) {
|
|
browser_window_refresh_url_bar(bw);
|
|
}
|
|
|
|
browser_window_stop_throbber(bw);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
nserror browser_window_reload(struct browser_window *bw, bool all)
|
|
{
|
|
hlcache_handle *c;
|
|
unsigned int i;
|
|
struct nsurl *reload_url;
|
|
|
|
if ((bw->current_content) == NULL ||
|
|
(bw->loading_content) != NULL) {
|
|
return NSERROR_INVALID;
|
|
}
|
|
|
|
if (all && content_get_type(bw->current_content) == CONTENT_HTML) {
|
|
struct html_stylesheet *sheets;
|
|
struct content_html_object *object;
|
|
unsigned int count;
|
|
|
|
c = bw->current_content;
|
|
|
|
/* invalidate objects */
|
|
object = html_get_objects(c, &count);
|
|
|
|
for (; object != NULL; object = object->next) {
|
|
if (object->content != NULL)
|
|
content_invalidate_reuse_data(object->content);
|
|
}
|
|
|
|
/* invalidate stylesheets */
|
|
sheets = html_get_stylesheets(c, &count);
|
|
|
|
for (i = STYLESHEET_START; i != count; i++) {
|
|
if (sheets[i].sheet != NULL) {
|
|
content_invalidate_reuse_data(sheets[i].sheet);
|
|
}
|
|
}
|
|
}
|
|
|
|
content_invalidate_reuse_data(bw->current_content);
|
|
|
|
reload_url = hlcache_handle_get_url(bw->current_content);
|
|
|
|
return browser_window_navigate(bw,
|
|
reload_url,
|
|
NULL,
|
|
BW_NAVIGATE_NONE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
void browser_window_set_status(struct browser_window *bw, const char *text)
|
|
{
|
|
int text_len;
|
|
/* find topmost window */
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
|
|
if ((bw->status.text != NULL) &&
|
|
(strcmp(text, bw->status.text) == 0)) {
|
|
/* status text is unchanged */
|
|
bw->status.match++;
|
|
return;
|
|
}
|
|
|
|
/* status text is changed */
|
|
|
|
text_len = strlen(text);
|
|
|
|
if ((bw->status.text == NULL) || (bw->status.text_len < text_len)) {
|
|
/* no current string allocation or it is not long enough */
|
|
free(bw->status.text);
|
|
bw->status.text = strdup(text);
|
|
bw->status.text_len = text_len;
|
|
} else {
|
|
/* current allocation has enough space */
|
|
memcpy(bw->status.text, text, text_len + 1);
|
|
}
|
|
|
|
bw->status.miss++;
|
|
guit->window->set_status(bw->window, bw->status.text);
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in netsurf/browser_window.h */
|
|
void browser_window_set_pointer(struct browser_window *bw,
|
|
browser_pointer_shape shape)
|
|
{
|
|
struct browser_window *root = browser_window_get_root(bw);
|
|
gui_pointer_shape gui_shape;
|
|
bool loading;
|
|
uint64_t ms_now;
|
|
|
|
assert(root);
|
|
assert(root->window);
|
|
|
|
loading = ((bw->loading_content != NULL) ||
|
|
((bw->current_content != NULL) &&
|
|
(content_get_status(bw->current_content) == CONTENT_STATUS_READY)));
|
|
|
|
nsu_getmonotonic_ms(&ms_now);
|
|
|
|
if (loading && ((ms_now - bw->last_action) < 1000)) {
|
|
/* If loading and less than 1 second since last link followed,
|
|
* force progress indicator pointer */
|
|
gui_shape = GUI_POINTER_PROGRESS;
|
|
|
|
} else if (shape == BROWSER_POINTER_AUTO) {
|
|
/* Up to browser window to decide */
|
|
if (loading) {
|
|
gui_shape = GUI_POINTER_PROGRESS;
|
|
} else {
|
|
gui_shape = GUI_POINTER_DEFAULT;
|
|
}
|
|
|
|
} else {
|
|
/* Use what we were told */
|
|
gui_shape = (gui_pointer_shape)shape;
|
|
}
|
|
|
|
guit->window->set_pointer(root->window, gui_shape);
|
|
}
|
|
|
|
|
|
/* exported function documented in netsurf/browser_window.h */
|
|
nserror browser_window_schedule_reformat(struct browser_window *bw)
|
|
{
|
|
if (bw->window == NULL) {
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
|
|
return guit->misc->schedule(0, scheduled_reformat, bw);
|
|
}
|
|
|
|
|
|
/* exported function documented in netsurf/browser_window.h */
|
|
void
|
|
browser_window_reformat(struct browser_window *bw,
|
|
bool background,
|
|
int width,
|
|
int height)
|
|
{
|
|
hlcache_handle *c = bw->current_content;
|
|
|
|
if (c == NULL)
|
|
return;
|
|
|
|
if (bw->browser_window_type != BROWSER_WINDOW_IFRAME) {
|
|
/* Iframe dimensions are already scaled in parent's layout */
|
|
width /= bw->scale;
|
|
height /= bw->scale;
|
|
}
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window; subtract scrollbar width */
|
|
width -= bw->scroll_y ? SCROLLBAR_WIDTH : 0;
|
|
height -= bw->scroll_x ? SCROLLBAR_WIDTH : 0;
|
|
|
|
width = width > 0 ? width : 0;
|
|
height = height > 0 ? height : 0;
|
|
}
|
|
|
|
content_reformat(c, background, width, height);
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
nserror
|
|
browser_window_set_scale(struct browser_window *bw, float scale, bool absolute)
|
|
{
|
|
nserror res;
|
|
|
|
/* get top browser window */
|
|
while (bw->parent) {
|
|
bw = bw->parent;
|
|
}
|
|
|
|
if (!absolute) {
|
|
/* snap small values around 1.0 */
|
|
if ((scale + bw->scale) > (1.01 - scale) &&
|
|
(scale + bw->scale) < (0.99 + scale)) {
|
|
scale = 1.0;
|
|
} else {
|
|
scale += bw->scale;
|
|
}
|
|
}
|
|
|
|
/* clamp range between 0.1 and 10 (10% and 1000%) */
|
|
if (scale < SCALE_MINIMUM) {
|
|
scale = SCALE_MINIMUM;
|
|
} else if (scale > SCALE_MAXIMUM) {
|
|
scale = SCALE_MAXIMUM;
|
|
}
|
|
|
|
res = browser_window_set_scale_internal(bw, scale);
|
|
if (res == NSERROR_OK) {
|
|
browser_window_recalculate_frameset(bw);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
float browser_window_get_scale(struct browser_window *bw)
|
|
{
|
|
if (bw == NULL) {
|
|
return 1.0;
|
|
}
|
|
|
|
return bw->scale;
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
struct browser_window *
|
|
browser_window_find_target(struct browser_window *bw,
|
|
const char *target,
|
|
browser_mouse_state mouse)
|
|
{
|
|
struct browser_window *bw_target;
|
|
struct browser_window *top;
|
|
hlcache_handle *c;
|
|
int rdepth;
|
|
nserror error;
|
|
|
|
/* use the base target if we don't have one */
|
|
c = bw->current_content;
|
|
if (target == NULL &&
|
|
c != NULL &&
|
|
content_get_type(c) == CONTENT_HTML) {
|
|
target = html_get_base_target(c);
|
|
}
|
|
if (target == NULL) {
|
|
target = TARGET_SELF;
|
|
}
|
|
|
|
/* allow the simple case of target="_blank" to be ignored if requested
|
|
*/
|
|
if ((!(mouse & BROWSER_MOUSE_CLICK_2)) &&
|
|
(!((mouse & BROWSER_MOUSE_CLICK_2) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) &&
|
|
(!nsoption_bool(target_blank))) {
|
|
/* not a mouse button 2 click
|
|
* not a mouse button 1 click with ctrl pressed
|
|
* configured to ignore target="_blank" */
|
|
if ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank")))
|
|
return bw;
|
|
}
|
|
|
|
/* handle reserved keywords */
|
|
if (((nsoption_bool(button_2_tab)) &&
|
|
(mouse & BROWSER_MOUSE_CLICK_2))||
|
|
((!nsoption_bool(button_2_tab)) &&
|
|
((mouse & BROWSER_MOUSE_CLICK_1) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) ||
|
|
((nsoption_bool(button_2_tab)) &&
|
|
((target == TARGET_BLANK) ||
|
|
(!strcasecmp(target, "_blank"))))) {
|
|
/* open in new tab if:
|
|
* - button_2 opens in new tab and button_2 was pressed
|
|
* OR
|
|
* - button_2 doesn't open in new tabs and button_1 was
|
|
* pressed with ctrl held
|
|
* OR
|
|
* - button_2 opens in new tab and the link target is "_blank"
|
|
*/
|
|
error = browser_window_create(BW_CREATE_TAB |
|
|
BW_CREATE_HISTORY |
|
|
BW_CREATE_CLONE,
|
|
NULL,
|
|
NULL,
|
|
bw,
|
|
&bw_target);
|
|
if (error != NSERROR_OK) {
|
|
return bw;
|
|
}
|
|
return bw_target;
|
|
} else if (((!nsoption_bool(button_2_tab)) &&
|
|
(mouse & BROWSER_MOUSE_CLICK_2)) ||
|
|
((nsoption_bool(button_2_tab)) &&
|
|
((mouse & BROWSER_MOUSE_CLICK_1) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) ||
|
|
((!nsoption_bool(button_2_tab)) &&
|
|
((target == TARGET_BLANK) ||
|
|
(!strcasecmp(target, "_blank"))))) {
|
|
/* open in new window if:
|
|
* - button_2 doesn't open in new tabs and button_2 was pressed
|
|
* OR
|
|
* - button_2 opens in new tab and button_1 was pressed with
|
|
* ctrl held
|
|
* OR
|
|
* - button_2 doesn't open in new tabs and the link target is
|
|
* "_blank"
|
|
*/
|
|
error = browser_window_create(BW_CREATE_HISTORY |
|
|
BW_CREATE_CLONE,
|
|
NULL,
|
|
NULL,
|
|
bw,
|
|
&bw_target);
|
|
if (error != NSERROR_OK) {
|
|
return bw;
|
|
}
|
|
return bw_target;
|
|
} else if ((target == TARGET_SELF) || (!strcasecmp(target, "_self"))) {
|
|
return bw;
|
|
} else if ((target == TARGET_PARENT) ||
|
|
(!strcasecmp(target, "_parent"))) {
|
|
if (bw->parent)
|
|
return bw->parent;
|
|
return bw;
|
|
} else if ((target == TARGET_TOP) || (!strcasecmp(target, "_top"))) {
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
return bw;
|
|
}
|
|
|
|
/* find frame according to B.8, ie using the following priorities:
|
|
*
|
|
* 1) current frame
|
|
* 2) closest to front
|
|
*/
|
|
rdepth = -1;
|
|
bw_target = NULL;
|
|
for (top = bw; top->parent; top = top->parent);
|
|
browser_window_find_target_internal(top, target, 0, bw, &rdepth,
|
|
&bw_target);
|
|
if (bw_target)
|
|
return bw_target;
|
|
|
|
/* we require a new window using the target name */
|
|
if (!nsoption_bool(target_blank))
|
|
return bw;
|
|
|
|
error = browser_window_create(BW_CREATE_CLONE | BW_CREATE_HISTORY,
|
|
NULL,
|
|
NULL,
|
|
bw,
|
|
&bw_target);
|
|
if (error != NSERROR_OK) {
|
|
return bw;
|
|
}
|
|
|
|
/* frame names should begin with an alphabetic character (a-z,A-Z),
|
|
* however in practice you get things such as '_new' and '2left'. The
|
|
* only real effect this has is when giving out names as it can be
|
|
* assumed that an author intended '_new' to create a new nameless
|
|
* window (ie '_blank') whereas in the case of '2left' the intention
|
|
* was for a new named window. As such we merely special case windows
|
|
* that begin with an underscore. */
|
|
if (target[0] != '_') {
|
|
bw_target->name = strdup(target);
|
|
}
|
|
return bw_target;
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
void
|
|
browser_window_mouse_track(struct browser_window *bw,
|
|
browser_mouse_state mouse,
|
|
int x, int y)
|
|
{
|
|
browser_window_mouse_track_internal(bw,
|
|
mouse,
|
|
x / bw->scale,
|
|
y / bw->scale);
|
|
}
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
void
|
|
browser_window_mouse_click(struct browser_window *bw,
|
|
browser_mouse_state mouse,
|
|
int x, int y)
|
|
{
|
|
browser_window_mouse_click_internal(bw,
|
|
mouse,
|
|
x / bw->scale,
|
|
y / bw->scale);
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
void browser_window_page_drag_start(struct browser_window *bw, int x, int y)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
browser_window_set_drag_type(bw, DRAGGING_PAGE_SCROLL, NULL);
|
|
|
|
bw->drag.start_x = x;
|
|
bw->drag.start_y = y;
|
|
|
|
if (bw->window != NULL) {
|
|
/* Front end window */
|
|
guit->window->get_scroll(bw->window,
|
|
&bw->drag.start_scroll_x,
|
|
&bw->drag.start_scroll_y);
|
|
|
|
guit->window->event(bw->window, GW_EVENT_SCROLL_START);
|
|
} else {
|
|
/* Core managed browser window */
|
|
bw->drag.start_scroll_x = scrollbar_get_offset(bw->scroll_x);
|
|
bw->drag.start_scroll_y = scrollbar_get_offset(bw->scroll_y);
|
|
}
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
bool browser_window_back_available(struct browser_window *bw)
|
|
{
|
|
if (bw != NULL && bw->internal_nav) {
|
|
/* Internal nav, back is possible */
|
|
return true;
|
|
}
|
|
return (bw && bw->history && browser_window_history_back_available(bw));
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
bool browser_window_forward_available(struct browser_window *bw)
|
|
{
|
|
return (bw && bw->history && browser_window_history_forward_available(bw));
|
|
}
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
bool browser_window_reload_available(struct browser_window *bw)
|
|
{
|
|
return (bw && bw->current_content && !bw->loading_content);
|
|
}
|
|
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
bool browser_window_stop_available(struct browser_window *bw)
|
|
{
|
|
return (bw && (bw->loading_content ||
|
|
(bw->current_content &&
|
|
(content_get_status(bw->current_content) !=
|
|
CONTENT_STATUS_DONE))));
|
|
}
|
|
|
|
/* exported interface documented in netsurf/browser_window.h */
|
|
bool
|
|
browser_window_exec(struct browser_window *bw, const char *src, size_t srclen)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (!bw->current_content) {
|
|
NSLOG(netsurf, DEEPDEBUG, "Unable to exec, no content");
|
|
return false;
|
|
}
|
|
|
|
if (content_get_status(bw->current_content) != CONTENT_STATUS_DONE) {
|
|
NSLOG(netsurf, DEEPDEBUG, "Unable to exec, content not done");
|
|
return false;
|
|
}
|
|
|
|
/* Okay it should be safe, forward the request through to the content
|
|
* itself. Only HTML contents currently support executing code
|
|
*/
|
|
return content_exec(bw->current_content, src, srclen);
|
|
}
|
|
|
|
|
|
/* exported interface documented in browser_window.h */
|
|
nserror
|
|
browser_window_console_log(struct browser_window *bw,
|
|
browser_window_console_source src,
|
|
const char *msg,
|
|
size_t msglen,
|
|
browser_window_console_flags flags)
|
|
{
|
|
browser_window_console_flags log_level = flags & BW_CS_FLAG_LEVEL_MASK;
|
|
struct browser_window *root = browser_window_get_root(bw);
|
|
|
|
assert(msg != NULL);
|
|
/* We don't assert msglen > 0, if someone wants to log a real empty
|
|
* string then we won't stop them. It does sometimes happen from
|
|
* JavaScript for example.
|
|
*/
|
|
|
|
/* bw is the target of the log, but root is where we log it */
|
|
|
|
NSLOG(netsurf, DEEPDEBUG, "Logging message in %p targetted at %p", root, bw);
|
|
NSLOG(netsurf, DEEPDEBUG, "Log came from %s",
|
|
((src == BW_CS_INPUT) ? "user input" :
|
|
(src == BW_CS_SCRIPT_ERROR) ? "script error" :
|
|
(src == BW_CS_SCRIPT_CONSOLE) ? "script console" :
|
|
"unknown input location"));
|
|
|
|
switch (log_level) {
|
|
case BW_CS_FLAG_LEVEL_DEBUG:
|
|
NSLOG(netsurf, DEBUG, "%.*s", (int)msglen, msg);
|
|
break;
|
|
case BW_CS_FLAG_LEVEL_LOG:
|
|
NSLOG(netsurf, VERBOSE, "%.*s", (int)msglen, msg);
|
|
break;
|
|
case BW_CS_FLAG_LEVEL_INFO:
|
|
NSLOG(netsurf, INFO, "%.*s", (int)msglen, msg);
|
|
break;
|
|
case BW_CS_FLAG_LEVEL_WARN:
|
|
NSLOG(netsurf, WARNING, "%.*s", (int)msglen, msg);
|
|
break;
|
|
case BW_CS_FLAG_LEVEL_ERROR:
|
|
NSLOG(netsurf, ERROR, "%.*s", (int)msglen, msg);
|
|
break;
|
|
default:
|
|
/* Unreachable */
|
|
break;
|
|
}
|
|
|
|
guit->window->console_log(root->window, src, msg, msglen, flags);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* Exported interface, documented in browser_private.h */
|
|
nserror
|
|
browser_window__reload_current_parameters(struct browser_window *bw)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->current_parameters.post_urlenc != NULL) {
|
|
free(bw->current_parameters.post_urlenc);
|
|
bw->current_parameters.post_urlenc = NULL;
|
|
}
|
|
|
|
if (bw->current_parameters.post_multipart != NULL) {
|
|
fetch_multipart_data_destroy(bw->current_parameters.post_multipart);
|
|
bw->current_parameters.post_multipart = NULL;
|
|
}
|
|
|
|
if (bw->current_parameters.url == NULL) {
|
|
/* We have never navigated so go to about:blank */
|
|
bw->current_parameters.url = nsurl_ref(corestring_nsurl_about_blank);
|
|
}
|
|
|
|
bw->current_parameters.flags &= ~BW_NAVIGATE_HISTORY;
|
|
bw->internal_nav = false;
|
|
|
|
browser_window__free_fetch_parameters(&bw->loading_parameters);
|
|
memcpy(&bw->loading_parameters, &bw->current_parameters, sizeof(bw->loading_parameters));
|
|
memset(&bw->current_parameters, 0, sizeof(bw->current_parameters));
|
|
return browser_window__navigate_internal(bw, &bw->loading_parameters);
|
|
}
|
|
|
|
/* Exported interface, documented in browser_window.h */
|
|
browser_window_page_info_state browser_window_get_page_info_state(
|
|
const struct browser_window *bw)
|
|
{
|
|
lwc_string *scheme;
|
|
bool match;
|
|
|
|
assert(bw != NULL);
|
|
|
|
/* Do we have any content? If not -- UNKNOWN */
|
|
if (bw->current_content == NULL) {
|
|
return PAGE_STATE_UNKNOWN;
|
|
}
|
|
|
|
scheme = nsurl_get_component(
|
|
hlcache_handle_get_url(bw->current_content), NSURL_SCHEME);
|
|
|
|
/* Is this an internal scheme? */
|
|
if ((lwc_string_isequal(scheme, corestring_lwc_about,
|
|
&match) == lwc_error_ok &&
|
|
(match == true)) ||
|
|
(lwc_string_isequal(scheme, corestring_lwc_data,
|
|
&match) == lwc_error_ok &&
|
|
(match == true)) ||
|
|
(lwc_string_isequal(scheme, corestring_lwc_resource,
|
|
&match) == lwc_error_ok &&
|
|
(match == true))) {
|
|
lwc_string_unref(scheme);
|
|
return PAGE_STATE_INTERNAL;
|
|
}
|
|
|
|
/* Is this file:/// ? */
|
|
if (lwc_string_isequal(scheme, corestring_lwc_file,
|
|
&match) == lwc_error_ok &&
|
|
match == true) {
|
|
lwc_string_unref(scheme);
|
|
return PAGE_STATE_LOCAL;
|
|
}
|
|
|
|
/* If not https, from here on down that'd be insecure */
|
|
if ((lwc_string_isequal(scheme, corestring_lwc_https,
|
|
&match) == lwc_error_ok &&
|
|
(match == false))) {
|
|
/* Some remote content, not https, therefore insecure */
|
|
lwc_string_unref(scheme);
|
|
return PAGE_STATE_INSECURE;
|
|
}
|
|
|
|
lwc_string_unref(scheme);
|
|
|
|
/* Did we have to override this SSL setting? */
|
|
if (urldb_get_cert_permissions(hlcache_handle_get_url(bw->current_content))) {
|
|
return PAGE_STATE_SECURE_OVERRIDE;
|
|
}
|
|
|
|
/* If we've seen insecure content internally then we need to say so */
|
|
if (content_saw_insecure_objects(bw->current_content)) {
|
|
return PAGE_STATE_SECURE_ISSUES;
|
|
}
|
|
|
|
/* All is well, return secure state */
|
|
return PAGE_STATE_SECURE;
|
|
}
|
|
|
|
/* Exported interface, documented in browser_window.h */
|
|
nserror
|
|
browser_window_get_ssl_chain(struct browser_window *bw,
|
|
struct cert_chain **chain)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->current_cert_chain == NULL) {
|
|
return NSERROR_NOT_FOUND;
|
|
}
|
|
|
|
*chain = bw->current_cert_chain;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/* Exported interface, documented in browser_window.h */
|
|
int browser_window_get_cookie_count(
|
|
const struct browser_window *bw)
|
|
{
|
|
/** \todo Implement cookie count */
|
|
return 0;
|
|
}
|
|
|
|
/* Exported interface, documented in browser_window.h */
|
|
nserror browser_window_show_cookies(
|
|
const struct browser_window *bw)
|
|
{
|
|
nserror err;
|
|
nsurl *url = browser_window_access_url(bw);
|
|
lwc_string *host = nsurl_get_component(url, NSURL_HOST);
|
|
const char *string = (host != NULL) ? lwc_string_data(host) : NULL;
|
|
|
|
/** \todo Ensure cookie manager is open. (Ask front end.) */
|
|
|
|
err = cookie_manager_set_search_string(string);
|
|
|
|
if (host != NULL) {
|
|
lwc_string_unref(host);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/* Exported interface, documented in browser_window.h */
|
|
nserror browser_window_show_certificates(struct browser_window *bw)
|
|
{
|
|
nserror res;
|
|
nsurl *url;
|
|
size_t allocsize;
|
|
size_t urlstrlen;
|
|
uint8_t *urlstr;
|
|
size_t depth;
|
|
|
|
if (bw->current_cert_chain == NULL) {
|
|
return NSERROR_NOT_FOUND;
|
|
}
|
|
|
|
allocsize = 20;
|
|
for (depth = 0; depth < bw->current_cert_chain->depth; depth++) {
|
|
allocsize += 7; /* allow for &cert= */
|
|
allocsize += 4 * ((bw->current_cert_chain->certs[depth].der_length + 2) / 3);
|
|
}
|
|
|
|
urlstr = malloc(allocsize);
|
|
if (urlstr == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
urlstrlen = snprintf((char *)urlstr, allocsize, "about:certificate");
|
|
for (depth = 0; depth < bw->current_cert_chain->depth; depth++) {
|
|
nsuerror nsures;
|
|
size_t output_length;
|
|
|
|
urlstrlen += snprintf((char *)urlstr + urlstrlen,
|
|
allocsize - urlstrlen,
|
|
"&cert=");
|
|
|
|
output_length = allocsize - urlstrlen;
|
|
nsures = nsu_base64_encode_url(
|
|
bw->current_cert_chain->certs[depth].der,
|
|
bw->current_cert_chain->certs[depth].der_length,
|
|
(uint8_t *)urlstr + urlstrlen,
|
|
&output_length);
|
|
if (nsures != NSUERROR_OK) {
|
|
free(urlstr);
|
|
return (nserror)nsures;
|
|
}
|
|
urlstrlen += output_length;
|
|
}
|
|
urlstr[17] = '?';
|
|
urlstr[urlstrlen] = 0;
|
|
|
|
res = nsurl_create((const char *)urlstr, &url);
|
|
free(urlstr);
|
|
if (res != NSERROR_OK) {
|
|
return res;
|
|
}
|
|
|
|
res = browser_window_create(BW_CREATE_HISTORY |
|
|
BW_CREATE_FOREGROUND |
|
|
BW_CREATE_TAB,
|
|
url,
|
|
NULL,
|
|
bw,
|
|
NULL);
|
|
|
|
nsurl_unref(url);
|
|
|
|
return res;
|
|
}
|